From 8b3670b8dda5277cc16c1b4752fa6dc3b7691179 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Sun, 17 May 2026 05:22:57 +0800 Subject: [PATCH] feat(xai): support namespace tools and enhance tool normalization logic - Added `namespace` tool type support, enabling nested tools to be normalized and moved to the top level. - Refactored tool normalization logic into `normalizeXAITool` for reusability and clarity. - Updated `xai_executor` test cases to validate namespace tool handling and nested tool normalization. --- internal/runtime/executor/xai_executor.go | 74 ++++++++++++++----- .../runtime/executor/xai_executor_test.go | 40 ++++++++-- 2 files changed, 88 insertions(+), 26 deletions(-) diff --git a/internal/runtime/executor/xai_executor.go b/internal/runtime/executor/xai_executor.go index a9ca369c..3060eaf5 100644 --- a/internal/runtime/executor/xai_executor.go +++ b/internal/runtime/executor/xai_executor.go @@ -34,6 +34,7 @@ const ( xaiCustomToolType = "custom" xaiFunctionToolType = "function" xaiImageGenerationToolType = "image_generation" + xaiNamespaceToolType = "namespace" xaiToolSearchType = "tool_search" xaiWebSearchToolType = "web_search" xaiImagesGenerationsPath = "/images/generations" @@ -664,30 +665,34 @@ func normalizeXAITools(body []byte) []byte { filtered := []byte(`[]`) for _, tool := range tools.Array() { toolType := tool.Get("type").String() - if toolType == xaiToolSearchType || toolType == xaiImageGenerationToolType { + if toolType == xaiNamespaceToolType { changed = true + if namespaceTools := tool.Get("tools"); namespaceTools.IsArray() { + for _, nestedTool := range namespaceTools.Array() { + nestedRaw, nestedChanged, ok := normalizeXAITool(nestedTool) + if !ok { + return body + } + changed = changed || nestedChanged + if len(nestedRaw) == 0 { + continue + } + updated, errSet := sjson.SetRawBytes(filtered, "-1", nestedRaw) + if errSet != nil { + return body + } + filtered = updated + } + } continue } - raw := []byte(tool.Raw) - if toolType == xaiCustomToolType { - if tool.Get("name").String() == "apply_patch" { - changed = true - continue - } - updatedTool, errSet := sjson.SetBytes(raw, "type", xaiFunctionToolType) - if errSet != nil { - return body - } - raw = updatedTool - changed = true + raw, toolChanged, ok := normalizeXAITool(tool) + if !ok { + return body } - if toolType == xaiWebSearchToolType && tool.Get("external_web_access").Exists() { - updatedTool, errDel := sjson.DeleteBytes(raw, "external_web_access") - if errDel != nil { - return body - } - raw = updatedTool - changed = true + changed = changed || toolChanged + if len(raw) == 0 { + continue } updated, errSet := sjson.SetRawBytes(filtered, "-1", raw) if errSet != nil { @@ -705,6 +710,35 @@ func normalizeXAITools(body []byte) []byte { return updated } +func normalizeXAITool(tool gjson.Result) ([]byte, bool, bool) { + toolType := tool.Get("type").String() + changed := false + if toolType == xaiToolSearchType || toolType == xaiImageGenerationToolType { + return nil, true, true + } + raw := []byte(tool.Raw) + if toolType == xaiCustomToolType { + if tool.Get("name").String() == "apply_patch" { + return nil, true, true + } + updatedTool, errSet := sjson.SetBytes(raw, "type", xaiFunctionToolType) + if errSet != nil { + return nil, false, false + } + raw = updatedTool + changed = true + } + if toolType == xaiWebSearchToolType && tool.Get("external_web_access").Exists() { + updatedTool, errDel := sjson.DeleteBytes(raw, "external_web_access") + if errDel != nil { + return nil, false, false + } + raw = updatedTool + changed = true + } + return raw, changed, true +} + func normalizeXAIInputReasoningItems(body []byte) []byte { input := gjson.GetBytes(body, "input") if !input.Exists() || !input.IsArray() { diff --git a/internal/runtime/executor/xai_executor_test.go b/internal/runtime/executor/xai_executor_test.go index 751f1d15..59bdbe78 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":[{"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"]}]}`), + 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"]},{"type":"namespace","name":"codex_app","description":"Tools in the codex_app namespace.","tools":[{"type":"function","name":"automation_update"},{"type":"custom","name":"namespace_custom"},{"type":"tool_search"}]}]}`), }, cliproxyexecutor.Options{ SourceFormat: sdktranslator.FormatOpenAIResponse, Stream: false, @@ -101,9 +101,11 @@ func TestXAIExecutorExecuteShapesResponsesRequest(t *testing.T) { 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)) + if len(tools) != 5 { + t.Fatalf("tools length = %d, want 5; body=%s", len(tools), string(gotBody)) } + foundAutomationUpdate := false + foundNamespaceCustom := false for i, tool := range tools { toolType := tool.Get("type").String() if toolType == "image_generation" { @@ -115,6 +117,12 @@ func TestXAIExecutorExecuteShapesResponsesRequest(t *testing.T) { if got := tool.Get("name").String(); got == "apply_patch" { t.Fatalf("tools.%d.name = apply_patch, want removed; body=%s", i, string(gotBody)) } + switch tool.Get("name").String() { + case "automation_update": + foundAutomationUpdate = true + case "namespace_custom": + foundNamespaceCustom = true + } if toolType == "web_search" { if tool.Get("external_web_access").Exists() { t.Fatalf("tools.%d.external_web_access exists, want removed; body=%s", i, string(gotBody)) @@ -124,6 +132,12 @@ func TestXAIExecutorExecuteShapesResponsesRequest(t *testing.T) { } } } + if !foundAutomationUpdate { + t.Fatalf("namespace function tool was not moved to top-level tools; body=%s", string(gotBody)) + } + if !foundNamespaceCustom { + t.Fatalf("namespace custom tool was not moved to top-level tools; body=%s", string(gotBody)) + } for _, include := range gjson.GetBytes(gotBody, "include").Array() { if include.String() == "reasoning.encrypted_content" { t.Fatalf("xai request must not ask for encrypted reasoning content: %s", string(gotBody)) @@ -192,7 +206,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":[{"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"]}]}`), + 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"]},{"type":"namespace","name":"codex_app","description":"Tools in the codex_app namespace.","tools":[{"type":"function","name":"automation_update"},{"type":"custom","name":"namespace_custom"},{"type":"tool_search"}]}]}`), }, cliproxyexecutor.Options{ SourceFormat: sdktranslator.FormatOpenAIResponse, Stream: true, @@ -207,8 +221,8 @@ func TestXAIExecutorExecuteStreamFiltersToolSearchTool(t *testing.T) { } tools := gjson.GetBytes(gotBody, "tools").Array() - if len(tools) != 3 { - t.Fatalf("tools length = %d, want 3; body=%s", len(tools), string(gotBody)) + if len(tools) != 5 { + t.Fatalf("tools length = %d, want 5; 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)) @@ -219,6 +233,8 @@ func TestXAIExecutorExecuteStreamFiltersToolSearchTool(t *testing.T) { 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)) } + foundAutomationUpdate := false + foundNamespaceCustom := false for i, tool := range tools { toolType := tool.Get("type").String() if toolType == "image_generation" { @@ -230,6 +246,12 @@ func TestXAIExecutorExecuteStreamFiltersToolSearchTool(t *testing.T) { if got := tool.Get("name").String(); got == "apply_patch" { t.Fatalf("tools.%d.name = apply_patch, want removed; body=%s", i, string(gotBody)) } + switch tool.Get("name").String() { + case "automation_update": + foundAutomationUpdate = true + case "namespace_custom": + foundNamespaceCustom = true + } if toolType == "web_search" { if tool.Get("external_web_access").Exists() { t.Fatalf("tools.%d.external_web_access exists, want removed; body=%s", i, string(gotBody)) @@ -239,6 +261,12 @@ func TestXAIExecutorExecuteStreamFiltersToolSearchTool(t *testing.T) { } } } + if !foundAutomationUpdate { + t.Fatalf("namespace function tool was not moved to top-level tools; body=%s", string(gotBody)) + } + if !foundNamespaceCustom { + t.Fatalf("namespace custom tool was not moved to top-level tools; body=%s", string(gotBody)) + } } func TestXAIExecutorExecuteImagesUsesImagesEndpoint(t *testing.T) {