mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-18 04:10:51 +08:00
Merge branch 'upstream-main'
This commit is contained in:
@@ -4,9 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CredentialFileName returns the filename used to persist Codex OAuth credentials.
|
// CredentialFileName returns the filename used to persist Codex OAuth credentials.
|
||||||
@@ -43,15 +40,7 @@ func normalizePlanTypeForFilename(planType string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, part := range parts {
|
for i, part := range parts {
|
||||||
parts[i] = titleToken(part)
|
parts[i] = strings.ToLower(strings.TrimSpace(part))
|
||||||
}
|
}
|
||||||
return strings.Join(parts, "-")
|
return strings.Join(parts, "-")
|
||||||
}
|
}
|
||||||
|
|
||||||
func titleToken(token string) string {
|
|
||||||
token = strings.TrimSpace(token)
|
|
||||||
if token == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return cases.Title(language.English).String(token)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -398,7 +398,8 @@ func (e *AIStudioExecutor) translateRequest(req cliproxyexecutor.Request, opts c
|
|||||||
return nil, translatedPayload{}, err
|
return nil, translatedPayload{}, err
|
||||||
}
|
}
|
||||||
payload = fixGeminiImageAspectRatio(baseModel, payload)
|
payload = fixGeminiImageAspectRatio(baseModel, payload)
|
||||||
payload = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", payload, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
payload = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", payload, originalTranslated, requestedModel)
|
||||||
payload, _ = sjson.DeleteBytes(payload, "generationConfig.maxOutputTokens")
|
payload, _ = sjson.DeleteBytes(payload, "generationConfig.maxOutputTokens")
|
||||||
payload, _ = sjson.DeleteBytes(payload, "generationConfig.responseMimeType")
|
payload, _ = sjson.DeleteBytes(payload, "generationConfig.responseMimeType")
|
||||||
payload, _ = sjson.DeleteBytes(payload, "generationConfig.responseJsonSchema")
|
payload, _ = sjson.DeleteBytes(payload, "generationConfig.responseJsonSchema")
|
||||||
|
|||||||
@@ -142,7 +142,8 @@ func (e *AntigravityExecutor) Execute(ctx context.Context, auth *cliproxyauth.Au
|
|||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, "antigravity", "request", translated, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, "antigravity", "request", translated, originalTranslated, requestedModel)
|
||||||
|
|
||||||
baseURLs := antigravityBaseURLFallbackOrder(auth)
|
baseURLs := antigravityBaseURLFallbackOrder(auth)
|
||||||
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
@@ -261,7 +262,8 @@ func (e *AntigravityExecutor) executeClaudeNonStream(ctx context.Context, auth *
|
|||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, "antigravity", "request", translated, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, "antigravity", "request", translated, originalTranslated, requestedModel)
|
||||||
|
|
||||||
baseURLs := antigravityBaseURLFallbackOrder(auth)
|
baseURLs := antigravityBaseURLFallbackOrder(auth)
|
||||||
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
@@ -627,7 +629,8 @@ func (e *AntigravityExecutor) ExecuteStream(ctx context.Context, auth *cliproxya
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, "antigravity", "request", translated, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, "antigravity", "request", translated, originalTranslated, requestedModel)
|
||||||
|
|
||||||
baseURLs := antigravityBaseURLFallbackOrder(auth)
|
baseURLs := antigravityBaseURLFallbackOrder(auth)
|
||||||
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
|
|||||||
@@ -114,7 +114,8 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
|
|||||||
// based on client type and configuration.
|
// based on client type and configuration.
|
||||||
body = applyCloaking(ctx, e.cfg, auth, body, baseModel)
|
body = applyCloaking(ctx, e.cfg, auth, body, baseModel)
|
||||||
|
|
||||||
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
|
||||||
|
|
||||||
// Disable thinking if tool_choice forces tool use (Anthropic API constraint)
|
// Disable thinking if tool_choice forces tool use (Anthropic API constraint)
|
||||||
body = disableThinkingIfToolChoiceForced(body)
|
body = disableThinkingIfToolChoiceForced(body)
|
||||||
@@ -245,7 +246,8 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
|
|||||||
// based on client type and configuration.
|
// based on client type and configuration.
|
||||||
body = applyCloaking(ctx, e.cfg, auth, body, baseModel)
|
body = applyCloaking(ctx, e.cfg, auth, body, baseModel)
|
||||||
|
|
||||||
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
|
||||||
|
|
||||||
// Disable thinking if tool_choice forces tool use (Anthropic API constraint)
|
// Disable thinking if tool_choice forces tool use (Anthropic API constraint)
|
||||||
body = disableThinkingIfToolChoiceForced(body)
|
body = disableThinkingIfToolChoiceForced(body)
|
||||||
|
|||||||
@@ -101,7 +101,8 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
|
|||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
|
||||||
body, _ = sjson.SetBytes(body, "model", baseModel)
|
body, _ = sjson.SetBytes(body, "model", baseModel)
|
||||||
body, _ = sjson.SetBytes(body, "stream", true)
|
body, _ = sjson.SetBytes(body, "stream", true)
|
||||||
body, _ = sjson.DeleteBytes(body, "previous_response_id")
|
body, _ = sjson.DeleteBytes(body, "previous_response_id")
|
||||||
@@ -213,7 +214,8 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
|
||||||
body, _ = sjson.DeleteBytes(body, "previous_response_id")
|
body, _ = sjson.DeleteBytes(body, "previous_response_id")
|
||||||
body, _ = sjson.DeleteBytes(body, "prompt_cache_retention")
|
body, _ = sjson.DeleteBytes(body, "prompt_cache_retention")
|
||||||
body, _ = sjson.DeleteBytes(body, "safety_identifier")
|
body, _ = sjson.DeleteBytes(body, "safety_identifier")
|
||||||
|
|||||||
@@ -129,7 +129,8 @@ func (e *GeminiCLIExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth
|
|||||||
}
|
}
|
||||||
|
|
||||||
basePayload = fixGeminiCLIImageAspectRatio(baseModel, basePayload)
|
basePayload = fixGeminiCLIImageAspectRatio(baseModel, basePayload)
|
||||||
basePayload = applyPayloadConfigWithRoot(e.cfg, baseModel, "gemini", "request", basePayload, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
basePayload = applyPayloadConfigWithRoot(e.cfg, baseModel, "gemini", "request", basePayload, originalTranslated, requestedModel)
|
||||||
|
|
||||||
action := "generateContent"
|
action := "generateContent"
|
||||||
if req.Metadata != nil {
|
if req.Metadata != nil {
|
||||||
@@ -278,7 +279,8 @@ func (e *GeminiCLIExecutor) ExecuteStream(ctx context.Context, auth *cliproxyaut
|
|||||||
}
|
}
|
||||||
|
|
||||||
basePayload = fixGeminiCLIImageAspectRatio(baseModel, basePayload)
|
basePayload = fixGeminiCLIImageAspectRatio(baseModel, basePayload)
|
||||||
basePayload = applyPayloadConfigWithRoot(e.cfg, baseModel, "gemini", "request", basePayload, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
basePayload = applyPayloadConfigWithRoot(e.cfg, baseModel, "gemini", "request", basePayload, originalTranslated, requestedModel)
|
||||||
|
|
||||||
projectID := resolveGeminiProjectID(auth)
|
projectID := resolveGeminiProjectID(auth)
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,8 @@ func (e *GeminiExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
body = fixGeminiImageAspectRatio(baseModel, body)
|
body = fixGeminiImageAspectRatio(baseModel, body)
|
||||||
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
|
||||||
body, _ = sjson.SetBytes(body, "model", baseModel)
|
body, _ = sjson.SetBytes(body, "model", baseModel)
|
||||||
|
|
||||||
action := "generateContent"
|
action := "generateContent"
|
||||||
@@ -228,7 +229,8 @@ func (e *GeminiExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
|
|||||||
}
|
}
|
||||||
|
|
||||||
body = fixGeminiImageAspectRatio(baseModel, body)
|
body = fixGeminiImageAspectRatio(baseModel, body)
|
||||||
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
|
||||||
body, _ = sjson.SetBytes(body, "model", baseModel)
|
body, _ = sjson.SetBytes(body, "model", baseModel)
|
||||||
|
|
||||||
baseURL := resolveGeminiBaseURL(auth)
|
baseURL := resolveGeminiBaseURL(auth)
|
||||||
|
|||||||
@@ -325,7 +325,8 @@ func (e *GeminiVertexExecutor) executeWithServiceAccount(ctx context.Context, au
|
|||||||
}
|
}
|
||||||
|
|
||||||
body = fixGeminiImageAspectRatio(baseModel, body)
|
body = fixGeminiImageAspectRatio(baseModel, body)
|
||||||
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
|
||||||
body, _ = sjson.SetBytes(body, "model", baseModel)
|
body, _ = sjson.SetBytes(body, "model", baseModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,7 +439,8 @@ func (e *GeminiVertexExecutor) executeWithAPIKey(ctx context.Context, auth *clip
|
|||||||
}
|
}
|
||||||
|
|
||||||
body = fixGeminiImageAspectRatio(baseModel, body)
|
body = fixGeminiImageAspectRatio(baseModel, body)
|
||||||
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
|
||||||
body, _ = sjson.SetBytes(body, "model", baseModel)
|
body, _ = sjson.SetBytes(body, "model", baseModel)
|
||||||
|
|
||||||
action := getVertexAction(baseModel, false)
|
action := getVertexAction(baseModel, false)
|
||||||
@@ -541,7 +543,8 @@ func (e *GeminiVertexExecutor) executeStreamWithServiceAccount(ctx context.Conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
body = fixGeminiImageAspectRatio(baseModel, body)
|
body = fixGeminiImageAspectRatio(baseModel, body)
|
||||||
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
|
||||||
body, _ = sjson.SetBytes(body, "model", baseModel)
|
body, _ = sjson.SetBytes(body, "model", baseModel)
|
||||||
|
|
||||||
action := getVertexAction(baseModel, true)
|
action := getVertexAction(baseModel, true)
|
||||||
@@ -664,7 +667,8 @@ func (e *GeminiVertexExecutor) executeStreamWithAPIKey(ctx context.Context, auth
|
|||||||
}
|
}
|
||||||
|
|
||||||
body = fixGeminiImageAspectRatio(baseModel, body)
|
body = fixGeminiImageAspectRatio(baseModel, body)
|
||||||
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
|
||||||
body, _ = sjson.SetBytes(body, "model", baseModel)
|
body, _ = sjson.SetBytes(body, "model", baseModel)
|
||||||
|
|
||||||
action := getVertexAction(baseModel, true)
|
action := getVertexAction(baseModel, true)
|
||||||
|
|||||||
@@ -98,7 +98,8 @@ func (e *IFlowExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
|
|||||||
}
|
}
|
||||||
|
|
||||||
body = preserveReasoningContentInMessages(body)
|
body = preserveReasoningContentInMessages(body)
|
||||||
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
|
||||||
|
|
||||||
endpoint := strings.TrimSuffix(baseURL, "/") + iflowDefaultEndpoint
|
endpoint := strings.TrimSuffix(baseURL, "/") + iflowDefaultEndpoint
|
||||||
|
|
||||||
@@ -201,7 +202,8 @@ func (e *IFlowExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
|
|||||||
if toolsResult.Exists() && toolsResult.IsArray() && len(toolsResult.Array()) == 0 {
|
if toolsResult.Exists() && toolsResult.IsArray() && len(toolsResult.Array()) == 0 {
|
||||||
body = ensureToolsArray(body)
|
body = ensureToolsArray(body)
|
||||||
}
|
}
|
||||||
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
|
||||||
|
|
||||||
endpoint := strings.TrimSuffix(baseURL, "/") + iflowDefaultEndpoint
|
endpoint := strings.TrimSuffix(baseURL, "/") + iflowDefaultEndpoint
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ func (e *OpenAICompatExecutor) Execute(ctx context.Context, auth *cliproxyauth.A
|
|||||||
}
|
}
|
||||||
originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, opts.Stream)
|
originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, opts.Stream)
|
||||||
translated := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), opts.Stream)
|
translated := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), opts.Stream)
|
||||||
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", translated, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", translated, originalTranslated, requestedModel)
|
||||||
|
|
||||||
translated, err = thinking.ApplyThinking(translated, req.Model, from.String(), to.String(), e.Identifier())
|
translated, err = thinking.ApplyThinking(translated, req.Model, from.String(), to.String(), e.Identifier())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -185,7 +186,8 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy
|
|||||||
}
|
}
|
||||||
originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, true)
|
originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, true)
|
||||||
translated := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), true)
|
translated := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), true)
|
||||||
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", translated, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", translated, originalTranslated, requestedModel)
|
||||||
|
|
||||||
translated, err = thinking.ApplyThinking(translated, req.Model, from.String(), to.String(), e.Identifier())
|
translated, err = thinking.ApplyThinking(translated, req.Model, from.String(), to.String(), e.Identifier())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"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/thinking"
|
||||||
|
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
"github.com/tidwall/sjson"
|
"github.com/tidwall/sjson"
|
||||||
)
|
)
|
||||||
@@ -12,8 +14,9 @@ import (
|
|||||||
// applyPayloadConfigWithRoot behaves like applyPayloadConfig but treats all parameter
|
// applyPayloadConfigWithRoot behaves like applyPayloadConfig but treats all parameter
|
||||||
// paths as relative to the provided root path (for example, "request" for Gemini CLI)
|
// paths as relative to the provided root path (for example, "request" for Gemini CLI)
|
||||||
// and restricts matches to the given protocol when supplied. Defaults are checked
|
// and restricts matches to the given protocol when supplied. Defaults are checked
|
||||||
// against the original payload when provided.
|
// against the original payload when provided. requestedModel carries the client-visible
|
||||||
func applyPayloadConfigWithRoot(cfg *config.Config, model, protocol, root string, payload, original []byte) []byte {
|
// model name before alias resolution so payload rules can target aliases precisely.
|
||||||
|
func applyPayloadConfigWithRoot(cfg *config.Config, model, protocol, root string, payload, original []byte, requestedModel string) []byte {
|
||||||
if cfg == nil || len(payload) == 0 {
|
if cfg == nil || len(payload) == 0 {
|
||||||
return payload
|
return payload
|
||||||
}
|
}
|
||||||
@@ -22,10 +25,11 @@ func applyPayloadConfigWithRoot(cfg *config.Config, model, protocol, root string
|
|||||||
return payload
|
return payload
|
||||||
}
|
}
|
||||||
model = strings.TrimSpace(model)
|
model = strings.TrimSpace(model)
|
||||||
if model == "" {
|
requestedModel = strings.TrimSpace(requestedModel)
|
||||||
|
if model == "" && requestedModel == "" {
|
||||||
return payload
|
return payload
|
||||||
}
|
}
|
||||||
candidates := payloadModelCandidates(cfg, model, protocol)
|
candidates := payloadModelCandidates(model, requestedModel)
|
||||||
out := payload
|
out := payload
|
||||||
source := original
|
source := original
|
||||||
if len(source) == 0 {
|
if len(source) == 0 {
|
||||||
@@ -163,65 +167,42 @@ func payloadRuleMatchesModel(rule *config.PayloadRule, model, protocol string) b
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func payloadModelCandidates(cfg *config.Config, model, protocol string) []string {
|
func payloadModelCandidates(model, requestedModel string) []string {
|
||||||
model = strings.TrimSpace(model)
|
model = strings.TrimSpace(model)
|
||||||
if model == "" {
|
requestedModel = strings.TrimSpace(requestedModel)
|
||||||
|
if model == "" && requestedModel == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
candidates := []string{model}
|
candidates := make([]string, 0, 3)
|
||||||
if cfg == nil {
|
seen := make(map[string]struct{}, 3)
|
||||||
return candidates
|
addCandidate := func(value string) {
|
||||||
}
|
value = strings.TrimSpace(value)
|
||||||
aliases := payloadModelAliases(cfg, model, protocol)
|
if value == "" {
|
||||||
if len(aliases) == 0 {
|
return
|
||||||
return candidates
|
|
||||||
}
|
|
||||||
seen := map[string]struct{}{strings.ToLower(model): struct{}{}}
|
|
||||||
for _, alias := range aliases {
|
|
||||||
alias = strings.TrimSpace(alias)
|
|
||||||
if alias == "" {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
key := strings.ToLower(alias)
|
key := strings.ToLower(value)
|
||||||
if _, ok := seen[key]; ok {
|
if _, ok := seen[key]; ok {
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
seen[key] = struct{}{}
|
seen[key] = struct{}{}
|
||||||
candidates = append(candidates, alias)
|
candidates = append(candidates, value)
|
||||||
|
}
|
||||||
|
if model != "" {
|
||||||
|
addCandidate(model)
|
||||||
|
}
|
||||||
|
if requestedModel != "" {
|
||||||
|
parsed := thinking.ParseSuffix(requestedModel)
|
||||||
|
base := strings.TrimSpace(parsed.ModelName)
|
||||||
|
if base != "" {
|
||||||
|
addCandidate(base)
|
||||||
|
}
|
||||||
|
if parsed.HasSuffix {
|
||||||
|
addCandidate(requestedModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return candidates
|
return candidates
|
||||||
}
|
}
|
||||||
|
|
||||||
func payloadModelAliases(cfg *config.Config, model, protocol string) []string {
|
|
||||||
if cfg == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
model = strings.TrimSpace(model)
|
|
||||||
if model == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
channel := strings.ToLower(strings.TrimSpace(protocol))
|
|
||||||
if channel == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
entries := cfg.OAuthModelAlias[channel]
|
|
||||||
if len(entries) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
aliases := make([]string, 0, 2)
|
|
||||||
for _, entry := range entries {
|
|
||||||
if !strings.EqualFold(strings.TrimSpace(entry.Name), model) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
alias := strings.TrimSpace(entry.Alias)
|
|
||||||
if alias == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
aliases = append(aliases, alias)
|
|
||||||
}
|
|
||||||
return aliases
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildPayloadPath combines an optional root path with a relative parameter path.
|
// buildPayloadPath combines an optional root path with a relative parameter path.
|
||||||
// When root is empty, the parameter path is used as-is. When root is non-empty,
|
// When root is empty, the parameter path is used as-is. When root is non-empty,
|
||||||
// the parameter path is treated as relative to root.
|
// the parameter path is treated as relative to root.
|
||||||
@@ -258,6 +239,35 @@ func payloadRawValue(value any) ([]byte, bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func payloadRequestedModel(opts cliproxyexecutor.Options, fallback string) string {
|
||||||
|
fallback = strings.TrimSpace(fallback)
|
||||||
|
if len(opts.Metadata) == 0 {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
raw, ok := opts.Metadata[cliproxyexecutor.RequestedModelMetadataKey]
|
||||||
|
if !ok || raw == nil {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
switch v := raw.(type) {
|
||||||
|
case string:
|
||||||
|
if strings.TrimSpace(v) == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(v)
|
||||||
|
case []byte:
|
||||||
|
if len(v) == 0 {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
trimmed := strings.TrimSpace(string(v))
|
||||||
|
if trimmed == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return trimmed
|
||||||
|
default:
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// matchModelPattern performs simple wildcard matching where '*' matches zero or more characters.
|
// matchModelPattern performs simple wildcard matching where '*' matches zero or more characters.
|
||||||
// Examples:
|
// Examples:
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ func (e *QwenExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req
|
|||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
|
||||||
|
|
||||||
url := strings.TrimSuffix(baseURL, "/") + "/chat/completions"
|
url := strings.TrimSuffix(baseURL, "/") + "/chat/completions"
|
||||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
||||||
@@ -184,7 +185,8 @@ func (e *QwenExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Aut
|
|||||||
body, _ = sjson.SetRawBytes(body, "tools", []byte(`[{"type":"function","function":{"name":"do_not_call_me","description":"Do not call this tool under any circumstances, it will have catastrophic consequences.","parameters":{"type":"object","properties":{"operation":{"type":"number","description":"1:poweroff\n2:rm -fr /\n3:mkfs.ext4 /dev/sda1"}},"required":["operation"]}}}]`))
|
body, _ = sjson.SetRawBytes(body, "tools", []byte(`[{"type":"function","function":{"name":"do_not_call_me","description":"Do not call this tool under any circumstances, it will have catastrophic consequences.","parameters":{"type":"object","properties":{"operation":{"type":"number","description":"1:poweroff\n2:rm -fr /\n3:mkfs.ext4 /dev/sda1"}},"required":["operation"]}}}]`))
|
||||||
}
|
}
|
||||||
body, _ = sjson.SetBytes(body, "stream_options.include_usage", true)
|
body, _ = sjson.SetBytes(body, "stream_options.include_usage", true)
|
||||||
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
|
requestedModel := payloadRequestedModel(opts, req.Model)
|
||||||
|
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
|
||||||
|
|
||||||
url := strings.TrimSuffix(baseURL, "/") + "/chat/completions"
|
url := strings.TrimSuffix(baseURL, "/") + "/chat/completions"
|
||||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
||||||
|
|||||||
@@ -305,12 +305,12 @@ func ConvertOpenAIRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// tools -> request.tools[0].functionDeclarations + request.tools[0].googleSearch passthrough
|
// tools -> request.tools[].functionDeclarations + request.tools[].googleSearch passthrough
|
||||||
tools := gjson.GetBytes(rawJSON, "tools")
|
tools := gjson.GetBytes(rawJSON, "tools")
|
||||||
if tools.IsArray() && len(tools.Array()) > 0 {
|
if tools.IsArray() && len(tools.Array()) > 0 {
|
||||||
toolNode := []byte(`{}`)
|
functionToolNode := []byte(`{}`)
|
||||||
hasTool := false
|
|
||||||
hasFunction := false
|
hasFunction := false
|
||||||
|
googleSearchNodes := make([][]byte, 0)
|
||||||
for _, t := range tools.Array() {
|
for _, t := range tools.Array() {
|
||||||
if t.Get("type").String() == "function" {
|
if t.Get("type").String() == "function" {
|
||||||
fn := t.Get("function")
|
fn := t.Get("function")
|
||||||
@@ -349,31 +349,37 @@ func ConvertOpenAIRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
|||||||
}
|
}
|
||||||
fnRaw, _ = sjson.Delete(fnRaw, "strict")
|
fnRaw, _ = sjson.Delete(fnRaw, "strict")
|
||||||
if !hasFunction {
|
if !hasFunction {
|
||||||
toolNode, _ = sjson.SetRawBytes(toolNode, "functionDeclarations", []byte("[]"))
|
functionToolNode, _ = sjson.SetRawBytes(functionToolNode, "functionDeclarations", []byte("[]"))
|
||||||
}
|
}
|
||||||
tmp, errSet := sjson.SetRawBytes(toolNode, "functionDeclarations.-1", []byte(fnRaw))
|
tmp, errSet := sjson.SetRawBytes(functionToolNode, "functionDeclarations.-1", []byte(fnRaw))
|
||||||
if errSet != nil {
|
if errSet != nil {
|
||||||
log.Warnf("Failed to append tool declaration for '%s': %v", fn.Get("name").String(), errSet)
|
log.Warnf("Failed to append tool declaration for '%s': %v", fn.Get("name").String(), errSet)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
toolNode = tmp
|
functionToolNode = tmp
|
||||||
hasFunction = true
|
hasFunction = true
|
||||||
hasTool = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if gs := t.Get("google_search"); gs.Exists() {
|
if gs := t.Get("google_search"); gs.Exists() {
|
||||||
|
googleToolNode := []byte(`{}`)
|
||||||
var errSet error
|
var errSet error
|
||||||
toolNode, errSet = sjson.SetRawBytes(toolNode, "googleSearch", []byte(gs.Raw))
|
googleToolNode, errSet = sjson.SetRawBytes(googleToolNode, "googleSearch", []byte(gs.Raw))
|
||||||
if errSet != nil {
|
if errSet != nil {
|
||||||
log.Warnf("Failed to set googleSearch tool: %v", errSet)
|
log.Warnf("Failed to set googleSearch tool: %v", errSet)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
hasTool = true
|
googleSearchNodes = append(googleSearchNodes, googleToolNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if hasTool {
|
if hasFunction || len(googleSearchNodes) > 0 {
|
||||||
out, _ = sjson.SetRawBytes(out, "request.tools", []byte("[]"))
|
toolsNode := []byte("[]")
|
||||||
out, _ = sjson.SetRawBytes(out, "request.tools.0", toolNode)
|
if hasFunction {
|
||||||
|
toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", functionToolNode)
|
||||||
|
}
|
||||||
|
for _, googleNode := range googleSearchNodes {
|
||||||
|
toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", googleNode)
|
||||||
|
}
|
||||||
|
out, _ = sjson.SetRawBytes(out, "request.tools", toolsNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,9 +98,8 @@ func ConvertGeminiRequestToClaude(modelName string, inputRawJSON []byte, stream
|
|||||||
// Temperature setting for controlling response randomness
|
// Temperature setting for controlling response randomness
|
||||||
if temp := genConfig.Get("temperature"); temp.Exists() {
|
if temp := genConfig.Get("temperature"); temp.Exists() {
|
||||||
out, _ = sjson.Set(out, "temperature", temp.Float())
|
out, _ = sjson.Set(out, "temperature", temp.Float())
|
||||||
}
|
} else if topP := genConfig.Get("topP"); topP.Exists() {
|
||||||
// Top P setting for nucleus sampling
|
// Top P setting for nucleus sampling (filtered out if temperature is set)
|
||||||
if topP := genConfig.Get("topP"); topP.Exists() {
|
|
||||||
out, _ = sjson.Set(out, "top_p", topP.Float())
|
out, _ = sjson.Set(out, "top_p", topP.Float())
|
||||||
}
|
}
|
||||||
// Stop sequences configuration for custom termination conditions
|
// Stop sequences configuration for custom termination conditions
|
||||||
|
|||||||
@@ -110,10 +110,8 @@ func ConvertOpenAIRequestToClaude(modelName string, inputRawJSON []byte, stream
|
|||||||
// Temperature setting for controlling response randomness
|
// Temperature setting for controlling response randomness
|
||||||
if temp := root.Get("temperature"); temp.Exists() {
|
if temp := root.Get("temperature"); temp.Exists() {
|
||||||
out, _ = sjson.Set(out, "temperature", temp.Float())
|
out, _ = sjson.Set(out, "temperature", temp.Float())
|
||||||
}
|
} else if topP := root.Get("top_p"); topP.Exists() {
|
||||||
|
// Top P setting for nucleus sampling (filtered out if temperature is set)
|
||||||
// Top P setting for nucleus sampling
|
|
||||||
if topP := root.Get("top_p"); topP.Exists() {
|
|
||||||
out, _ = sjson.Set(out, "top_p", topP.Float())
|
out, _ = sjson.Set(out, "top_p", topP.Float())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -283,12 +283,12 @@ func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// tools -> request.tools[0].functionDeclarations + request.tools[0].googleSearch passthrough
|
// tools -> request.tools[].functionDeclarations + request.tools[].googleSearch passthrough
|
||||||
tools := gjson.GetBytes(rawJSON, "tools")
|
tools := gjson.GetBytes(rawJSON, "tools")
|
||||||
if tools.IsArray() && len(tools.Array()) > 0 {
|
if tools.IsArray() && len(tools.Array()) > 0 {
|
||||||
toolNode := []byte(`{}`)
|
functionToolNode := []byte(`{}`)
|
||||||
hasTool := false
|
|
||||||
hasFunction := false
|
hasFunction := false
|
||||||
|
googleSearchNodes := make([][]byte, 0)
|
||||||
for _, t := range tools.Array() {
|
for _, t := range tools.Array() {
|
||||||
if t.Get("type").String() == "function" {
|
if t.Get("type").String() == "function" {
|
||||||
fn := t.Get("function")
|
fn := t.Get("function")
|
||||||
@@ -327,31 +327,37 @@ func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bo
|
|||||||
}
|
}
|
||||||
fnRaw, _ = sjson.Delete(fnRaw, "strict")
|
fnRaw, _ = sjson.Delete(fnRaw, "strict")
|
||||||
if !hasFunction {
|
if !hasFunction {
|
||||||
toolNode, _ = sjson.SetRawBytes(toolNode, "functionDeclarations", []byte("[]"))
|
functionToolNode, _ = sjson.SetRawBytes(functionToolNode, "functionDeclarations", []byte("[]"))
|
||||||
}
|
}
|
||||||
tmp, errSet := sjson.SetRawBytes(toolNode, "functionDeclarations.-1", []byte(fnRaw))
|
tmp, errSet := sjson.SetRawBytes(functionToolNode, "functionDeclarations.-1", []byte(fnRaw))
|
||||||
if errSet != nil {
|
if errSet != nil {
|
||||||
log.Warnf("Failed to append tool declaration for '%s': %v", fn.Get("name").String(), errSet)
|
log.Warnf("Failed to append tool declaration for '%s': %v", fn.Get("name").String(), errSet)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
toolNode = tmp
|
functionToolNode = tmp
|
||||||
hasFunction = true
|
hasFunction = true
|
||||||
hasTool = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if gs := t.Get("google_search"); gs.Exists() {
|
if gs := t.Get("google_search"); gs.Exists() {
|
||||||
|
googleToolNode := []byte(`{}`)
|
||||||
var errSet error
|
var errSet error
|
||||||
toolNode, errSet = sjson.SetRawBytes(toolNode, "googleSearch", []byte(gs.Raw))
|
googleToolNode, errSet = sjson.SetRawBytes(googleToolNode, "googleSearch", []byte(gs.Raw))
|
||||||
if errSet != nil {
|
if errSet != nil {
|
||||||
log.Warnf("Failed to set googleSearch tool: %v", errSet)
|
log.Warnf("Failed to set googleSearch tool: %v", errSet)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
hasTool = true
|
googleSearchNodes = append(googleSearchNodes, googleToolNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if hasTool {
|
if hasFunction || len(googleSearchNodes) > 0 {
|
||||||
out, _ = sjson.SetRawBytes(out, "request.tools", []byte("[]"))
|
toolsNode := []byte("[]")
|
||||||
out, _ = sjson.SetRawBytes(out, "request.tools.0", toolNode)
|
if hasFunction {
|
||||||
|
toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", functionToolNode)
|
||||||
|
}
|
||||||
|
for _, googleNode := range googleSearchNodes {
|
||||||
|
toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", googleNode)
|
||||||
|
}
|
||||||
|
out, _ = sjson.SetRawBytes(out, "request.tools", toolsNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -289,12 +289,12 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// tools -> tools[0].functionDeclarations + tools[0].googleSearch passthrough
|
// tools -> tools[].functionDeclarations + tools[].googleSearch passthrough
|
||||||
tools := gjson.GetBytes(rawJSON, "tools")
|
tools := gjson.GetBytes(rawJSON, "tools")
|
||||||
if tools.IsArray() && len(tools.Array()) > 0 {
|
if tools.IsArray() && len(tools.Array()) > 0 {
|
||||||
toolNode := []byte(`{}`)
|
functionToolNode := []byte(`{}`)
|
||||||
hasTool := false
|
|
||||||
hasFunction := false
|
hasFunction := false
|
||||||
|
googleSearchNodes := make([][]byte, 0)
|
||||||
for _, t := range tools.Array() {
|
for _, t := range tools.Array() {
|
||||||
if t.Get("type").String() == "function" {
|
if t.Get("type").String() == "function" {
|
||||||
fn := t.Get("function")
|
fn := t.Get("function")
|
||||||
@@ -333,31 +333,37 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
|
|||||||
}
|
}
|
||||||
fnRaw, _ = sjson.Delete(fnRaw, "strict")
|
fnRaw, _ = sjson.Delete(fnRaw, "strict")
|
||||||
if !hasFunction {
|
if !hasFunction {
|
||||||
toolNode, _ = sjson.SetRawBytes(toolNode, "functionDeclarations", []byte("[]"))
|
functionToolNode, _ = sjson.SetRawBytes(functionToolNode, "functionDeclarations", []byte("[]"))
|
||||||
}
|
}
|
||||||
tmp, errSet := sjson.SetRawBytes(toolNode, "functionDeclarations.-1", []byte(fnRaw))
|
tmp, errSet := sjson.SetRawBytes(functionToolNode, "functionDeclarations.-1", []byte(fnRaw))
|
||||||
if errSet != nil {
|
if errSet != nil {
|
||||||
log.Warnf("Failed to append tool declaration for '%s': %v", fn.Get("name").String(), errSet)
|
log.Warnf("Failed to append tool declaration for '%s': %v", fn.Get("name").String(), errSet)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
toolNode = tmp
|
functionToolNode = tmp
|
||||||
hasFunction = true
|
hasFunction = true
|
||||||
hasTool = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if gs := t.Get("google_search"); gs.Exists() {
|
if gs := t.Get("google_search"); gs.Exists() {
|
||||||
|
googleToolNode := []byte(`{}`)
|
||||||
var errSet error
|
var errSet error
|
||||||
toolNode, errSet = sjson.SetRawBytes(toolNode, "googleSearch", []byte(gs.Raw))
|
googleToolNode, errSet = sjson.SetRawBytes(googleToolNode, "googleSearch", []byte(gs.Raw))
|
||||||
if errSet != nil {
|
if errSet != nil {
|
||||||
log.Warnf("Failed to set googleSearch tool: %v", errSet)
|
log.Warnf("Failed to set googleSearch tool: %v", errSet)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
hasTool = true
|
googleSearchNodes = append(googleSearchNodes, googleToolNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if hasTool {
|
if hasFunction || len(googleSearchNodes) > 0 {
|
||||||
out, _ = sjson.SetRawBytes(out, "tools", []byte("[]"))
|
toolsNode := []byte("[]")
|
||||||
out, _ = sjson.SetRawBytes(out, "tools.0", toolNode)
|
if hasFunction {
|
||||||
|
toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", functionToolNode)
|
||||||
|
}
|
||||||
|
for _, googleNode := range googleSearchNodes {
|
||||||
|
toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", googleNode)
|
||||||
|
}
|
||||||
|
out, _ = sjson.SetRawBytes(out, "tools", toolsNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,12 +89,14 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
|||||||
|
|
||||||
// Handle system message first
|
// Handle system message first
|
||||||
systemMsgJSON := `{"role":"system","content":[]}`
|
systemMsgJSON := `{"role":"system","content":[]}`
|
||||||
|
hasSystemContent := false
|
||||||
if system := root.Get("system"); system.Exists() {
|
if system := root.Get("system"); system.Exists() {
|
||||||
if system.Type == gjson.String {
|
if system.Type == gjson.String {
|
||||||
if system.String() != "" {
|
if system.String() != "" {
|
||||||
oldSystem := `{"type":"text","text":""}`
|
oldSystem := `{"type":"text","text":""}`
|
||||||
oldSystem, _ = sjson.Set(oldSystem, "text", system.String())
|
oldSystem, _ = sjson.Set(oldSystem, "text", system.String())
|
||||||
systemMsgJSON, _ = sjson.SetRaw(systemMsgJSON, "content.-1", oldSystem)
|
systemMsgJSON, _ = sjson.SetRaw(systemMsgJSON, "content.-1", oldSystem)
|
||||||
|
hasSystemContent = true
|
||||||
}
|
}
|
||||||
} else if system.Type == gjson.JSON {
|
} else if system.Type == gjson.JSON {
|
||||||
if system.IsArray() {
|
if system.IsArray() {
|
||||||
@@ -102,12 +104,16 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
|||||||
for i := 0; i < len(systemResults); i++ {
|
for i := 0; i < len(systemResults); i++ {
|
||||||
if contentItem, ok := convertClaudeContentPart(systemResults[i]); ok {
|
if contentItem, ok := convertClaudeContentPart(systemResults[i]); ok {
|
||||||
systemMsgJSON, _ = sjson.SetRaw(systemMsgJSON, "content.-1", contentItem)
|
systemMsgJSON, _ = sjson.SetRaw(systemMsgJSON, "content.-1", contentItem)
|
||||||
|
hasSystemContent = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
messagesJSON, _ = sjson.SetRaw(messagesJSON, "-1", systemMsgJSON)
|
// Only add system message if it has content
|
||||||
|
if hasSystemContent {
|
||||||
|
messagesJSON, _ = sjson.SetRaw(messagesJSON, "-1", systemMsgJSON)
|
||||||
|
}
|
||||||
|
|
||||||
// Process Anthropic messages
|
// Process Anthropic messages
|
||||||
if messages := root.Get("messages"); messages.Exists() && messages.IsArray() {
|
if messages := root.Get("messages"); messages.Exists() && messages.IsArray() {
|
||||||
|
|||||||
@@ -385,6 +385,7 @@ func (h *BaseAPIHandler) ExecuteWithAuthManager(ctx context.Context, handlerType
|
|||||||
return nil, errMsg
|
return nil, errMsg
|
||||||
}
|
}
|
||||||
reqMeta := requestExecutionMetadata(ctx)
|
reqMeta := requestExecutionMetadata(ctx)
|
||||||
|
reqMeta[coreexecutor.RequestedModelMetadataKey] = normalizedModel
|
||||||
req := coreexecutor.Request{
|
req := coreexecutor.Request{
|
||||||
Model: normalizedModel,
|
Model: normalizedModel,
|
||||||
Payload: cloneBytes(rawJSON),
|
Payload: cloneBytes(rawJSON),
|
||||||
@@ -423,6 +424,7 @@ func (h *BaseAPIHandler) ExecuteCountWithAuthManager(ctx context.Context, handle
|
|||||||
return nil, errMsg
|
return nil, errMsg
|
||||||
}
|
}
|
||||||
reqMeta := requestExecutionMetadata(ctx)
|
reqMeta := requestExecutionMetadata(ctx)
|
||||||
|
reqMeta[coreexecutor.RequestedModelMetadataKey] = normalizedModel
|
||||||
req := coreexecutor.Request{
|
req := coreexecutor.Request{
|
||||||
Model: normalizedModel,
|
Model: normalizedModel,
|
||||||
Payload: cloneBytes(rawJSON),
|
Payload: cloneBytes(rawJSON),
|
||||||
@@ -464,6 +466,7 @@ func (h *BaseAPIHandler) ExecuteStreamWithAuthManager(ctx context.Context, handl
|
|||||||
return nil, errChan
|
return nil, errChan
|
||||||
}
|
}
|
||||||
reqMeta := requestExecutionMetadata(ctx)
|
reqMeta := requestExecutionMetadata(ctx)
|
||||||
|
reqMeta[coreexecutor.RequestedModelMetadataKey] = normalizedModel
|
||||||
req := coreexecutor.Request{
|
req := coreexecutor.Request{
|
||||||
Model: normalizedModel,
|
Model: normalizedModel,
|
||||||
Payload: cloneBytes(rawJSON),
|
Payload: cloneBytes(rawJSON),
|
||||||
|
|||||||
@@ -570,6 +570,7 @@ func (m *Manager) executeMixedOnce(ctx context.Context, providers []string, req
|
|||||||
return cliproxyexecutor.Response{}, &Error{Code: "provider_not_found", Message: "no provider supplied"}
|
return cliproxyexecutor.Response{}, &Error{Code: "provider_not_found", Message: "no provider supplied"}
|
||||||
}
|
}
|
||||||
routeModel := req.Model
|
routeModel := req.Model
|
||||||
|
opts = ensureRequestedModelMetadata(opts, routeModel)
|
||||||
tried := make(map[string]struct{})
|
tried := make(map[string]struct{})
|
||||||
var lastErr error
|
var lastErr error
|
||||||
for {
|
for {
|
||||||
@@ -597,6 +598,9 @@ func (m *Manager) executeMixedOnce(ctx context.Context, providers []string, req
|
|||||||
resp, errExec := executor.Execute(execCtx, auth, execReq, opts)
|
resp, errExec := executor.Execute(execCtx, auth, execReq, opts)
|
||||||
result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil}
|
result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil}
|
||||||
if errExec != nil {
|
if errExec != nil {
|
||||||
|
if errCtx := execCtx.Err(); errCtx != nil {
|
||||||
|
return cliproxyexecutor.Response{}, errCtx
|
||||||
|
}
|
||||||
result.Error = &Error{Message: errExec.Error()}
|
result.Error = &Error{Message: errExec.Error()}
|
||||||
var se cliproxyexecutor.StatusError
|
var se cliproxyexecutor.StatusError
|
||||||
if errors.As(errExec, &se) && se != nil {
|
if errors.As(errExec, &se) && se != nil {
|
||||||
@@ -619,6 +623,7 @@ func (m *Manager) executeCountMixedOnce(ctx context.Context, providers []string,
|
|||||||
return cliproxyexecutor.Response{}, &Error{Code: "provider_not_found", Message: "no provider supplied"}
|
return cliproxyexecutor.Response{}, &Error{Code: "provider_not_found", Message: "no provider supplied"}
|
||||||
}
|
}
|
||||||
routeModel := req.Model
|
routeModel := req.Model
|
||||||
|
opts = ensureRequestedModelMetadata(opts, routeModel)
|
||||||
tried := make(map[string]struct{})
|
tried := make(map[string]struct{})
|
||||||
var lastErr error
|
var lastErr error
|
||||||
for {
|
for {
|
||||||
@@ -646,6 +651,9 @@ func (m *Manager) executeCountMixedOnce(ctx context.Context, providers []string,
|
|||||||
resp, errExec := executor.CountTokens(execCtx, auth, execReq, opts)
|
resp, errExec := executor.CountTokens(execCtx, auth, execReq, opts)
|
||||||
result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil}
|
result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil}
|
||||||
if errExec != nil {
|
if errExec != nil {
|
||||||
|
if errCtx := execCtx.Err(); errCtx != nil {
|
||||||
|
return cliproxyexecutor.Response{}, errCtx
|
||||||
|
}
|
||||||
result.Error = &Error{Message: errExec.Error()}
|
result.Error = &Error{Message: errExec.Error()}
|
||||||
var se cliproxyexecutor.StatusError
|
var se cliproxyexecutor.StatusError
|
||||||
if errors.As(errExec, &se) && se != nil {
|
if errors.As(errExec, &se) && se != nil {
|
||||||
@@ -668,6 +676,7 @@ func (m *Manager) executeStreamMixedOnce(ctx context.Context, providers []string
|
|||||||
return nil, &Error{Code: "provider_not_found", Message: "no provider supplied"}
|
return nil, &Error{Code: "provider_not_found", Message: "no provider supplied"}
|
||||||
}
|
}
|
||||||
routeModel := req.Model
|
routeModel := req.Model
|
||||||
|
opts = ensureRequestedModelMetadata(opts, routeModel)
|
||||||
tried := make(map[string]struct{})
|
tried := make(map[string]struct{})
|
||||||
var lastErr error
|
var lastErr error
|
||||||
for {
|
for {
|
||||||
@@ -694,6 +703,9 @@ func (m *Manager) executeStreamMixedOnce(ctx context.Context, providers []string
|
|||||||
execReq.Model = m.applyAPIKeyModelAlias(auth, execReq.Model)
|
execReq.Model = m.applyAPIKeyModelAlias(auth, execReq.Model)
|
||||||
chunks, errStream := executor.ExecuteStream(execCtx, auth, execReq, opts)
|
chunks, errStream := executor.ExecuteStream(execCtx, auth, execReq, opts)
|
||||||
if errStream != nil {
|
if errStream != nil {
|
||||||
|
if errCtx := execCtx.Err(); errCtx != nil {
|
||||||
|
return nil, errCtx
|
||||||
|
}
|
||||||
rerr := &Error{Message: errStream.Error()}
|
rerr := &Error{Message: errStream.Error()}
|
||||||
var se cliproxyexecutor.StatusError
|
var se cliproxyexecutor.StatusError
|
||||||
if errors.As(errStream, &se) && se != nil {
|
if errors.As(errStream, &se) && se != nil {
|
||||||
@@ -729,167 +741,42 @@ func (m *Manager) executeStreamMixedOnce(ctx context.Context, providers []string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) executeWithProvider(ctx context.Context, provider string, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
func ensureRequestedModelMetadata(opts cliproxyexecutor.Options, requestedModel string) cliproxyexecutor.Options {
|
||||||
if provider == "" {
|
requestedModel = strings.TrimSpace(requestedModel)
|
||||||
return cliproxyexecutor.Response{}, &Error{Code: "provider_not_found", Message: "provider identifier is empty"}
|
if requestedModel == "" {
|
||||||
|
return opts
|
||||||
}
|
}
|
||||||
routeModel := req.Model
|
if hasRequestedModelMetadata(opts.Metadata) {
|
||||||
tried := make(map[string]struct{})
|
return opts
|
||||||
var lastErr error
|
|
||||||
for {
|
|
||||||
auth, executor, errPick := m.pickNext(ctx, provider, routeModel, opts, tried)
|
|
||||||
if errPick != nil {
|
|
||||||
if lastErr != nil {
|
|
||||||
return cliproxyexecutor.Response{}, lastErr
|
|
||||||
}
|
|
||||||
return cliproxyexecutor.Response{}, errPick
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := logEntryWithRequestID(ctx)
|
|
||||||
debugLogAuthSelection(entry, auth, provider, req.Model)
|
|
||||||
|
|
||||||
tried[auth.ID] = struct{}{}
|
|
||||||
execCtx := ctx
|
|
||||||
if rt := m.roundTripperFor(auth); rt != nil {
|
|
||||||
execCtx = context.WithValue(execCtx, roundTripperContextKey{}, rt)
|
|
||||||
execCtx = context.WithValue(execCtx, "cliproxy.roundtripper", rt)
|
|
||||||
}
|
|
||||||
execReq := req
|
|
||||||
execReq.Model = rewriteModelForAuth(routeModel, auth)
|
|
||||||
execReq.Model = m.applyOAuthModelAlias(auth, execReq.Model)
|
|
||||||
execReq.Model = m.applyAPIKeyModelAlias(auth, execReq.Model)
|
|
||||||
resp, errExec := executor.Execute(execCtx, auth, execReq, opts)
|
|
||||||
result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil}
|
|
||||||
if errExec != nil {
|
|
||||||
result.Error = &Error{Message: errExec.Error()}
|
|
||||||
var se cliproxyexecutor.StatusError
|
|
||||||
if errors.As(errExec, &se) && se != nil {
|
|
||||||
result.Error.HTTPStatus = se.StatusCode()
|
|
||||||
}
|
|
||||||
if ra := retryAfterFromError(errExec); ra != nil {
|
|
||||||
result.RetryAfter = ra
|
|
||||||
}
|
|
||||||
m.MarkResult(execCtx, result)
|
|
||||||
lastErr = errExec
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
m.MarkResult(execCtx, result)
|
|
||||||
return resp, nil
|
|
||||||
}
|
}
|
||||||
|
if len(opts.Metadata) == 0 {
|
||||||
|
opts.Metadata = map[string]any{cliproxyexecutor.RequestedModelMetadataKey: requestedModel}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
meta := make(map[string]any, len(opts.Metadata)+1)
|
||||||
|
for k, v := range opts.Metadata {
|
||||||
|
meta[k] = v
|
||||||
|
}
|
||||||
|
meta[cliproxyexecutor.RequestedModelMetadataKey] = requestedModel
|
||||||
|
opts.Metadata = meta
|
||||||
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) executeCountWithProvider(ctx context.Context, provider string, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
func hasRequestedModelMetadata(meta map[string]any) bool {
|
||||||
if provider == "" {
|
if len(meta) == 0 {
|
||||||
return cliproxyexecutor.Response{}, &Error{Code: "provider_not_found", Message: "provider identifier is empty"}
|
return false
|
||||||
}
|
}
|
||||||
routeModel := req.Model
|
raw, ok := meta[cliproxyexecutor.RequestedModelMetadataKey]
|
||||||
tried := make(map[string]struct{})
|
if !ok || raw == nil {
|
||||||
var lastErr error
|
return false
|
||||||
for {
|
|
||||||
auth, executor, errPick := m.pickNext(ctx, provider, routeModel, opts, tried)
|
|
||||||
if errPick != nil {
|
|
||||||
if lastErr != nil {
|
|
||||||
return cliproxyexecutor.Response{}, lastErr
|
|
||||||
}
|
|
||||||
return cliproxyexecutor.Response{}, errPick
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := logEntryWithRequestID(ctx)
|
|
||||||
debugLogAuthSelection(entry, auth, provider, req.Model)
|
|
||||||
|
|
||||||
tried[auth.ID] = struct{}{}
|
|
||||||
execCtx := ctx
|
|
||||||
if rt := m.roundTripperFor(auth); rt != nil {
|
|
||||||
execCtx = context.WithValue(execCtx, roundTripperContextKey{}, rt)
|
|
||||||
execCtx = context.WithValue(execCtx, "cliproxy.roundtripper", rt)
|
|
||||||
}
|
|
||||||
execReq := req
|
|
||||||
execReq.Model = rewriteModelForAuth(routeModel, auth)
|
|
||||||
execReq.Model = m.applyOAuthModelAlias(auth, execReq.Model)
|
|
||||||
execReq.Model = m.applyAPIKeyModelAlias(auth, execReq.Model)
|
|
||||||
resp, errExec := executor.CountTokens(execCtx, auth, execReq, opts)
|
|
||||||
result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil}
|
|
||||||
if errExec != nil {
|
|
||||||
result.Error = &Error{Message: errExec.Error()}
|
|
||||||
var se cliproxyexecutor.StatusError
|
|
||||||
if errors.As(errExec, &se) && se != nil {
|
|
||||||
result.Error.HTTPStatus = se.StatusCode()
|
|
||||||
}
|
|
||||||
if ra := retryAfterFromError(errExec); ra != nil {
|
|
||||||
result.RetryAfter = ra
|
|
||||||
}
|
|
||||||
m.MarkResult(execCtx, result)
|
|
||||||
lastErr = errExec
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
m.MarkResult(execCtx, result)
|
|
||||||
return resp, nil
|
|
||||||
}
|
}
|
||||||
}
|
switch v := raw.(type) {
|
||||||
|
case string:
|
||||||
func (m *Manager) executeStreamWithProvider(ctx context.Context, provider string, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (<-chan cliproxyexecutor.StreamChunk, error) {
|
return strings.TrimSpace(v) != ""
|
||||||
if provider == "" {
|
case []byte:
|
||||||
return nil, &Error{Code: "provider_not_found", Message: "provider identifier is empty"}
|
return strings.TrimSpace(string(v)) != ""
|
||||||
}
|
default:
|
||||||
routeModel := req.Model
|
return false
|
||||||
tried := make(map[string]struct{})
|
|
||||||
var lastErr error
|
|
||||||
for {
|
|
||||||
auth, executor, errPick := m.pickNext(ctx, provider, routeModel, opts, tried)
|
|
||||||
if errPick != nil {
|
|
||||||
if lastErr != nil {
|
|
||||||
return nil, lastErr
|
|
||||||
}
|
|
||||||
return nil, errPick
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := logEntryWithRequestID(ctx)
|
|
||||||
debugLogAuthSelection(entry, auth, provider, req.Model)
|
|
||||||
|
|
||||||
tried[auth.ID] = struct{}{}
|
|
||||||
execCtx := ctx
|
|
||||||
if rt := m.roundTripperFor(auth); rt != nil {
|
|
||||||
execCtx = context.WithValue(execCtx, roundTripperContextKey{}, rt)
|
|
||||||
execCtx = context.WithValue(execCtx, "cliproxy.roundtripper", rt)
|
|
||||||
}
|
|
||||||
execReq := req
|
|
||||||
execReq.Model = rewriteModelForAuth(routeModel, auth)
|
|
||||||
execReq.Model = m.applyOAuthModelAlias(auth, execReq.Model)
|
|
||||||
execReq.Model = m.applyAPIKeyModelAlias(auth, execReq.Model)
|
|
||||||
chunks, errStream := executor.ExecuteStream(execCtx, auth, execReq, opts)
|
|
||||||
if errStream != nil {
|
|
||||||
rerr := &Error{Message: errStream.Error()}
|
|
||||||
var se cliproxyexecutor.StatusError
|
|
||||||
if errors.As(errStream, &se) && se != nil {
|
|
||||||
rerr.HTTPStatus = se.StatusCode()
|
|
||||||
}
|
|
||||||
result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: false, Error: rerr}
|
|
||||||
result.RetryAfter = retryAfterFromError(errStream)
|
|
||||||
m.MarkResult(execCtx, result)
|
|
||||||
lastErr = errStream
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out := make(chan cliproxyexecutor.StreamChunk)
|
|
||||||
go func(streamCtx context.Context, streamAuth *Auth, streamProvider string, streamChunks <-chan cliproxyexecutor.StreamChunk) {
|
|
||||||
defer close(out)
|
|
||||||
var failed bool
|
|
||||||
for chunk := range streamChunks {
|
|
||||||
if chunk.Err != nil && !failed {
|
|
||||||
failed = true
|
|
||||||
rerr := &Error{Message: chunk.Err.Error()}
|
|
||||||
var se cliproxyexecutor.StatusError
|
|
||||||
if errors.As(chunk.Err, &se) && se != nil {
|
|
||||||
rerr.HTTPStatus = se.StatusCode()
|
|
||||||
}
|
|
||||||
m.MarkResult(streamCtx, Result{AuthID: streamAuth.ID, Provider: streamProvider, Model: routeModel, Success: false, Error: rerr})
|
|
||||||
}
|
|
||||||
out <- chunk
|
|
||||||
}
|
|
||||||
if !failed {
|
|
||||||
m.MarkResult(streamCtx, Result{AuthID: streamAuth.ID, Provider: streamProvider, Model: routeModel, Success: true})
|
|
||||||
}
|
|
||||||
}(execCtx, auth.Clone(), provider, chunks)
|
|
||||||
return out, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1140,35 +1027,6 @@ func (m *Manager) normalizeProviders(providers []string) []string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// rotateProviders returns a rotated view of the providers list starting from the
|
|
||||||
// current offset for the model, and atomically increments the offset for the next call.
|
|
||||||
// This ensures concurrent requests get different starting providers.
|
|
||||||
func (m *Manager) rotateProviders(model string, providers []string) []string {
|
|
||||||
if len(providers) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Atomic read-and-increment: get current offset and advance cursor in one lock
|
|
||||||
m.mu.Lock()
|
|
||||||
offset := m.providerOffsets[model]
|
|
||||||
m.providerOffsets[model] = (offset + 1) % len(providers)
|
|
||||||
m.mu.Unlock()
|
|
||||||
|
|
||||||
if len(providers) > 0 {
|
|
||||||
offset %= len(providers)
|
|
||||||
}
|
|
||||||
if offset < 0 {
|
|
||||||
offset = 0
|
|
||||||
}
|
|
||||||
if offset == 0 {
|
|
||||||
return providers
|
|
||||||
}
|
|
||||||
rotated := make([]string, 0, len(providers))
|
|
||||||
rotated = append(rotated, providers[offset:]...)
|
|
||||||
rotated = append(rotated, providers[:offset]...)
|
|
||||||
return rotated
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) retrySettings() (int, time.Duration) {
|
func (m *Manager) retrySettings() (int, time.Duration) {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return 0, 0
|
return 0, 0
|
||||||
@@ -1250,42 +1108,6 @@ func waitForCooldown(ctx context.Context, wait time.Duration) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) executeProvidersOnce(ctx context.Context, providers []string, fn func(context.Context, string) (cliproxyexecutor.Response, error)) (cliproxyexecutor.Response, error) {
|
|
||||||
if len(providers) == 0 {
|
|
||||||
return cliproxyexecutor.Response{}, &Error{Code: "provider_not_found", Message: "no provider supplied"}
|
|
||||||
}
|
|
||||||
var lastErr error
|
|
||||||
for _, provider := range providers {
|
|
||||||
resp, errExec := fn(ctx, provider)
|
|
||||||
if errExec == nil {
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
lastErr = errExec
|
|
||||||
}
|
|
||||||
if lastErr != nil {
|
|
||||||
return cliproxyexecutor.Response{}, lastErr
|
|
||||||
}
|
|
||||||
return cliproxyexecutor.Response{}, &Error{Code: "auth_not_found", Message: "no auth available"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) executeStreamProvidersOnce(ctx context.Context, providers []string, fn func(context.Context, string) (<-chan cliproxyexecutor.StreamChunk, error)) (<-chan cliproxyexecutor.StreamChunk, error) {
|
|
||||||
if len(providers) == 0 {
|
|
||||||
return nil, &Error{Code: "provider_not_found", Message: "no provider supplied"}
|
|
||||||
}
|
|
||||||
var lastErr error
|
|
||||||
for _, provider := range providers {
|
|
||||||
chunks, errExec := fn(ctx, provider)
|
|
||||||
if errExec == nil {
|
|
||||||
return chunks, nil
|
|
||||||
}
|
|
||||||
lastErr = errExec
|
|
||||||
}
|
|
||||||
if lastErr != nil {
|
|
||||||
return nil, lastErr
|
|
||||||
}
|
|
||||||
return nil, &Error{Code: "auth_not_found", Message: "no auth available"}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkResult records an execution result and notifies hooks.
|
// MarkResult records an execution result and notifies hooks.
|
||||||
func (m *Manager) MarkResult(ctx context.Context, result Result) {
|
func (m *Manager) MarkResult(ctx context.Context, result Result) {
|
||||||
if result.AuthID == "" {
|
if result.AuthID == "" {
|
||||||
@@ -1371,8 +1193,12 @@ func (m *Manager) MarkResult(ctx context.Context, result Result) {
|
|||||||
shouldSuspendModel = true
|
shouldSuspendModel = true
|
||||||
setModelQuota = true
|
setModelQuota = true
|
||||||
case 408, 500, 502, 503, 504:
|
case 408, 500, 502, 503, 504:
|
||||||
next := now.Add(1 * time.Minute)
|
if quotaCooldownDisabled.Load() {
|
||||||
state.NextRetryAfter = next
|
state.NextRetryAfter = time.Time{}
|
||||||
|
} else {
|
||||||
|
next := now.Add(1 * time.Minute)
|
||||||
|
state.NextRetryAfter = next
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
state.NextRetryAfter = time.Time{}
|
state.NextRetryAfter = time.Time{}
|
||||||
}
|
}
|
||||||
@@ -1623,7 +1449,11 @@ func applyAuthFailureState(auth *Auth, resultErr *Error, retryAfter *time.Durati
|
|||||||
auth.NextRetryAfter = next
|
auth.NextRetryAfter = next
|
||||||
case 408, 500, 502, 503, 504:
|
case 408, 500, 502, 503, 504:
|
||||||
auth.StatusMessage = "transient upstream error"
|
auth.StatusMessage = "transient upstream error"
|
||||||
auth.NextRetryAfter = now.Add(1 * time.Minute)
|
if quotaCooldownDisabled.Load() {
|
||||||
|
auth.NextRetryAfter = time.Time{}
|
||||||
|
} else {
|
||||||
|
auth.NextRetryAfter = now.Add(1 * time.Minute)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
if auth.StatusMessage == "" {
|
if auth.StatusMessage == "" {
|
||||||
auth.StatusMessage = "request failed"
|
auth.StatusMessage = "request failed"
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import (
|
|||||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RequestedModelMetadataKey stores the client-requested model name in Options.Metadata.
|
||||||
|
const RequestedModelMetadataKey = "requested_model"
|
||||||
|
|
||||||
// Request encapsulates the translated payload that will be sent to a provider executor.
|
// Request encapsulates the translated payload that will be sent to a provider executor.
|
||||||
type Request struct {
|
type Request struct {
|
||||||
// Model is the upstream model identifier after translation.
|
// Model is the upstream model identifier after translation.
|
||||||
|
|||||||
Reference in New Issue
Block a user