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.
This commit is contained in:
Luis Pater
2026-05-17 05:22:57 +08:00
parent 96754f5a33
commit 8b3670b8dd
2 changed files with 88 additions and 26 deletions
+54 -20
View File
@@ -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() {