From ddd10539adf3fea72c9ab23d20c5f97dc8d6602c Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Sun, 17 May 2026 04:51:17 +0800 Subject: [PATCH] feat(xai): normalize xAI input reasoning items and enhance test cases - Added `normalizeXAIInputReasoningItems` to clean up `input` reasoning items, removing null `content` and `encrypted_content` fields. - Updated `xai_executor` test cases to validate input normalization and reasoning item handling. --- internal/runtime/executor/xai_executor.go | 32 +++++++++++++++++++ .../runtime/executor/xai_executor_test.go | 22 +++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/internal/runtime/executor/xai_executor.go b/internal/runtime/executor/xai_executor.go index fe8b0baa2..a9ca369c9 100644 --- a/internal/runtime/executor/xai_executor.go +++ b/internal/runtime/executor/xai_executor.go @@ -500,6 +500,7 @@ func (e *XAIExecutor) prepareResponsesRequest(ctx context.Context, req cliproxye body, _ = sjson.DeleteBytes(body, "safety_identifier") body, _ = sjson.DeleteBytes(body, "stream_options") body = normalizeXAITools(body) + body = normalizeXAIInputReasoningItems(body) body = normalizeCodexInstructions(body) body = sanitizeXAIResponsesBody(body, baseModel) @@ -704,6 +705,37 @@ func normalizeXAITools(body []byte) []byte { return updated } +func normalizeXAIInputReasoningItems(body []byte) []byte { + input := gjson.GetBytes(body, "input") + if !input.Exists() || !input.IsArray() { + return body + } + + updated := body + for i, item := range input.Array() { + if item.Get("type").String() != "reasoning" { + continue + } + contentPath := fmt.Sprintf("input.%d.content", i) + if content := gjson.GetBytes(updated, contentPath); content.Exists() && content.Type == gjson.Null { + updatedBody, errDel := sjson.DeleteBytes(updated, contentPath) + if errDel != nil { + return body + } + updated = updatedBody + } + encryptedContentPath := fmt.Sprintf("input.%d.encrypted_content", i) + if encryptedContent := gjson.GetBytes(updated, encryptedContentPath); encryptedContent.Exists() && encryptedContent.Type == gjson.Null { + updatedBody, errDel := sjson.DeleteBytes(updated, encryptedContentPath) + if errDel != nil { + return body + } + updated = updatedBody + } + } + return updated +} + func removeXAIEncryptedReasoningInclude(body []byte) []byte { include := gjson.GetBytes(body, "include") if !include.Exists() || !include.IsArray() { diff --git a/internal/runtime/executor/xai_executor_test.go b/internal/runtime/executor/xai_executor_test.go index 42003b316..751f1d15d 100644 --- a/internal/runtime/executor/xai_executor_test.go +++ b/internal/runtime/executor/xai_executor_test.go @@ -55,7 +55,7 @@ func TestXAIExecutorExecuteShapesResponsesRequest(t *testing.T) { _, err := exec.Execute(context.Background(), auth, cliproxyexecutor.Request{ Model: "grok-4.3", - Payload: []byte(`{"model":"grok-4.3","input":"hello","include":["reasoning.encrypted_content"],"reasoning":{"effort":"high"},"tools":[{"type":"tool_search"},{"type":"image_generation"},{"type":"custom","name":"apply_patch"},{"type":"custom","name":"custom_lookup"},{"type":"function","name":"lookup"},{"type":"web_search","external_web_access":true,"search_content_types":["text","image"]}]}`), + Payload: []byte(`{"model":"grok-4.3","input":[{"type":"reasoning","summary":[{"type":"summary_text","text":"test"}],"content":null,"encrypted_content":null},{"role":"user","content":"hello"}],"include":["reasoning.encrypted_content"],"reasoning":{"effort":"high"},"tools":[{"type":"tool_search"},{"type":"image_generation"},{"type":"custom","name":"apply_patch"},{"type":"custom","name":"custom_lookup"},{"type":"function","name":"lookup"},{"type":"web_search","external_web_access":true,"search_content_types":["text","image"]}]}`), }, cliproxyexecutor.Options{ SourceFormat: sdktranslator.FormatOpenAIResponse, Stream: false, @@ -91,6 +91,15 @@ func TestXAIExecutorExecuteShapesResponsesRequest(t *testing.T) { if gjson.GetBytes(gotBody, "reasoning.effort").String() != "high" { t.Fatalf("reasoning.effort = %q, want high; body=%s", gjson.GetBytes(gotBody, "reasoning.effort").String(), string(gotBody)) } + if gjson.GetBytes(gotBody, "input.0.content").Exists() { + t.Fatalf("input.0.content exists, want removed; body=%s", string(gotBody)) + } + if gjson.GetBytes(gotBody, "input.0.encrypted_content").Exists() { + t.Fatalf("input.0.encrypted_content exists, want removed; body=%s", string(gotBody)) + } + if got := gjson.GetBytes(gotBody, "input.0.summary.0.text").String(); got != "test" { + t.Fatalf("input.0.summary.0.text = %q, want test; body=%s", got, string(gotBody)) + } tools := gjson.GetBytes(gotBody, "tools").Array() if len(tools) != 3 { t.Fatalf("tools length = %d, want 3; body=%s", len(tools), string(gotBody)) @@ -183,7 +192,7 @@ func TestXAIExecutorExecuteStreamFiltersToolSearchTool(t *testing.T) { result, err := exec.ExecuteStream(context.Background(), auth, cliproxyexecutor.Request{ Model: "grok-4.3", - Payload: []byte(`{"model":"grok-4.3","input":"hello","tools":[{"type":"tool_search"},{"type":"image_generation"},{"type":"custom","name":"apply_patch"},{"type":"custom","name":"custom_lookup"},{"type":"function","name":"lookup"},{"type":"web_search","external_web_access":true,"search_content_types":["text","image"]}]}`), + Payload: []byte(`{"model":"grok-4.3","input":[{"type":"reasoning","summary":[{"type":"summary_text","text":"test"}],"content":null,"encrypted_content":null},{"role":"user","content":"hello"}],"tools":[{"type":"tool_search"},{"type":"image_generation"},{"type":"custom","name":"apply_patch"},{"type":"custom","name":"custom_lookup"},{"type":"function","name":"lookup"},{"type":"web_search","external_web_access":true,"search_content_types":["text","image"]}]}`), }, cliproxyexecutor.Options{ SourceFormat: sdktranslator.FormatOpenAIResponse, Stream: true, @@ -201,6 +210,15 @@ func TestXAIExecutorExecuteStreamFiltersToolSearchTool(t *testing.T) { if len(tools) != 3 { t.Fatalf("tools length = %d, want 3; body=%s", len(tools), string(gotBody)) } + if gjson.GetBytes(gotBody, "input.0.content").Exists() { + t.Fatalf("input.0.content exists, want removed; body=%s", string(gotBody)) + } + if gjson.GetBytes(gotBody, "input.0.encrypted_content").Exists() { + t.Fatalf("input.0.encrypted_content exists, want removed; body=%s", string(gotBody)) + } + if got := gjson.GetBytes(gotBody, "input.0.summary.0.text").String(); got != "test" { + t.Fatalf("input.0.summary.0.text = %q, want test; body=%s", got, string(gotBody)) + } for i, tool := range tools { toolType := tool.Get("type").String() if toolType == "image_generation" {