fix(openai): set tool_choice auto for Codex image bridge

This commit is contained in:
Hao Liu
2026-06-26 19:10:37 +08:00
parent c275422251
commit e5f7836bf3
5 changed files with 52 additions and 0 deletions
@@ -754,6 +754,20 @@ func ensureOpenAIResponsesImageGenerationTool(reqBody map[string]any) bool {
return true
}
func ensureOpenAIResponsesImageGenerationToolChoiceAuto(reqBody map[string]any) bool {
if len(reqBody) == 0 || !hasOpenAIImageGenerationTool(reqBody) {
return false
}
if isCodexSparkModel(firstNonEmptyString(reqBody["model"])) {
return false
}
if _, ok := reqBody["tool_choice"]; ok {
return false
}
reqBody["tool_choice"] = "auto"
return true
}
func applyCodexImageGenerationBridgeInstructions(reqBody map[string]any) bool {
if len(reqBody) == 0 || !hasOpenAIImageGenerationTool(reqBody) {
return false
@@ -2696,6 +2696,10 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
markDecodedModified()
logger.LegacyPrintf("service.openai_gateway", "[OpenAI] Injected /responses image_generation tool for Codex client")
}
if codexImageGenerationBridgeEnabled && ensureOpenAIResponsesImageGenerationToolChoiceAuto(decoded) {
markDecodedModified()
logger.LegacyPrintf("service.openai_gateway", "[OpenAI] Set /responses image_generation tool_choice=auto for Codex client")
}
if normalizeOpenAIResponsesImageGenerationTools(decoded) {
markDecodedModified()
logger.LegacyPrintf("service.openai_gateway", "[OpenAI] Normalized /responses image_generation tool payload")
@@ -116,6 +116,11 @@ func TestOpenAIGatewayServiceForward_CodexImageInjectionRespectsGroupCapability(
require.Equal(t, tt.wantInjected, hasImageTool)
instructions := gjson.GetBytes(upstream.lastBody, "instructions").String()
require.Equal(t, tt.wantInjected, strings.Contains(instructions, "image_generation"))
toolChoice := gjson.GetBytes(upstream.lastBody, "tool_choice")
require.Equal(t, tt.wantInjected, toolChoice.Exists())
if tt.wantInjected {
require.Equal(t, "auto", toolChoice.String())
}
})
}
}
@@ -175,10 +180,34 @@ func TestOpenAIGatewayServiceForward_ChannelBridgeOverrideEnablesCodexInjection(
require.NotNil(t, result)
require.NotNil(t, upstream.lastReq)
require.True(t, gjson.GetBytes(upstream.lastBody, `tools.#(type=="image_generation")`).Exists())
require.Equal(t, "auto", gjson.GetBytes(upstream.lastBody, "tool_choice").String())
instructions := gjson.GetBytes(upstream.lastBody, "instructions").String()
require.Contains(t, instructions, "image_generation")
}
func TestOpenAIGatewayServiceForward_CodexBridgePreservesExistingToolChoice(t *testing.T) {
gin.SetMode(gin.TestMode)
upstream := &httpUpstreamRecorder{
resp: &http.Response{
StatusCode: http.StatusOK,
Header: http.Header{"Content-Type": []string{"application/json"}},
Body: io.NopCloser(strings.NewReader(`{"id":"resp_codex_tool_choice","model":"gpt-5.4","usage":{"input_tokens":1,"output_tokens":1}}`)),
},
}
svc := newOpenAIImageGenerationControlTestService(upstream)
svc.cfg.Gateway.CodexImageGenerationBridgeEnabled = true
c, _ := newOpenAIImageGenerationControlTestContext(true, "codex_cli_rs/0.98.0")
account := newOpenAIImageGenerationControlTestAccount()
result, err := svc.Forward(context.Background(), c, account, []byte(`{"model":"gpt-5.4","input":"draw","stream":false,"tools":[{"type":"image_generation"}],"tool_choice":{"type":"image_generation"}}`))
require.NoError(t, err)
require.NotNil(t, result)
require.NotNil(t, upstream.lastReq)
require.Equal(t, "image_generation", gjson.GetBytes(upstream.lastBody, "tool_choice.type").String())
}
func TestOpenAIGatewayService_CodexImageGenerationBridgeOverridePrecedence(t *testing.T) {
groupID := int64(4242)
@@ -2666,6 +2666,10 @@ func (s *OpenAIGatewayService) ProxyResponsesWebSocketFromClient(
bridgeModified = true
logOpenAIWSModeInfo("ingress_ws_codex_image_tool_injected account_id=%d", account.ID)
}
if ensureOpenAIResponsesImageGenerationToolChoiceAuto(payloadMap) {
bridgeModified = true
logOpenAIWSModeInfo("ingress_ws_codex_image_tool_choice_auto account_id=%d", account.ID)
}
if normalizeOpenAIResponsesImageGenerationTools(payloadMap) {
bridgeModified = true
}
@@ -431,6 +431,7 @@ func TestOpenAIGatewayService_ProxyResponsesWebSocketFromClient_InjectsCodexImag
upstreamPayload := requestToJSONString(captureConn.writes[0])
require.True(t, gjson.Get(upstreamPayload, `tools.#(type=="image_generation")`).Exists())
require.Equal(t, "png", gjson.Get(upstreamPayload, `tools.#(type=="image_generation").output_format`).String())
require.Equal(t, "auto", gjson.Get(upstreamPayload, "tool_choice").String())
require.Contains(t, gjson.Get(upstreamPayload, "instructions").String(), "image_generation")
}