package executor import ( "bytes" "encoding/json" "fmt" "net/http" "sort" "strings" "time" "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" "github.com/router-for-me/CLIProxyAPI/v7/internal/signature" "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson") // xAI executes these x_search subtools server-side but exposes their trace as// client-style tool calls. Hide the trace so Responses clients do not execute it again.type xaiInternalXSearchResponseFilter struct { enabled bool clientDeclaredTools map[xaiClientToolKey]struct{} droppedOutputIndexes map[int64]struct{} droppedItemIDs map[string]struct{}} func newXAIInternalXSearchResponseFilter(enabled bool, clientDeclaredTools map[xaiClientToolKey]struct{}) *xaiInternalXSearchResponseFilter { filter := &xaiInternalXSearchResponseFilter{ enabled: enabled, clientDeclaredTools: clientDeclaredTools, } if enabled { filter.droppedOutputIndexes = make(map[int64]struct{}) filter.droppedItemIDs = make(map[string]struct{}) } return filter} func xaiRequestHasNativeXSearch(body []byte) bool { if gjson.GetBytes(body, `tools.#(type=="x_search")`).Exists() { return true } // Multipath queries return an array of matches; an empty array still Exists(). // Check the match count instead of Exists() for additional_tools injection. return len(gjson.GetBytes(body, `input.#(type=="additional_tools")#.tools.#(type=="x_search")`).Array()) > 0} // collectXAIClientDeclaredToolKeys records client-declared function/custom tools// using the Responses post-restore identity (short name + optional namespace) and// the effective upstream tool type after normalizeXAITool. Client custom tools// are normalized to function before being sent to xAI, so keys use function for// both declaration kinds. Must run before normalizeXAITools flattens namespace wrappers.func collectXAIClientDeclaredToolKeys(body []byte) map[xaiClientToolKey]struct{} { keys := make(map[xaiClientToolKey]struct{}) collect := func(tools gjson.Result) { if !tools.Exists() || !tools.IsArray() { return } for _, tool := range tools.Array() { switch toolType := strings.TrimSpace(tool.Get("type").String()); toolType { case xaiNamespaceToolType: namespaceName := strings.TrimSpace(tool.Get("name").String()) if namespaceName == "" { continue } for _, nestedTool := range tool.Get("tools").Array() { nestedType := strings.TrimSpace(nestedTool.Get("type").String()) if nestedType != xaiFunctionToolType && nestedType != xaiCustomToolType { continue } toolName := strings.TrimSpace(nestedTool.Get("name").String()) if toolName == "" { continue } // normalizeXAITool converts custom → function before upstream send. keys[xaiClientToolKey{namespace: namespaceName, name: toolName, toolType: xaiEffectiveDeclaredToolType(nestedType)}] = struct{}{} } case xaiFunctionToolType, xaiCustomToolType: toolName := strings.TrimSpace(tool.Get("name").String()) if toolName == "" { continue } // normalizeXAITool converts custom → function before upstream send. keys[xaiClientToolKey{namespace: "", name: toolName, toolType: xaiEffectiveDeclaredToolType(toolType)}] = struct{}{} } } } collect(gjson.GetBytes(body, "tools")) input := gjson.GetBytes(body, "input") if input.Exists() && input.IsArray() { for _, item := range input.Array() { if item.Get("type").String() == "additional_tools" { collect(item.Get("tools")) } } } return keys} // xaiEffectiveDeclaredToolType returns the tool type actually sent upstream// after normalizeXAITool. Client custom tools are rewritten to function.func xaiEffectiveDeclaredToolType(toolType string) string { if strings.TrimSpace(toolType) == xaiCustomToolType { return xaiFunctionToolType } return strings.TrimSpace(toolType)} func xaiIsInternalXSearchToolName(name string) bool { switch strings.TrimSpace(name) { case "x_user_search", "x_semantic_search", "x_keyword_search", "x_thread_fetch": return true default: return false }} // xaiResponseCallDeclaredType maps a Responses output call type to the effective// upstream tool declaration kind used when matching client-declared tools.// Client custom tools are normalized to function before upstream send, so only// function_call can match a client-declared same-name tool; custom_tool_call// remains the internal X Search trace shape.func xaiResponseCallDeclaredType(itemType string) string { switch strings.TrimSpace(itemType) { case "function_call": return xaiFunctionToolType case "custom_tool_call": return xaiCustomToolType default: return "" }} // xaiIsInternalXSearchCallID reports whether call_id matches the evidenced xAI// X Search server-side trace prefix (xs_call...), as observed in Responses traffic// for native x_search subtools (see issue #4282 / PR #4284 fixtures).func xaiIsInternalXSearchCallID(callID string) bool { return strings.HasPrefix(strings.TrimSpace(callID), "xs_call")} // xaiIsInternalXSearchCall reports whether an output item is an xAI server-side// X Search subtool trace that should be hidden from Responses clients.//// Evidence from xAI Responses traffic (issue #4282 / PR #4284):// - native x_search subtools are emitted as custom_tool_call items named// x_user_search / x_semantic_search / x_keyword_search / x_thread_fetch// - those traces commonly use call_id values prefixed with "xs_call"//// Client tools that share a short name are preserved only when the response call// kind matches the effective upstream declaration type. Because normalizeXAITool// rewrites client custom → function, a client custom x_keyword_search is keyed as// function and therefore preserves function_call while still filtering genuine// internal custom_tool_call / xs_call* traces. Namespaced restored client tools// are never treated as internal.func xaiIsInternalXSearchCall(item gjson.Result, clientDeclaredTools map[xaiClientToolKey]struct{}) bool { itemType := strings.TrimSpace(item.Get("type").String()) declaredType := xaiResponseCallDeclaredType(itemType) if declaredType == "" { return false } name := strings.TrimSpace(item.Get("name").String()) if !xaiIsInternalXSearchToolName(name) { return false } namespace := strings.TrimSpace(item.Get("namespace").String()) // Namespaced calls are restored client tools, never xAI internal X Search traces. if namespace != "" { return false } // Evidenced internal call_id prefix always identifies server-side X Search traces, // even when a client tool reuses the same short name. if xaiIsInternalXSearchCallID(item.Get("call_id").String()) { return true } // Preserve only client tools whose effective upstream declaration kind matches // this call type (function_call ↔ function after custom normalization). if _, declared := clientDeclaredTools[xaiClientToolKey{namespace: namespace, name: name, toolType: declaredType}]; declared { return false } return true} func (f *xaiInternalXSearchResponseFilter) apply(eventData []byte) []byte { if f == nil || !f.enabled || len(eventData) == 0 || !gjson.ValidBytes(eventData) { return eventData } if item := gjson.GetBytes(eventData, "item"); xaiIsInternalXSearchCall(item, f.clientDeclaredTools) { f.recordDroppedItem(eventData, item) return nil } eventData = f.filterCompletedOutput(eventData) if f.referencesDroppedItem(eventData) { return nil } return f.compactOutputIndex(eventData)} func (f *xaiInternalXSearchResponseFilter) recordDroppedItem(eventData []byte, item gjson.Result) { if outputIndex := gjson.GetBytes(eventData, "output_index"); outputIndex.Exists() { f.droppedOutputIndexes[outputIndex.Int()] = struct{}{} } for _, path := range []string{"id", "call_id"} { if id := strings.TrimSpace(item.Get(path).String()); id != "" { f.droppedItemIDs[id] = struct{}{} } }} func (f *xaiInternalXSearchResponseFilter) referencesDroppedItem(eventData []byte) bool { if outputIndex := gjson.GetBytes(eventData, "output_index"); outputIndex.Exists() { if _, dropped := f.droppedOutputIndexes[outputIndex.Int()]; dropped { return true } } for _, path := range []string{"item_id", "call_id"} { id := strings.TrimSpace(gjson.GetBytes(eventData, path).String()) if _, dropped := f.droppedItemIDs[id]; id != "" && dropped { return true } } return false} func (f *xaiInternalXSearchResponseFilter) compactOutputIndex(eventData []byte) []byte { outputIndex := gjson.GetBytes(eventData, "output_index") if !outputIndex.Exists() { return eventData } original := outputIndex.Int() removedBefore := int64(0) for dropped := range f.droppedOutputIndexes { if dropped < original { removedBefore++ } } if removedBefore == 0 { return eventData } updated, errSet := sjson.SetBytes(eventData, "output_index", original-removedBefore) if errSet != nil { return eventData } return updated} func (f *xaiInternalXSearchResponseFilter) filterCompletedOutput(eventData []byte) []byte { output := gjson.GetBytes(eventData, "response.output") if !output.IsArray() { return eventData } var clientDeclaredTools map[xaiClientToolKey]struct{} if f != nil { clientDeclaredTools = f.clientDeclaredTools } items := make([]json.RawMessage, 0, len(output.Array())) changed := false for _, item := range output.Array() { if xaiIsInternalXSearchCall(item, clientDeclaredTools) { changed = true continue } items = append(items, json.RawMessage(item.Raw)) } if !changed { return eventData } rawOutput, errMarshal := json.Marshal(items) if errMarshal != nil { return eventData } updated, errSet := sjson.SetRawBytes(eventData, "response.output", rawOutput) if errSet != nil { return eventData } return updated} func normalizeXAIInputNamespaceToolCalls(body []byte) []byte { return normalizeXAIInputNamespaceToolCallsWithFold(body, xaiShouldFoldNamespaceTools(body, false))} func normalizeXAIInputNamespaceToolCallsWithFold(body []byte, shouldFold bool) []byte { if !gjson.ValidBytes(body) { return body } input := gjson.GetBytes(body, "input") if !input.Exists() || !input.IsArray() { return body } for index, item := range input.Array() { if item.Get("type").String() != "function_call" { continue } namespaceName := strings.TrimSpace(item.Get("namespace").String()) toolName := strings.TrimSpace(item.Get("name").String()) if namespaceName == "" { continue } qualifiedName := qualifyXAINamespaceToolName(namespaceName, toolName) var isFolded bool if xaiHasFunctionToolNamed(body, namespaceName) { isFolded = true } else if xaiHasFunctionToolNamed(body, qualifiedName) { isFolded = false } else { isFolded = shouldFold } if isFolded { namePath := fmt.Sprintf("input.%d.name", index) namespacePath := fmt.Sprintf("input.%d.namespace", index) argsPath := fmt.Sprintf("input.%d.arguments", index) dispatcherArgs := map[string]any{ "name": toolName, } if rawArgs := item.Get("arguments").String(); rawArgs != "" { if gjson.Valid(rawArgs) { dispatcherArgs["arguments"] = json.RawMessage(rawArgs) } else { dispatcherArgs["arguments"] = rawArgs } } encodedArgs, errMarshal := json.Marshal(dispatcherArgs) if errMarshal != nil { continue } updated, errSet := sjson.SetBytes(body, namePath, namespaceName) if errSet != nil { continue } updated, errSet = sjson.SetBytes(updated, argsPath, string(encodedArgs)) if errSet != nil { continue } updated, errDelete := sjson.DeleteBytes(updated, namespacePath) if errDelete != nil { continue } body = updated continue } if qualifiedName == "" { continue } namePath := fmt.Sprintf("input.%d.name", index) namespacePath := fmt.Sprintf("input.%d.namespace", index) updated, errSet := sjson.SetBytes(body, namePath, qualifiedName) if errSet != nil { continue } updated, errDelete := sjson.DeleteBytes(updated, namespacePath) if errDelete != nil { continue } body = updated } return body} type xaiNamespaceRestorer struct { refs map[string]xaiNamespaceToolRef dispatcherItemIDs map[string]string} func newXAINamespaceRestorer(refs map[string]xaiNamespaceToolRef) *xaiNamespaceRestorer { return &xaiNamespaceRestorer{ refs: refs, dispatcherItemIDs: make(map[string]string), }} func (r *xaiNamespaceRestorer) restore(data []byte) []byte { if r == nil || len(r.refs) == 0 || len(data) == 0 || !gjson.ValidBytes(data) { return data } eventType := gjson.GetBytes(data, "type").String() switch eventType { case "response.output_item.added": item := gjson.GetBytes(data, "item") if item.Get("type").String() == "function_call" { name := strings.TrimSpace(item.Get("name").String()) itemID := strings.TrimSpace(item.Get("id").String()) if ref, ok := r.refs[name]; ok && ref.isDispatcher { if itemID != "" { r.dispatcherItemIDs[itemID] = ref.namespace } data, _ = sjson.SetBytes(data, "item.namespace", ref.namespace) } } return data case "response.function_call_arguments.done": itemID := strings.TrimSpace(gjson.GetBytes(data, "item_id").String()) if namespaceName, isDisp := r.dispatcherItemIDs[itemID]; isDisp { rawArgs := gjson.GetBytes(data, "arguments").String() if _, childArgs, ok := unwrapXAIDispatcherArguments(rawArgs, namespaceName, r.refs); ok { updated, errSet := sjson.SetBytes(data, "arguments", string(childArgs)) if errSet == nil { data = updated } } } return data default: data = r.restoreAtPath(data, "item") output := gjson.GetBytes(data, "response.output") if output.Exists() && output.IsArray() { for index := range output.Array() { data = r.restoreAtPath(data, fmt.Sprintf("response.output.%d", index)) } } return data }} func (r *xaiNamespaceRestorer) restoreAtPath(data []byte, path string) []byte { if gjson.GetBytes(data, path+".type").String() != "function_call" { return data } qualifiedName := strings.TrimSpace(gjson.GetBytes(data, path+".name").String()) ref, ok := r.refs[qualifiedName] if !ok { return data } if ref.isDispatcher { rawArgs := gjson.GetBytes(data, path+".arguments").String() childName, childArgs, unwrapped := unwrapXAIDispatcherArguments(rawArgs, ref.namespace, r.refs) if !unwrapped && childName == "" { childName = ref.name } updated, errSet := sjson.SetBytes(data, path+".namespace", ref.namespace) if errSet != nil { return data } if childName != "" { if updatedName, errSetName := sjson.SetBytes(updated, path+".name", childName); errSetName == nil { updated = updatedName } } if len(childArgs) > 0 { if updatedArgs, errSetArgs := sjson.SetBytes(updated, path+".arguments", string(childArgs)); errSetArgs == nil { updated = updatedArgs } } return updated } updated, errSet := sjson.SetBytes(data, path+".name", ref.name) if errSet != nil { return data } updated, errSet = sjson.SetBytes(updated, path+".namespace", ref.namespace) if errSet != nil { return data } return updated} func unwrapXAIDispatcherArguments(rawArgs string, namespaceName string, refs map[string]xaiNamespaceToolRef) (string, []byte, bool) { if !gjson.Valid(rawArgs) { return "", nil, false } argsParsed := gjson.Parse(rawArgs) nameField := argsParsed.Get("name") if !nameField.Exists() || nameField.Type != gjson.String { return "", nil, false } childName := strings.TrimSpace(nameField.String()) if childName == "" { return "", nil, false } if namespaceName != "" { qualified := qualifyXAINamespaceToolName(namespaceName, childName) if ref, exists := refs[qualified]; exists && ref.isDispatcher { return "", nil, false } } else { isChildOfDispatcher := false for _, ref := range refs { if ref.isDispatcher && (ref.name == childName || ref.namespace == childName) { isChildOfDispatcher = true break } } if !isChildOfDispatcher && !argsParsed.Get("arguments").Exists() { return "", nil, false } } var childArgs []byte if argsField := argsParsed.Get("arguments"); argsField.Exists() {Lines 1–500 of 1100. Load more
package executor import ( "bytes" "encoding/json" "fmt" "net/http" "sort" "strings" "time" "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" "github.com/router-for-me/CLIProxyAPI/v7/internal/signature" "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) // xAI executes these x_search subtools server-side but exposes their trace as // client-style tool calls. Hide the trace so Responses clients do not execute it again. type xaiInternalXSearchResponseFilter struct { enabled bool clientDeclaredTools map[xaiClientToolKey]struct{} droppedOutputIndexes map[int64]struct{} droppedItemIDs map[string]struct{} } func newXAIInternalXSearchResponseFilter(enabled bool, clientDeclaredTools map[xaiClientToolKey]struct{}) *xaiInternalXSearchResponseFilter { filter := &xaiInternalXSearchResponseFilter{ enabled: enabled, clientDeclaredTools: clientDeclaredTools, } if enabled { filter.droppedOutputIndexes = make(map[int64]struct{}) filter.droppedItemIDs = make(map[string]struct{}) } return filter } func xaiRequestHasNativeXSearch(body []byte) bool { if gjson.GetBytes(body, `tools.#(type=="x_search")`).Exists() { return true } // Multipath queries return an array of matches; an empty array still Exists(). // Check the match count instead of Exists() for additional_tools injection. return len(gjson.GetBytes(body, `input.#(type=="additional_tools")#.tools.#(type=="x_search")`).Array()) > 0 } // collectXAIClientDeclaredToolKeys records client-declared function/custom tools // using the Responses post-restore identity (short name + optional namespace) and // the effective upstream tool type after normalizeXAITool. Client custom tools // are normalized to function before being sent to xAI, so keys use function for // both declaration kinds. Must run before normalizeXAITools flattens namespace wrappers. func collectXAIClientDeclaredToolKeys(body []byte) map[xaiClientToolKey]struct{} { keys := make(map[xaiClientToolKey]struct{}) collect := func(tools gjson.Result) { if !tools.Exists() || !tools.IsArray() { return } for _, tool := range tools.Array() { switch toolType := strings.TrimSpace(tool.Get("type").String()); toolType { case xaiNamespaceToolType: namespaceName := strings.TrimSpace(tool.Get("name").String()) if namespaceName == "" { continue } for _, nestedTool := range tool.Get("tools").Array() { nestedType := strings.TrimSpace(nestedTool.Get("type").String()) if nestedType != xaiFunctionToolType && nestedType != xaiCustomToolType { continue } toolName := strings.TrimSpace(nestedTool.Get("name").String()) if toolName == "" { continue } // normalizeXAITool converts custom → function before upstream send. keys[xaiClientToolKey{namespace: namespaceName, name: toolName, toolType: xaiEffectiveDeclaredToolType(nestedType)}] = struct{}{} } case xaiFunctionToolType, xaiCustomToolType: toolName := strings.TrimSpace(tool.Get("name").String()) if toolName == "" { continue } // normalizeXAITool converts custom → function before upstream send. keys[xaiClientToolKey{namespace: "", name: toolName, toolType: xaiEffectiveDeclaredToolType(toolType)}] = struct{}{} } } } collect(gjson.GetBytes(body, "tools")) input := gjson.GetBytes(body, "input") if input.Exists() && input.IsArray() { for _, item := range input.Array() { if item.Get("type").String() == "additional_tools" { collect(item.Get("tools")) } } } return keys } // xaiEffectiveDeclaredToolType returns the tool type actually sent upstream // after normalizeXAITool. Client custom tools are rewritten to function. func xaiEffectiveDeclaredToolType(toolType string) string { if strings.TrimSpace(toolType) == xaiCustomToolType { return xaiFunctionToolType } return strings.TrimSpace(toolType) } func xaiIsInternalXSearchToolName(name string) bool { switch strings.TrimSpace(name) { case "x_user_search", "x_semantic_search", "x_keyword_search", "x_thread_fetch": return true default: return false } } // xaiResponseCallDeclaredType maps a Responses output call type to the effective // upstream tool declaration kind used when matching client-declared tools. // Client custom tools are normalized to function before upstream send, so only // function_call can match a client-declared same-name tool; custom_tool_call // remains the internal X Search trace shape. func xaiResponseCallDeclaredType(itemType string) string { switch strings.TrimSpace(itemType) { case "function_call": return xaiFunctionToolType case "custom_tool_call": return xaiCustomToolType default: return "" } } // xaiIsInternalXSearchCallID reports whether call_id matches the evidenced xAI // X Search server-side trace prefix (xs_call...), as observed in Responses traffic // for native x_search subtools (see issue #4282 / PR #4284 fixtures). func xaiIsInternalXSearchCallID(callID string) bool { return strings.HasPrefix(strings.TrimSpace(callID), "xs_call") } // xaiIsInternalXSearchCall reports whether an output item is an xAI server-side // X Search subtool trace that should be hidden from Responses clients. // // Evidence from xAI Responses traffic (issue #4282 / PR #4284): // - native x_search subtools are emitted as custom_tool_call items named // x_user_search / x_semantic_search / x_keyword_search / x_thread_fetch // - those traces commonly use call_id values prefixed with "xs_call" // // Client tools that share a short name are preserved only when the response call // kind matches the effective upstream declaration type. Because normalizeXAITool // rewrites client custom → function, a client custom x_keyword_search is keyed as // function and therefore preserves function_call while still filtering genuine // internal custom_tool_call / xs_call* traces. Namespaced restored client tools // are never treated as internal. func xaiIsInternalXSearchCall(item gjson.Result, clientDeclaredTools map[xaiClientToolKey]struct{}) bool { itemType := strings.TrimSpace(item.Get("type").String()) declaredType := xaiResponseCallDeclaredType(itemType) if declaredType == "" { return false } name := strings.TrimSpace(item.Get("name").String()) if !xaiIsInternalXSearchToolName(name) { return false } namespace := strings.TrimSpace(item.Get("namespace").String()) // Namespaced calls are restored client tools, never xAI internal X Search traces. if namespace != "" { return false } // Evidenced internal call_id prefix always identifies server-side X Search traces, // even when a client tool reuses the same short name. if xaiIsInternalXSearchCallID(item.Get("call_id").String()) { return true } // Preserve only client tools whose effective upstream declaration kind matches // this call type (function_call ↔ function after custom normalization). if _, declared := clientDeclaredTools[xaiClientToolKey{namespace: namespace, name: name, toolType: declaredType}]; declared { return false } return true } func (f *xaiInternalXSearchResponseFilter) apply(eventData []byte) []byte { if f == nil || !f.enabled || len(eventData) == 0 || !gjson.ValidBytes(eventData) { return eventData } if item := gjson.GetBytes(eventData, "item"); xaiIsInternalXSearchCall(item, f.clientDeclaredTools) { f.recordDroppedItem(eventData, item) return nil } eventData = f.filterCompletedOutput(eventData) if f.referencesDroppedItem(eventData) { return nil } return f.compactOutputIndex(eventData) } func (f *xaiInternalXSearchResponseFilter) recordDroppedItem(eventData []byte, item gjson.Result) { if outputIndex := gjson.GetBytes(eventData, "output_index"); outputIndex.Exists() { f.droppedOutputIndexes[outputIndex.Int()] = struct{}{} } for _, path := range []string{"id", "call_id"} { if id := strings.TrimSpace(item.Get(path).String()); id != "" { f.droppedItemIDs[id] = struct{}{} } } } func (f *xaiInternalXSearchResponseFilter) referencesDroppedItem(eventData []byte) bool { if outputIndex := gjson.GetBytes(eventData, "output_index"); outputIndex.Exists() { if _, dropped := f.droppedOutputIndexes[outputIndex.Int()]; dropped { return true } } for _, path := range []string{"item_id", "call_id"} { id := strings.TrimSpace(gjson.GetBytes(eventData, path).String()) if _, dropped := f.droppedItemIDs[id]; id != "" && dropped { return true } } return false } func (f *xaiInternalXSearchResponseFilter) compactOutputIndex(eventData []byte) []byte { outputIndex := gjson.GetBytes(eventData, "output_index") if !outputIndex.Exists() { return eventData } original := outputIndex.Int() removedBefore := int64(0) for dropped := range f.droppedOutputIndexes { if dropped < original { removedBefore++ } } if removedBefore == 0 { return eventData } updated, errSet := sjson.SetBytes(eventData, "output_index", original-removedBefore) if errSet != nil { return eventData } return updated } func (f *xaiInternalXSearchResponseFilter) filterCompletedOutput(eventData []byte) []byte { output := gjson.GetBytes(eventData, "response.output") if !output.IsArray() { return eventData } var clientDeclaredTools map[xaiClientToolKey]struct{} if f != nil { clientDeclaredTools = f.clientDeclaredTools } items := make([]json.RawMessage, 0, len(output.Array())) changed := false for _, item := range output.Array() { if xaiIsInternalXSearchCall(item, clientDeclaredTools) { changed = true continue } items = append(items, json.RawMessage(item.Raw)) } if !changed { return eventData } rawOutput, errMarshal := json.Marshal(items) if errMarshal != nil { return eventData } updated, errSet := sjson.SetRawBytes(eventData, "response.output", rawOutput) if errSet != nil { return eventData } return updated } func normalizeXAIInputNamespaceToolCalls(body []byte) []byte { return normalizeXAIInputNamespaceToolCallsWithFold(body, xaiShouldFoldNamespaceTools(body, false)) } func normalizeXAIInputNamespaceToolCallsWithFold(body []byte, shouldFold bool) []byte { if !gjson.ValidBytes(body) { return body } input := gjson.GetBytes(body, "input") if !input.Exists() || !input.IsArray() { return body } for index, item := range input.Array() { if item.Get("type").String() != "function_call" { continue } namespaceName := strings.TrimSpace(item.Get("namespace").String()) toolName := strings.TrimSpace(item.Get("name").String()) if namespaceName == "" { continue } qualifiedName := qualifyXAINamespaceToolName(namespaceName, toolName) var isFolded bool if xaiHasFunctionToolNamed(body, namespaceName) { isFolded = true } else if xaiHasFunctionToolNamed(body, qualifiedName) { isFolded = false } else { isFolded = shouldFold } if isFolded { namePath := fmt.Sprintf("input.%d.name", index) namespacePath := fmt.Sprintf("input.%d.namespace", index) argsPath := fmt.Sprintf("input.%d.arguments", index) dispatcherArgs := map[string]any{ "name": toolName, } if rawArgs := item.Get("arguments").String(); rawArgs != "" { if gjson.Valid(rawArgs) { dispatcherArgs["arguments"] = json.RawMessage(rawArgs) } else { dispatcherArgs["arguments"] = rawArgs } } encodedArgs, errMarshal := json.Marshal(dispatcherArgs) if errMarshal != nil { continue } updated, errSet := sjson.SetBytes(body, namePath, namespaceName) if errSet != nil { continue } updated, errSet = sjson.SetBytes(updated, argsPath, string(encodedArgs)) if errSet != nil { continue } updated, errDelete := sjson.DeleteBytes(updated, namespacePath) if errDelete != nil { continue } body = updated continue } if qualifiedName == "" { continue } namePath := fmt.Sprintf("input.%d.name", index) namespacePath := fmt.Sprintf("input.%d.namespace", index) updated, errSet := sjson.SetBytes(body, namePath, qualifiedName) if errSet != nil { continue } updated, errDelete := sjson.DeleteBytes(updated, namespacePath) if errDelete != nil { continue } body = updated } return body } type xaiNamespaceRestorer struct { refs map[string]xaiNamespaceToolRef dispatcherItemIDs map[string]string } func newXAINamespaceRestorer(refs map[string]xaiNamespaceToolRef) *xaiNamespaceRestorer { return &xaiNamespaceRestorer{ refs: refs, dispatcherItemIDs: make(map[string]string), } } func (r *xaiNamespaceRestorer) restore(data []byte) []byte { if r == nil || len(r.refs) == 0 || len(data) == 0 || !gjson.ValidBytes(data) { return data } eventType := gjson.GetBytes(data, "type").String() switch eventType { case "response.output_item.added": item := gjson.GetBytes(data, "item") if item.Get("type").String() == "function_call" { name := strings.TrimSpace(item.Get("name").String()) itemID := strings.TrimSpace(item.Get("id").String()) if ref, ok := r.refs[name]; ok && ref.isDispatcher { if itemID != "" { r.dispatcherItemIDs[itemID] = ref.namespace } data, _ = sjson.SetBytes(data, "item.namespace", ref.namespace) } } return data case "response.function_call_arguments.done": itemID := strings.TrimSpace(gjson.GetBytes(data, "item_id").String()) if namespaceName, isDisp := r.dispatcherItemIDs[itemID]; isDisp { rawArgs := gjson.GetBytes(data, "arguments").String() if _, childArgs, ok := unwrapXAIDispatcherArguments(rawArgs, namespaceName, r.refs); ok { updated, errSet := sjson.SetBytes(data, "arguments", string(childArgs)) if errSet == nil { data = updated } } } return data default: data = r.restoreAtPath(data, "item") output := gjson.GetBytes(data, "response.output") if output.Exists() && output.IsArray() { for index := range output.Array() { data = r.restoreAtPath(data, fmt.Sprintf("response.output.%d", index)) } } return data } } func (r *xaiNamespaceRestorer) restoreAtPath(data []byte, path string) []byte { if gjson.GetBytes(data, path+".type").String() != "function_call" { return data } qualifiedName := strings.TrimSpace(gjson.GetBytes(data, path+".name").String()) ref, ok := r.refs[qualifiedName] if !ok { return data } if ref.isDispatcher { rawArgs := gjson.GetBytes(data, path+".arguments").String() childName, childArgs, unwrapped := unwrapXAIDispatcherArguments(rawArgs, ref.namespace, r.refs) if !unwrapped && childName == "" { childName = ref.name } updated, errSet := sjson.SetBytes(data, path+".namespace", ref.namespace) if errSet != nil { return data } if childName != "" { if updatedName, errSetName := sjson.SetBytes(updated, path+".name", childName); errSetName == nil { updated = updatedName } } if len(childArgs) > 0 { if updatedArgs, errSetArgs := sjson.SetBytes(updated, path+".arguments", string(childArgs)); errSetArgs == nil { updated = updatedArgs } } return updated } updated, errSet := sjson.SetBytes(data, path+".name", ref.name) if errSet != nil { return data } updated, errSet = sjson.SetBytes(updated, path+".namespace", ref.namespace) if errSet != nil { return data } return updated } func unwrapXAIDispatcherArguments(rawArgs string, namespaceName string, refs map[string]xaiNamespaceToolRef) (string, []byte, bool) { if !gjson.Valid(rawArgs) { return "", nil, false } argsParsed := gjson.Parse(rawArgs) nameField := argsParsed.Get("name") if !nameField.Exists() || nameField.Type != gjson.String { return "", nil, false } childName := strings.TrimSpace(nameField.String()) if childName == "" { return "", nil, false } if namespaceName != "" { qualified := qualifyXAINamespaceToolName(namespaceName, childName) if ref, exists := refs[qualified]; exists && ref.isDispatcher { return "", nil, false } } else { isChildOfDispatcher := false for _, ref := range refs { if ref.isDispatcher && (ref.name == childName || ref.namespace == childName) { isChildOfDispatcher = true break } } if !isChildOfDispatcher && !argsParsed.Get("arguments").Exists() { return "", nil, false } } var childArgs []byte if argsField := argsParsed.Get("arguments"); argsField.Exists() {