diff --git a/internal/registry/model_definitions_static_data.go b/internal/registry/model_definitions_static_data.go index 182acdc5..45b1f133 100644 --- a/internal/registry/model_definitions_static_data.go +++ b/internal/registry/model_definitions_static_data.go @@ -716,6 +716,20 @@ func GetOpenAIModels() []*ModelInfo { SupportedParameters: []string{"tools"}, Thinking: &ThinkingSupport{Levels: []string{"low", "medium", "high", "xhigh"}}, }, + { + ID: "gpt-5.3-codex", + Object: "model", + Created: 1770307200, + OwnedBy: "openai", + Type: "openai", + Version: "gpt-5.3", + DisplayName: "GPT 5.3 Codex", + Description: "Stable version of GPT 5.3 Codex, The best model for coding and agentic tasks across domains.", + ContextLength: 400000, + MaxCompletionTokens: 128000, + SupportedParameters: []string{"tools"}, + Thinking: &ThinkingSupport{Levels: []string{"low", "medium", "high", "xhigh"}}, + }, } } diff --git a/internal/runtime/executor/aistudio_executor.go b/internal/runtime/executor/aistudio_executor.go index 317090d0..6faf028a 100644 --- a/internal/runtime/executor/aistudio_executor.go +++ b/internal/runtime/executor/aistudio_executor.go @@ -163,7 +163,7 @@ func (e *AIStudioExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, } reporter.publish(ctx, parseGeminiUsage(wsResp.Body)) var param any - out := sdktranslator.TranslateNonStream(ctx, body.toFormat, opts.SourceFormat, req.Model, bytes.Clone(opts.OriginalRequest), bytes.Clone(translatedReq), bytes.Clone(wsResp.Body), ¶m) + out := sdktranslator.TranslateNonStream(ctx, body.toFormat, opts.SourceFormat, req.Model, opts.OriginalRequest, bytes.Clone(translatedReq), bytes.Clone(wsResp.Body), ¶m) resp = cliproxyexecutor.Response{Payload: ensureColonSpacedJSON([]byte(out))} return resp, nil } @@ -279,7 +279,7 @@ func (e *AIStudioExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth if detail, ok := parseGeminiStreamUsage(filtered); ok { reporter.publish(ctx, detail) } - lines := sdktranslator.TranslateStream(ctx, body.toFormat, opts.SourceFormat, req.Model, bytes.Clone(opts.OriginalRequest), translatedReq, bytes.Clone(filtered), ¶m) + lines := sdktranslator.TranslateStream(ctx, body.toFormat, opts.SourceFormat, req.Model, opts.OriginalRequest, translatedReq, bytes.Clone(filtered), ¶m) for i := range lines { out <- cliproxyexecutor.StreamChunk{Payload: ensureColonSpacedJSON([]byte(lines[i]))} } @@ -295,7 +295,7 @@ func (e *AIStudioExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth if len(event.Payload) > 0 { appendAPIResponseChunk(ctx, e.cfg, bytes.Clone(event.Payload)) } - lines := sdktranslator.TranslateStream(ctx, body.toFormat, opts.SourceFormat, req.Model, bytes.Clone(opts.OriginalRequest), translatedReq, bytes.Clone(event.Payload), ¶m) + lines := sdktranslator.TranslateStream(ctx, body.toFormat, opts.SourceFormat, req.Model, opts.OriginalRequest, translatedReq, bytes.Clone(event.Payload), ¶m) for i := range lines { out <- cliproxyexecutor.StreamChunk{Payload: ensureColonSpacedJSON([]byte(lines[i]))} } @@ -393,10 +393,11 @@ func (e *AIStudioExecutor) translateRequest(req cliproxyexecutor.Request, opts c from := opts.SourceFormat to := sdktranslator.FromString("gemini") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, stream) payload := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), stream) payload, err := thinking.ApplyThinking(payload, req.Model, from.String(), to.String(), e.Identifier()) diff --git a/internal/runtime/executor/antigravity_executor.go b/internal/runtime/executor/antigravity_executor.go index 22062aba..7b38453f 100644 --- a/internal/runtime/executor/antigravity_executor.go +++ b/internal/runtime/executor/antigravity_executor.go @@ -133,10 +133,11 @@ func (e *AntigravityExecutor) Execute(ctx context.Context, auth *cliproxyauth.Au from := opts.SourceFormat to := sdktranslator.FromString("antigravity") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, false) translated := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), false) @@ -230,7 +231,7 @@ attemptLoop: reporter.publish(ctx, parseAntigravityUsage(bodyBytes)) var param any - converted := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), translated, bodyBytes, ¶m) + converted := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, opts.OriginalRequest, translated, bodyBytes, ¶m) resp = cliproxyexecutor.Response{Payload: []byte(converted)} reporter.ensurePublished(ctx) return resp, nil @@ -274,10 +275,11 @@ func (e *AntigravityExecutor) executeClaudeNonStream(ctx context.Context, auth * from := opts.SourceFormat to := sdktranslator.FromString("antigravity") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, true) translated := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), true) @@ -433,7 +435,7 @@ attemptLoop: reporter.publish(ctx, parseAntigravityUsage(resp.Payload)) var param any - converted := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), translated, resp.Payload, ¶m) + converted := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, opts.OriginalRequest, translated, resp.Payload, ¶m) resp = cliproxyexecutor.Response{Payload: []byte(converted)} reporter.ensurePublished(ctx) @@ -665,10 +667,11 @@ func (e *AntigravityExecutor) ExecuteStream(ctx context.Context, auth *cliproxya from := opts.SourceFormat to := sdktranslator.FromString("antigravity") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, true) translated := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), true) @@ -800,12 +803,12 @@ attemptLoop: reporter.publish(ctx, detail) } - chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), translated, bytes.Clone(payload), ¶m) + chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, opts.OriginalRequest, translated, bytes.Clone(payload), ¶m) for i := range chunks { out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])} } } - tail := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), translated, []byte("[DONE]"), ¶m) + tail := sdktranslator.TranslateStream(ctx, to, from, req.Model, opts.OriginalRequest, translated, []byte("[DONE]"), ¶m) for i := range tail { out <- cliproxyexecutor.StreamChunk{Payload: []byte(tail[i])} } diff --git a/internal/runtime/executor/claude_executor.go b/internal/runtime/executor/claude_executor.go index 5b76d02a..694de1ef 100644 --- a/internal/runtime/executor/claude_executor.go +++ b/internal/runtime/executor/claude_executor.go @@ -100,10 +100,11 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r to := sdktranslator.FromString("claude") // Use streaming translation to preserve function calling, except for claude. stream := from != to - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, stream) body := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), stream) body, _ = sjson.SetBytes(body, "model", baseModel) @@ -216,7 +217,7 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r to, from, req.Model, - bytes.Clone(opts.OriginalRequest), + opts.OriginalRequest, bodyForTranslation, data, ¶m, @@ -240,10 +241,11 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A defer reporter.trackFailure(ctx, &err) from := opts.SourceFormat to := sdktranslator.FromString("claude") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, true) body := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), true) body, _ = sjson.SetBytes(body, "model", baseModel) @@ -381,7 +383,7 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A to, from, req.Model, - bytes.Clone(opts.OriginalRequest), + opts.OriginalRequest, bodyForTranslation, bytes.Clone(line), ¶m, diff --git a/internal/runtime/executor/codex_executor.go b/internal/runtime/executor/codex_executor.go index 09ce644e..3de2ba3b 100644 --- a/internal/runtime/executor/codex_executor.go +++ b/internal/runtime/executor/codex_executor.go @@ -88,10 +88,11 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re from := opts.SourceFormat to := sdktranslator.FromString("codex") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, false) body := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), false) @@ -176,7 +177,7 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re } var param any - out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(originalPayload), body, line, ¶m) + out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, originalPayload, body, line, ¶m) resp = cliproxyexecutor.Response{Payload: []byte(out)} return resp, nil } @@ -197,10 +198,11 @@ func (e *CodexExecutor) executeCompact(ctx context.Context, auth *cliproxyauth.A from := opts.SourceFormat to := sdktranslator.FromString("openai-response") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, false) body := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), false) @@ -265,7 +267,7 @@ func (e *CodexExecutor) executeCompact(ctx context.Context, auth *cliproxyauth.A reporter.publish(ctx, parseOpenAIUsage(data)) reporter.ensurePublished(ctx) var param any - out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(originalPayload), body, data, ¶m) + out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, originalPayload, body, data, ¶m) resp = cliproxyexecutor.Response{Payload: []byte(out)} return resp, nil } @@ -286,10 +288,11 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au from := opts.SourceFormat to := sdktranslator.FromString("codex") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, true) body := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), true) @@ -378,7 +381,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au } } - chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(originalPayload), body, bytes.Clone(line), ¶m) + chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, originalPayload, body, bytes.Clone(line), ¶m) for i := range chunks { out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])} } diff --git a/internal/runtime/executor/gemini_cli_executor.go b/internal/runtime/executor/gemini_cli_executor.go index 16ff0158..a668c372 100644 --- a/internal/runtime/executor/gemini_cli_executor.go +++ b/internal/runtime/executor/gemini_cli_executor.go @@ -119,10 +119,11 @@ func (e *GeminiCLIExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth from := opts.SourceFormat to := sdktranslator.FromString("gemini-cli") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, false) basePayload := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), false) @@ -223,7 +224,7 @@ func (e *GeminiCLIExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth if httpResp.StatusCode >= 200 && httpResp.StatusCode < 300 { reporter.publish(ctx, parseGeminiCLIUsage(data)) var param any - out := sdktranslator.TranslateNonStream(respCtx, to, from, attemptModel, bytes.Clone(opts.OriginalRequest), payload, data, ¶m) + out := sdktranslator.TranslateNonStream(respCtx, to, from, attemptModel, opts.OriginalRequest, payload, data, ¶m) resp = cliproxyexecutor.Response{Payload: []byte(out)} return resp, nil } @@ -272,10 +273,11 @@ func (e *GeminiCLIExecutor) ExecuteStream(ctx context.Context, auth *cliproxyaut from := opts.SourceFormat to := sdktranslator.FromString("gemini-cli") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, true) basePayload := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), true) @@ -399,14 +401,14 @@ func (e *GeminiCLIExecutor) ExecuteStream(ctx context.Context, auth *cliproxyaut reporter.publish(ctx, detail) } if bytes.HasPrefix(line, dataTag) { - segments := sdktranslator.TranslateStream(respCtx, to, from, attemptModel, bytes.Clone(opts.OriginalRequest), reqBody, bytes.Clone(line), ¶m) + segments := sdktranslator.TranslateStream(respCtx, to, from, attemptModel, opts.OriginalRequest, reqBody, bytes.Clone(line), ¶m) for i := range segments { out <- cliproxyexecutor.StreamChunk{Payload: []byte(segments[i])} } } } - segments := sdktranslator.TranslateStream(respCtx, to, from, attemptModel, bytes.Clone(opts.OriginalRequest), reqBody, bytes.Clone([]byte("[DONE]")), ¶m) + segments := sdktranslator.TranslateStream(respCtx, to, from, attemptModel, opts.OriginalRequest, reqBody, bytes.Clone([]byte("[DONE]")), ¶m) for i := range segments { out <- cliproxyexecutor.StreamChunk{Payload: []byte(segments[i])} } @@ -428,12 +430,12 @@ func (e *GeminiCLIExecutor) ExecuteStream(ctx context.Context, auth *cliproxyaut appendAPIResponseChunk(ctx, e.cfg, data) reporter.publish(ctx, parseGeminiCLIUsage(data)) var param any - segments := sdktranslator.TranslateStream(respCtx, to, from, attemptModel, bytes.Clone(opts.OriginalRequest), reqBody, data, ¶m) + segments := sdktranslator.TranslateStream(respCtx, to, from, attemptModel, opts.OriginalRequest, reqBody, data, ¶m) for i := range segments { out <- cliproxyexecutor.StreamChunk{Payload: []byte(segments[i])} } - segments = sdktranslator.TranslateStream(respCtx, to, from, attemptModel, bytes.Clone(opts.OriginalRequest), reqBody, bytes.Clone([]byte("[DONE]")), ¶m) + segments = sdktranslator.TranslateStream(respCtx, to, from, attemptModel, opts.OriginalRequest, reqBody, bytes.Clone([]byte("[DONE]")), ¶m) for i := range segments { out <- cliproxyexecutor.StreamChunk{Payload: []byte(segments[i])} } diff --git a/internal/runtime/executor/gemini_executor.go b/internal/runtime/executor/gemini_executor.go index 8f729f5b..2d24d6ce 100644 --- a/internal/runtime/executor/gemini_executor.go +++ b/internal/runtime/executor/gemini_executor.go @@ -116,10 +116,11 @@ func (e *GeminiExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r // Official Gemini API via API key or OAuth bearer from := opts.SourceFormat to := sdktranslator.FromString("gemini") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, false) body := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), false) @@ -203,7 +204,7 @@ func (e *GeminiExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r appendAPIResponseChunk(ctx, e.cfg, data) reporter.publish(ctx, parseGeminiUsage(data)) var param any - out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, data, ¶m) + out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, opts.OriginalRequest, body, data, ¶m) resp = cliproxyexecutor.Response{Payload: []byte(out)} return resp, nil } @@ -222,10 +223,11 @@ func (e *GeminiExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A from := opts.SourceFormat to := sdktranslator.FromString("gemini") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, true) body := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), true) @@ -318,12 +320,12 @@ func (e *GeminiExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A if detail, ok := parseGeminiStreamUsage(payload); ok { reporter.publish(ctx, detail) } - lines := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone(payload), ¶m) + lines := sdktranslator.TranslateStream(ctx, to, from, req.Model, opts.OriginalRequest, body, bytes.Clone(payload), ¶m) for i := range lines { out <- cliproxyexecutor.StreamChunk{Payload: []byte(lines[i])} } } - lines := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone([]byte("[DONE]")), ¶m) + lines := sdktranslator.TranslateStream(ctx, to, from, req.Model, opts.OriginalRequest, body, bytes.Clone([]byte("[DONE]")), ¶m) for i := range lines { out <- cliproxyexecutor.StreamChunk{Payload: []byte(lines[i])} } diff --git a/internal/runtime/executor/gemini_vertex_executor.go b/internal/runtime/executor/gemini_vertex_executor.go index 2db0e37c..be2fc238 100644 --- a/internal/runtime/executor/gemini_vertex_executor.go +++ b/internal/runtime/executor/gemini_vertex_executor.go @@ -318,10 +318,11 @@ func (e *GeminiVertexExecutor) executeWithServiceAccount(ctx context.Context, au from := opts.SourceFormat to := sdktranslator.FromString("gemini") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, false) body = sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), false) @@ -417,7 +418,7 @@ func (e *GeminiVertexExecutor) executeWithServiceAccount(ctx context.Context, au from := opts.SourceFormat to := sdktranslator.FromString("gemini") var param any - out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, data, ¶m) + out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, opts.OriginalRequest, body, data, ¶m) resp = cliproxyexecutor.Response{Payload: []byte(out)} return resp, nil } @@ -432,10 +433,11 @@ func (e *GeminiVertexExecutor) executeWithAPIKey(ctx context.Context, auth *clip from := opts.SourceFormat to := sdktranslator.FromString("gemini") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, false) body := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), false) @@ -521,7 +523,7 @@ func (e *GeminiVertexExecutor) executeWithAPIKey(ctx context.Context, auth *clip appendAPIResponseChunk(ctx, e.cfg, data) reporter.publish(ctx, parseGeminiUsage(data)) var param any - out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, data, ¶m) + out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, opts.OriginalRequest, body, data, ¶m) resp = cliproxyexecutor.Response{Payload: []byte(out)} return resp, nil } @@ -536,10 +538,11 @@ func (e *GeminiVertexExecutor) executeStreamWithServiceAccount(ctx context.Conte from := opts.SourceFormat to := sdktranslator.FromString("gemini") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, true) body := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), true) @@ -632,12 +635,12 @@ func (e *GeminiVertexExecutor) executeStreamWithServiceAccount(ctx context.Conte if detail, ok := parseGeminiStreamUsage(line); ok { reporter.publish(ctx, detail) } - lines := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone(line), ¶m) + lines := sdktranslator.TranslateStream(ctx, to, from, req.Model, opts.OriginalRequest, body, bytes.Clone(line), ¶m) for i := range lines { out <- cliproxyexecutor.StreamChunk{Payload: []byte(lines[i])} } } - lines := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, []byte("[DONE]"), ¶m) + lines := sdktranslator.TranslateStream(ctx, to, from, req.Model, opts.OriginalRequest, body, []byte("[DONE]"), ¶m) for i := range lines { out <- cliproxyexecutor.StreamChunk{Payload: []byte(lines[i])} } @@ -660,10 +663,11 @@ func (e *GeminiVertexExecutor) executeStreamWithAPIKey(ctx context.Context, auth from := opts.SourceFormat to := sdktranslator.FromString("gemini") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, true) body := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), true) @@ -756,12 +760,12 @@ func (e *GeminiVertexExecutor) executeStreamWithAPIKey(ctx context.Context, auth if detail, ok := parseGeminiStreamUsage(line); ok { reporter.publish(ctx, detail) } - lines := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone(line), ¶m) + lines := sdktranslator.TranslateStream(ctx, to, from, req.Model, opts.OriginalRequest, body, bytes.Clone(line), ¶m) for i := range lines { out <- cliproxyexecutor.StreamChunk{Payload: []byte(lines[i])} } } - lines := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, []byte("[DONE]"), ¶m) + lines := sdktranslator.TranslateStream(ctx, to, from, req.Model, opts.OriginalRequest, body, []byte("[DONE]"), ¶m) for i := range lines { out <- cliproxyexecutor.StreamChunk{Payload: []byte(lines[i])} } diff --git a/internal/runtime/executor/iflow_executor.go b/internal/runtime/executor/iflow_executor.go index 08a0a5af..abe9bdfa 100644 --- a/internal/runtime/executor/iflow_executor.go +++ b/internal/runtime/executor/iflow_executor.go @@ -87,10 +87,11 @@ func (e *IFlowExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re from := opts.SourceFormat to := sdktranslator.FromString("openai") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, false) body := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), false) body, _ = sjson.SetBytes(body, "model", baseModel) @@ -163,7 +164,7 @@ func (e *IFlowExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re var param any // Note: TranslateNonStream uses req.Model (original with suffix) to preserve // the original model name in the response for client compatibility. - out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, data, ¶m) + out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, opts.OriginalRequest, body, data, ¶m) resp = cliproxyexecutor.Response{Payload: []byte(out)} return resp, nil } @@ -189,10 +190,11 @@ func (e *IFlowExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au from := opts.SourceFormat to := sdktranslator.FromString("openai") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, true) body := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), true) body, _ = sjson.SetBytes(body, "model", baseModel) @@ -274,7 +276,7 @@ func (e *IFlowExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au if detail, ok := parseOpenAIStreamUsage(line); ok { reporter.publish(ctx, detail) } - chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone(line), ¶m) + chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, opts.OriginalRequest, body, bytes.Clone(line), ¶m) for i := range chunks { out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])} } diff --git a/internal/runtime/executor/openai_compat_executor.go b/internal/runtime/executor/openai_compat_executor.go index ee61556e..3906948f 100644 --- a/internal/runtime/executor/openai_compat_executor.go +++ b/internal/runtime/executor/openai_compat_executor.go @@ -88,10 +88,11 @@ func (e *OpenAICompatExecutor) Execute(ctx context.Context, auth *cliproxyauth.A to = sdktranslator.FromString("openai-response") endpoint = "/responses/compact" } - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, opts.Stream) translated := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), opts.Stream) requestedModel := payloadRequestedModel(opts, req.Model) @@ -170,7 +171,7 @@ func (e *OpenAICompatExecutor) Execute(ctx context.Context, auth *cliproxyauth.A reporter.ensurePublished(ctx) // Translate response back to source format when needed var param any - out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), translated, body, ¶m) + out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, opts.OriginalRequest, translated, body, ¶m) resp = cliproxyexecutor.Response{Payload: []byte(out)} return resp, nil } @@ -189,10 +190,11 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy from := opts.SourceFormat to := sdktranslator.FromString("openai") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, true) translated := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), true) requestedModel := payloadRequestedModel(opts, req.Model) @@ -283,7 +285,7 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy // OpenAI-compatible streams are SSE: lines typically prefixed with "data: ". // Pass through translator; it yields one or more chunks for the target schema. - chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), translated, bytes.Clone(line), ¶m) + chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, opts.OriginalRequest, translated, bytes.Clone(line), ¶m) for i := range chunks { out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])} } diff --git a/internal/runtime/executor/qwen_executor.go b/internal/runtime/executor/qwen_executor.go index 8df359e9..526c1389 100644 --- a/internal/runtime/executor/qwen_executor.go +++ b/internal/runtime/executor/qwen_executor.go @@ -81,10 +81,11 @@ func (e *QwenExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req from := opts.SourceFormat to := sdktranslator.FromString("openai") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, false) body := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), false) body, _ = sjson.SetBytes(body, "model", baseModel) @@ -150,7 +151,7 @@ func (e *QwenExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req var param any // Note: TranslateNonStream uses req.Model (original with suffix) to preserve // the original model name in the response for client compatibility. - out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, data, ¶m) + out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, opts.OriginalRequest, body, data, ¶m) resp = cliproxyexecutor.Response{Payload: []byte(out)} return resp, nil } @@ -171,10 +172,11 @@ func (e *QwenExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Aut from := opts.SourceFormat to := sdktranslator.FromString("openai") - originalPayload := bytes.Clone(req.Payload) + originalPayloadSource := req.Payload if len(opts.OriginalRequest) > 0 { - originalPayload = bytes.Clone(opts.OriginalRequest) + originalPayloadSource = opts.OriginalRequest } + originalPayload := bytes.Clone(originalPayloadSource) originalTranslated := sdktranslator.TranslateRequest(from, to, baseModel, originalPayload, true) body := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), true) body, _ = sjson.SetBytes(body, "model", baseModel) @@ -253,12 +255,12 @@ func (e *QwenExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Aut if detail, ok := parseOpenAIStreamUsage(line); ok { reporter.publish(ctx, detail) } - chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone(line), ¶m) + chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, opts.OriginalRequest, body, bytes.Clone(line), ¶m) for i := range chunks { out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])} } } - doneChunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone([]byte("[DONE]")), ¶m) + doneChunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, opts.OriginalRequest, body, bytes.Clone([]byte("[DONE]")), ¶m) for i := range doneChunks { out <- cliproxyexecutor.StreamChunk{Payload: []byte(doneChunks[i])} } diff --git a/internal/util/gemini_schema.go b/internal/util/gemini_schema.go index b6b128d4..e74d1271 100644 --- a/internal/util/gemini_schema.go +++ b/internal/util/gemini_schema.go @@ -61,14 +61,20 @@ func cleanJSONSchema(jsonStr string, addPlaceholder bool) string { // removeKeywords removes all occurrences of specified keywords from the JSON schema. func removeKeywords(jsonStr string, keywords []string) string { + deletePaths := make([]string, 0) + pathsByField := findPathsByFields(jsonStr, keywords) for _, key := range keywords { - for _, p := range findPaths(jsonStr, key) { + for _, p := range pathsByField[key] { if isPropertyDefinition(trimSuffix(p, "."+key)) { continue } - jsonStr, _ = sjson.Delete(jsonStr, p) + deletePaths = append(deletePaths, p) } } + sortByDepth(deletePaths) + for _, p := range deletePaths { + jsonStr, _ = sjson.Delete(jsonStr, p) + } return jsonStr } @@ -235,8 +241,9 @@ var unsupportedConstraints = []string{ } func moveConstraintsToDescription(jsonStr string) string { + pathsByField := findPathsByFields(jsonStr, unsupportedConstraints) for _, key := range unsupportedConstraints { - for _, p := range findPaths(jsonStr, key) { + for _, p := range pathsByField[key] { val := gjson.Get(jsonStr, p) if !val.Exists() || val.IsObject() || val.IsArray() { continue @@ -424,14 +431,21 @@ func removeUnsupportedKeywords(jsonStr string) string { "$schema", "$defs", "definitions", "const", "$ref", "additionalProperties", "propertyNames", // Gemini doesn't support property name validation ) + + deletePaths := make([]string, 0) + pathsByField := findPathsByFields(jsonStr, keywords) for _, key := range keywords { - for _, p := range findPaths(jsonStr, key) { + for _, p := range pathsByField[key] { if isPropertyDefinition(trimSuffix(p, "."+key)) { continue } - jsonStr, _ = sjson.Delete(jsonStr, p) + deletePaths = append(deletePaths, p) } } + sortByDepth(deletePaths) + for _, p := range deletePaths { + jsonStr, _ = sjson.Delete(jsonStr, p) + } // Remove x-* extension fields (e.g., x-google-enum-descriptions) that are not supported by Gemini API jsonStr = removeExtensionFields(jsonStr) return jsonStr @@ -581,6 +595,42 @@ func findPaths(jsonStr, field string) []string { return paths } +func findPathsByFields(jsonStr string, fields []string) map[string][]string { + set := make(map[string]struct{}, len(fields)) + for _, field := range fields { + set[field] = struct{}{} + } + paths := make(map[string][]string, len(set)) + walkForFields(gjson.Parse(jsonStr), "", set, paths) + return paths +} + +func walkForFields(value gjson.Result, path string, fields map[string]struct{}, paths map[string][]string) { + switch value.Type { + case gjson.JSON: + value.ForEach(func(key, val gjson.Result) bool { + keyStr := key.String() + safeKey := escapeGJSONPathKey(keyStr) + + var childPath string + if path == "" { + childPath = safeKey + } else { + childPath = path + "." + safeKey + } + + if _, ok := fields[keyStr]; ok { + paths[keyStr] = append(paths[keyStr], childPath) + } + + walkForFields(val, childPath, fields, paths) + return true + }) + case gjson.String, gjson.Number, gjson.True, gjson.False, gjson.Null: + // Terminal types - no further traversal needed + } +} + func sortByDepth(paths []string) { sort.Slice(paths, func(i, j int) bool { return len(paths[i]) > len(paths[j]) }) } diff --git a/sdk/api/handlers/handlers.go b/sdk/api/handlers/handlers.go index fb26d4ff..624d5bc4 100644 --- a/sdk/api/handlers/handlers.go +++ b/sdk/api/handlers/handlers.go @@ -378,9 +378,13 @@ func (h *BaseAPIHandler) ExecuteWithAuthManager(ctx context.Context, handlerType } reqMeta := requestExecutionMetadata(ctx) reqMeta[coreexecutor.RequestedModelMetadataKey] = normalizedModel + payload := rawJSON + if len(payload) == 0 { + payload = nil + } req := coreexecutor.Request{ Model: normalizedModel, - Payload: cloneBytes(rawJSON), + Payload: payload, } opts := coreexecutor.Options{ Stream: false, @@ -417,9 +421,13 @@ func (h *BaseAPIHandler) ExecuteCountWithAuthManager(ctx context.Context, handle } reqMeta := requestExecutionMetadata(ctx) reqMeta[coreexecutor.RequestedModelMetadataKey] = normalizedModel + payload := rawJSON + if len(payload) == 0 { + payload = nil + } req := coreexecutor.Request{ Model: normalizedModel, - Payload: cloneBytes(rawJSON), + Payload: payload, } opts := coreexecutor.Options{ Stream: false, @@ -459,9 +467,13 @@ func (h *BaseAPIHandler) ExecuteStreamWithAuthManager(ctx context.Context, handl } reqMeta := requestExecutionMetadata(ctx) reqMeta[coreexecutor.RequestedModelMetadataKey] = normalizedModel + payload := rawJSON + if len(payload) == 0 { + payload = nil + } req := coreexecutor.Request{ Model: normalizedModel, - Payload: cloneBytes(rawJSON), + Payload: payload, } opts := coreexecutor.Options{ Stream: true,