Skip to content

Commit ecc850b

Browse files
committed
feat(executor): apply payload rules using requested model
1 parent 19b4ef3 commit ecc850b

File tree

13 files changed

+109
-71
lines changed

13 files changed

+109
-71
lines changed

internal/runtime/executor/aistudio_executor.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,8 @@ func (e *AIStudioExecutor) translateRequest(req cliproxyexecutor.Request, opts c
398398
return nil, translatedPayload{}, err
399399
}
400400
payload = fixGeminiImageAspectRatio(baseModel, payload)
401-
payload = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", payload, originalTranslated)
401+
requestedModel := payloadRequestedModel(opts, req.Model)
402+
payload = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", payload, originalTranslated, requestedModel)
402403
payload, _ = sjson.DeleteBytes(payload, "generationConfig.maxOutputTokens")
403404
payload, _ = sjson.DeleteBytes(payload, "generationConfig.responseMimeType")
404405
payload, _ = sjson.DeleteBytes(payload, "generationConfig.responseJsonSchema")

internal/runtime/executor/antigravity_executor.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ func (e *AntigravityExecutor) Execute(ctx context.Context, auth *cliproxyauth.Au
142142
return resp, err
143143
}
144144

145-
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, "antigravity", "request", translated, originalTranslated)
145+
requestedModel := payloadRequestedModel(opts, req.Model)
146+
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, "antigravity", "request", translated, originalTranslated, requestedModel)
146147

147148
baseURLs := antigravityBaseURLFallbackOrder(auth)
148149
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
@@ -261,7 +262,8 @@ func (e *AntigravityExecutor) executeClaudeNonStream(ctx context.Context, auth *
261262
return resp, err
262263
}
263264

264-
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, "antigravity", "request", translated, originalTranslated)
265+
requestedModel := payloadRequestedModel(opts, req.Model)
266+
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, "antigravity", "request", translated, originalTranslated, requestedModel)
265267

266268
baseURLs := antigravityBaseURLFallbackOrder(auth)
267269
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
@@ -627,7 +629,8 @@ func (e *AntigravityExecutor) ExecuteStream(ctx context.Context, auth *cliproxya
627629
return nil, err
628630
}
629631

630-
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, "antigravity", "request", translated, originalTranslated)
632+
requestedModel := payloadRequestedModel(opts, req.Model)
633+
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, "antigravity", "request", translated, originalTranslated, requestedModel)
631634

632635
baseURLs := antigravityBaseURLFallbackOrder(auth)
633636
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)

internal/runtime/executor/claude_executor.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
114114
// based on client type and configuration.
115115
body = applyCloaking(ctx, e.cfg, auth, body, baseModel)
116116

117-
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
117+
requestedModel := payloadRequestedModel(opts, req.Model)
118+
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
118119

119120
// Disable thinking if tool_choice forces tool use (Anthropic API constraint)
120121
body = disableThinkingIfToolChoiceForced(body)
@@ -245,7 +246,8 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
245246
// based on client type and configuration.
246247
body = applyCloaking(ctx, e.cfg, auth, body, baseModel)
247248

248-
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
249+
requestedModel := payloadRequestedModel(opts, req.Model)
250+
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
249251

250252
// Disable thinking if tool_choice forces tool use (Anthropic API constraint)
251253
body = disableThinkingIfToolChoiceForced(body)

internal/runtime/executor/codex_executor.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
101101
return resp, err
102102
}
103103

104-
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
104+
requestedModel := payloadRequestedModel(opts, req.Model)
105+
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
105106
body, _ = sjson.SetBytes(body, "model", baseModel)
106107
body, _ = sjson.SetBytes(body, "stream", true)
107108
body, _ = sjson.DeleteBytes(body, "previous_response_id")
@@ -213,7 +214,8 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
213214
return nil, err
214215
}
215216

216-
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
217+
requestedModel := payloadRequestedModel(opts, req.Model)
218+
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
217219
body, _ = sjson.DeleteBytes(body, "previous_response_id")
218220
body, _ = sjson.DeleteBytes(body, "prompt_cache_retention")
219221
body, _ = sjson.DeleteBytes(body, "safety_identifier")

internal/runtime/executor/gemini_cli_executor.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ func (e *GeminiCLIExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth
129129
}
130130

131131
basePayload = fixGeminiCLIImageAspectRatio(baseModel, basePayload)
132-
basePayload = applyPayloadConfigWithRoot(e.cfg, baseModel, "gemini", "request", basePayload, originalTranslated)
132+
requestedModel := payloadRequestedModel(opts, req.Model)
133+
basePayload = applyPayloadConfigWithRoot(e.cfg, baseModel, "gemini", "request", basePayload, originalTranslated, requestedModel)
133134

134135
action := "generateContent"
135136
if req.Metadata != nil {
@@ -278,7 +279,8 @@ func (e *GeminiCLIExecutor) ExecuteStream(ctx context.Context, auth *cliproxyaut
278279
}
279280

280281
basePayload = fixGeminiCLIImageAspectRatio(baseModel, basePayload)
281-
basePayload = applyPayloadConfigWithRoot(e.cfg, baseModel, "gemini", "request", basePayload, originalTranslated)
282+
requestedModel := payloadRequestedModel(opts, req.Model)
283+
basePayload = applyPayloadConfigWithRoot(e.cfg, baseModel, "gemini", "request", basePayload, originalTranslated, requestedModel)
282284

283285
projectID := resolveGeminiProjectID(auth)
284286

internal/runtime/executor/gemini_executor.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ func (e *GeminiExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
126126
}
127127

128128
body = fixGeminiImageAspectRatio(baseModel, body)
129-
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
129+
requestedModel := payloadRequestedModel(opts, req.Model)
130+
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
130131
body, _ = sjson.SetBytes(body, "model", baseModel)
131132

132133
action := "generateContent"
@@ -228,7 +229,8 @@ func (e *GeminiExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
228229
}
229230

230231
body = fixGeminiImageAspectRatio(baseModel, body)
231-
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
232+
requestedModel := payloadRequestedModel(opts, req.Model)
233+
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
232234
body, _ = sjson.SetBytes(body, "model", baseModel)
233235

234236
baseURL := resolveGeminiBaseURL(auth)

internal/runtime/executor/gemini_vertex_executor.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,8 @@ func (e *GeminiVertexExecutor) executeWithServiceAccount(ctx context.Context, au
325325
}
326326

327327
body = fixGeminiImageAspectRatio(baseModel, body)
328-
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
328+
requestedModel := payloadRequestedModel(opts, req.Model)
329+
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
329330
body, _ = sjson.SetBytes(body, "model", baseModel)
330331
}
331332

@@ -438,7 +439,8 @@ func (e *GeminiVertexExecutor) executeWithAPIKey(ctx context.Context, auth *clip
438439
}
439440

440441
body = fixGeminiImageAspectRatio(baseModel, body)
441-
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
442+
requestedModel := payloadRequestedModel(opts, req.Model)
443+
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
442444
body, _ = sjson.SetBytes(body, "model", baseModel)
443445

444446
action := getVertexAction(baseModel, false)
@@ -541,7 +543,8 @@ func (e *GeminiVertexExecutor) executeStreamWithServiceAccount(ctx context.Conte
541543
}
542544

543545
body = fixGeminiImageAspectRatio(baseModel, body)
544-
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
546+
requestedModel := payloadRequestedModel(opts, req.Model)
547+
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
545548
body, _ = sjson.SetBytes(body, "model", baseModel)
546549

547550
action := getVertexAction(baseModel, true)
@@ -664,7 +667,8 @@ func (e *GeminiVertexExecutor) executeStreamWithAPIKey(ctx context.Context, auth
664667
}
665668

666669
body = fixGeminiImageAspectRatio(baseModel, body)
667-
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
670+
requestedModel := payloadRequestedModel(opts, req.Model)
671+
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
668672
body, _ = sjson.SetBytes(body, "model", baseModel)
669673

670674
action := getVertexAction(baseModel, true)

internal/runtime/executor/iflow_executor.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ func (e *IFlowExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
9898
}
9999

100100
body = preserveReasoningContentInMessages(body)
101-
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
101+
requestedModel := payloadRequestedModel(opts, req.Model)
102+
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
102103

103104
endpoint := strings.TrimSuffix(baseURL, "/") + iflowDefaultEndpoint
104105

@@ -201,7 +202,8 @@ func (e *IFlowExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
201202
if toolsResult.Exists() && toolsResult.IsArray() && len(toolsResult.Array()) == 0 {
202203
body = ensureToolsArray(body)
203204
}
204-
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated)
205+
requestedModel := payloadRequestedModel(opts, req.Model)
206+
body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel)
205207

206208
endpoint := strings.TrimSuffix(baseURL, "/") + iflowDefaultEndpoint
207209

internal/runtime/executor/openai_compat_executor.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ func (e *OpenAICompatExecutor) Execute(ctx context.Context, auth *cliproxyauth.A
9090
}
9191
originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, opts.Stream)
9292
translated := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), opts.Stream)
93-
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", translated, originalTranslated)
93+
requestedModel := payloadRequestedModel(opts, req.Model)
94+
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", translated, originalTranslated, requestedModel)
9495

9596
translated, err = thinking.ApplyThinking(translated, req.Model, from.String(), to.String(), e.Identifier())
9697
if err != nil {
@@ -185,7 +186,8 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy
185186
}
186187
originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, true)
187188
translated := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), true)
188-
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", translated, originalTranslated)
189+
requestedModel := payloadRequestedModel(opts, req.Model)
190+
translated = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", translated, originalTranslated, requestedModel)
189191

190192
translated, err = thinking.ApplyThinking(translated, req.Model, from.String(), to.String(), e.Identifier())
191193
if err != nil {

internal/runtime/executor/payload_helpers.go

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@ import (
55
"strings"
66

77
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
8+
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
9+
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
810
"github.com/tidwall/gjson"
911
"github.com/tidwall/sjson"
1012
)
1113

1214
// applyPayloadConfigWithRoot behaves like applyPayloadConfig but treats all parameter
1315
// paths as relative to the provided root path (for example, "request" for Gemini CLI)
1416
// and restricts matches to the given protocol when supplied. Defaults are checked
15-
// against the original payload when provided.
16-
func applyPayloadConfigWithRoot(cfg *config.Config, model, protocol, root string, payload, original []byte) []byte {
17+
// against the original payload when provided. requestedModel carries the client-visible
18+
// model name before alias resolution so payload rules can target aliases precisely.
19+
func applyPayloadConfigWithRoot(cfg *config.Config, model, protocol, root string, payload, original []byte, requestedModel string) []byte {
1720
if cfg == nil || len(payload) == 0 {
1821
return payload
1922
}
@@ -22,10 +25,11 @@ func applyPayloadConfigWithRoot(cfg *config.Config, model, protocol, root string
2225
return payload
2326
}
2427
model = strings.TrimSpace(model)
25-
if model == "" {
28+
requestedModel = strings.TrimSpace(requestedModel)
29+
if model == "" && requestedModel == "" {
2630
return payload
2731
}
28-
candidates := payloadModelCandidates(cfg, model, protocol)
32+
candidates := payloadModelCandidates(model, requestedModel)
2933
out := payload
3034
source := original
3135
if len(source) == 0 {
@@ -163,63 +167,40 @@ func payloadRuleMatchesModel(rule *config.PayloadRule, model, protocol string) b
163167
return false
164168
}
165169

166-
func payloadModelCandidates(cfg *config.Config, model, protocol string) []string {
170+
func payloadModelCandidates(model, requestedModel string) []string {
167171
model = strings.TrimSpace(model)
168-
if model == "" {
172+
requestedModel = strings.TrimSpace(requestedModel)
173+
if model == "" && requestedModel == "" {
169174
return nil
170175
}
171-
candidates := []string{model}
172-
if cfg == nil {
173-
return candidates
174-
}
175-
aliases := payloadModelAliases(cfg, model, protocol)
176-
if len(aliases) == 0 {
177-
return candidates
178-
}
179-
seen := map[string]struct{}{strings.ToLower(model): struct{}{}}
180-
for _, alias := range aliases {
181-
alias = strings.TrimSpace(alias)
182-
if alias == "" {
183-
continue
176+
candidates := make([]string, 0, 3)
177+
seen := make(map[string]struct{}, 3)
178+
addCandidate := func(value string) {
179+
value = strings.TrimSpace(value)
180+
if value == "" {
181+
return
184182
}
185-
key := strings.ToLower(alias)
183+
key := strings.ToLower(value)
186184
if _, ok := seen[key]; ok {
187-
continue
185+
return
188186
}
189187
seen[key] = struct{}{}
190-
candidates = append(candidates, alias)
188+
candidates = append(candidates, value)
191189
}
192-
return candidates
193-
}
194-
195-
func payloadModelAliases(cfg *config.Config, model, protocol string) []string {
196-
if cfg == nil {
197-
return nil
198-
}
199-
model = strings.TrimSpace(model)
200-
if model == "" {
201-
return nil
202-
}
203-
channel := strings.ToLower(strings.TrimSpace(protocol))
204-
if channel == "" {
205-
return nil
206-
}
207-
entries := cfg.OAuthModelAlias[channel]
208-
if len(entries) == 0 {
209-
return nil
190+
if model != "" {
191+
addCandidate(model)
210192
}
211-
aliases := make([]string, 0, 2)
212-
for _, entry := range entries {
213-
if !strings.EqualFold(strings.TrimSpace(entry.Name), model) {
214-
continue
193+
if requestedModel != "" {
194+
parsed := thinking.ParseSuffix(requestedModel)
195+
base := strings.TrimSpace(parsed.ModelName)
196+
if base != "" {
197+
addCandidate(base)
215198
}
216-
alias := strings.TrimSpace(entry.Alias)
217-
if alias == "" {
218-
continue
199+
if parsed.HasSuffix {
200+
addCandidate(requestedModel)
219201
}
220-
aliases = append(aliases, alias)
221202
}
222-
return aliases
203+
return candidates
223204
}
224205

225206
// buildPayloadPath combines an optional root path with a relative parameter path.
@@ -258,6 +239,35 @@ func payloadRawValue(value any) ([]byte, bool) {
258239
}
259240
}
260241

242+
func payloadRequestedModel(opts cliproxyexecutor.Options, fallback string) string {
243+
fallback = strings.TrimSpace(fallback)
244+
if len(opts.Metadata) == 0 {
245+
return fallback
246+
}
247+
raw, ok := opts.Metadata[cliproxyexecutor.RequestedModelMetadataKey]
248+
if !ok || raw == nil {
249+
return fallback
250+
}
251+
switch v := raw.(type) {
252+
case string:
253+
if strings.TrimSpace(v) == "" {
254+
return fallback
255+
}
256+
return strings.TrimSpace(v)
257+
case []byte:
258+
if len(v) == 0 {
259+
return fallback
260+
}
261+
trimmed := strings.TrimSpace(string(v))
262+
if trimmed == "" {
263+
return fallback
264+
}
265+
return trimmed
266+
default:
267+
return fallback
268+
}
269+
}
270+
261271
// matchModelPattern performs simple wildcard matching where '*' matches zero or more characters.
262272
// Examples:
263273
//

0 commit comments

Comments
 (0)