1100 lines · 35.0 KB · Go
RawDownload
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.