Make Codex CLI passthrough

This commit is contained in:
cyhhao
2026-01-10 03:12:56 +08:00
parent 7a06c4873e
commit eb06006d6c
3 changed files with 54 additions and 65 deletions

View File

@@ -11,7 +11,6 @@ import (
"time"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware"
"github.com/Wei-Shaw/sub2api/internal/service"
@@ -92,17 +91,7 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
return
}
// For non-Codex CLI requests, set default instructions
userAgent := c.GetHeader("User-Agent")
if !openai.IsCodexCLIRequest(userAgent) {
reqBody["instructions"] = openai.DefaultInstructions
// Re-serialize body
body, err = json.Marshal(reqBody)
if err != nil {
h.errorResponse(c, http.StatusInternalServerError, "api_error", "Failed to process request")
return
}
}
// Track if we've started streaming (for error handling)
streamStarted := false

View File

@@ -135,7 +135,7 @@ func codexModeEnabled() bool {
}
}
func applyCodexOAuthTransform(reqBody map[string]any, codexMode bool) codexTransformResult {
func applyCodexOAuthTransform(reqBody map[string]any) codexTransformResult {
result := codexTransformResult{}
model := ""
@@ -151,16 +151,13 @@ func applyCodexOAuthTransform(reqBody map[string]any, codexMode bool) codexTrans
result.NormalizedModel = normalizedModel
}
reqBody["store"] = false
reqBody["stream"] = true
result.Modified = true
instructions := getCodexInstructions(normalizedModel)
if instructions != "" {
if existing, ok := reqBody["instructions"].(string); !ok || existing != instructions {
reqBody["instructions"] = instructions
result.Modified = true
}
if v, ok := reqBody["store"].(bool); !ok || v {
reqBody["store"] = false
result.Modified = true
}
if v, ok := reqBody["stream"].(bool); !ok || !v {
reqBody["stream"] = true
result.Modified = true
}
if _, ok := reqBody["max_output_tokens"]; ok {
@@ -180,49 +177,30 @@ func applyCodexOAuthTransform(reqBody map[string]any, codexMode bool) codexTrans
result.PromptCacheKey = strings.TrimSpace(v)
}
instructions := strings.TrimSpace(getCodexInstructions(normalizedModel))
existingInstructions, _ := reqBody["instructions"].(string)
existingInstructions = strings.TrimSpace(existingInstructions)
if instructions != "" {
if existingInstructions != "" && existingInstructions != instructions {
if input, ok := reqBody["input"].([]any); ok {
reqBody["input"] = prependSystemInstruction(input, existingInstructions)
result.Modified = true
}
}
if existingInstructions != instructions {
reqBody["instructions"] = instructions
result.Modified = true
}
}
if input, ok := reqBody["input"].([]any); ok {
input = filterCodexInput(input)
if codexMode {
cachedPrompt := getOpenCodeCodexPrompt()
input = filterOpenCodeSystemPromptsWithCachedPrompt(input, cachedPrompt)
if hasTools(reqBody) {
input = addCodexBridgeMessage(input)
}
} else if hasTools(reqBody) {
input = addToolRemapMessage(input)
}
input = normalizeOrphanedToolOutputs(input)
reqBody["input"] = input
result.Modified = true
}
effort, summary := resolveCodexReasoning(reqBody, normalizedModel)
if effort != "" || summary != "" {
reasoning := ensureMap(reqBody["reasoning"])
if effort != "" {
reasoning["effort"] = effort
}
if summary != "" {
reasoning["summary"] = summary
}
reqBody["reasoning"] = reasoning
result.Modified = true
}
textVerbosity := resolveTextVerbosity(reqBody)
if textVerbosity != "" {
text := ensureMap(reqBody["text"])
text["verbosity"] = textVerbosity
reqBody["text"] = text
result.Modified = true
}
include := resolveInclude(reqBody)
if include != nil {
reqBody["include"] = include
result.Modified = true
}
return result
}
@@ -487,6 +465,19 @@ func filterCodexInput(input []any) []any {
return filtered
}
func prependSystemInstruction(input []any, instructions string) []any {
message := map[string]any{
"role": "system",
"content": []any{
map[string]any{
"type": "input_text",
"text": instructions,
},
},
}
return append([]any{message}, input...)
}
func filterOpenCodeSystemPromptsWithCachedPrompt(input []any, cachedPrompt string) []any {
if len(input) == 0 {
return input

View File

@@ -21,6 +21,7 @@ import (
"time"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
"github.com/Wei-Shaw/sub2api/internal/util/responseheaders"
"github.com/Wei-Shaw/sub2api/internal/util/urlvalidator"
"github.com/gin-gonic/gin"
@@ -530,20 +531,28 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
reqModel, _ := reqBody["model"].(string)
reqStream, _ := reqBody["stream"].(bool)
promptCacheKey := ""
if v, ok := reqBody["prompt_cache_key"].(string); ok {
promptCacheKey = strings.TrimSpace(v)
}
// Track if body needs re-serialization
bodyModified := false
originalModel := reqModel
// Apply model mapping
mappedModel := account.GetMappedModel(reqModel)
if mappedModel != reqModel {
reqBody["model"] = mappedModel
bodyModified = true
isCodexCLI := openai.IsCodexCLIRequest(c.GetHeader("User-Agent"))
// Apply model mapping (skip for Codex CLI for transparent forwarding)
mappedModel := reqModel
if !isCodexCLI {
mappedModel = account.GetMappedModel(reqModel)
if mappedModel != reqModel {
reqBody["model"] = mappedModel
bodyModified = true
}
}
if account.Type == AccountTypeOAuth {
codexResult := applyCodexOAuthTransform(reqBody, codexModeEnabled())
if account.Type == AccountTypeOAuth && !isCodexCLI {
codexResult := applyCodexOAuthTransform(reqBody)
if codexResult.Modified {
bodyModified = true
}