diff --git a/backend/internal/service/openai_gateway_grok.go b/backend/internal/service/openai_gateway_grok.go index 19e44fc4d9..2d29bfaabf 100644 --- a/backend/internal/service/openai_gateway_grok.go +++ b/backend/internal/service/openai_gateway_grok.go @@ -122,13 +122,14 @@ func (s *OpenAIGatewayService) forwardGrokResponses( if usage == nil { usage = &OpenAIUsage{} } + reasoningEffort := extractOpenAIReasoningEffortFromBody(patchedBody, originalModel) return &OpenAIForwardResult{ RequestID: firstNonEmpty(resp.Header.Get("x-request-id"), resp.Header.Get("xai-request-id")), ResponseID: responseID, Usage: *usage, Model: originalModel, UpstreamModel: upstreamModel, - ReasoningEffort: ptrStringOrNil(normalizeOpenAIReasoningEffort(gjson.GetBytes(patchedBody, "reasoning.effort").String())), + ReasoningEffort: reasoningEffort, Stream: reqStream, OpenAIWSMode: false, ResponseHeaders: resp.Header.Clone(), diff --git a/backend/internal/service/openai_gateway_grok_test.go b/backend/internal/service/openai_gateway_grok_test.go index 80135033da..8dbd9ddad0 100644 --- a/backend/internal/service/openai_gateway_grok_test.go +++ b/backend/internal/service/openai_gateway_grok_test.go @@ -41,6 +41,17 @@ func TestPatchGrokResponsesBodySetsMappedModelAndDropsUnsupportedFields(t *testi require.Equal(t, "high", gjson.GetBytes(patched, "reasoning.effort").String()) } +func TestExtractGrokResponsesReasoningEffortSupportsOpenAICompatibleField(t *testing.T) { + t.Parallel() + + effort := extractOpenAIReasoningEffortFromBody( + []byte(`{"model":"grok-4.3","reasoning_effort":"high"}`), + "grok-4.3", + ) + require.NotNil(t, effort) + require.Equal(t, "high", *effort) +} + func TestPatchGrokResponsesBodyDropsGrok45ReasoningUnsupportedFields(t *testing.T) { t.Parallel() @@ -656,7 +667,7 @@ func TestForwardGrokResponsesStreamingUsesXAIResponsesAndSnapshots(t *testing.T) recorder := httptest.NewRecorder() c, _ := gin.CreateTestContext(recorder) - body := []byte(`{"model":"grok","input":"hi","stream":true}`) + body := []byte(`{"model":"grok","input":"hi","stream":true,"reasoning_effort":"high"}`) c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", bytes.NewReader(body)) c.Request.Header.Set("Content-Type", "application/json") c.Request.Header.Set("OpenAI-Beta", "responses=experimental") @@ -708,6 +719,7 @@ func TestForwardGrokResponsesStreamingUsesXAIResponsesAndSnapshots(t *testing.T) require.Equal(t, "Bearer access-token", upstream.lastReq.Header.Get("Authorization")) require.Equal(t, "responses=experimental", upstream.lastReq.Header.Get("OpenAI-Beta")) require.Equal(t, "grok-4.5", gjson.GetBytes(upstream.lastBody, "model").String()) + require.Equal(t, "high", gjson.GetBytes(upstream.lastBody, "reasoning_effort").String()) require.True(t, gjson.GetBytes(upstream.lastBody, "stream").Bool()) require.True(t, result.Stream) require.Equal(t, "resp_grok", result.ResponseID) @@ -715,6 +727,8 @@ func TestForwardGrokResponsesStreamingUsesXAIResponsesAndSnapshots(t *testing.T) require.Equal(t, 5, result.Usage.InputTokens) require.Equal(t, 3, result.Usage.OutputTokens) require.Equal(t, 2, result.Usage.CacheReadInputTokens) + require.NotNil(t, result.ReasoningEffort) + require.Equal(t, "high", *result.ReasoningEffort) require.Contains(t, recorder.Header().Get("Content-Type"), "text/event-stream") require.Contains(t, recorder.Body.String(), "response.output_text.delta") require.NotNil(t, repo.updates[52][grokQuotaSnapshotExtraKey])