Skip to content

Commit dc83985

Browse files
Copilotpelikhan
andcommitted
Extract tools merger logic to separate file (Phase 3)
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
1 parent 7ae99d2 commit dc83985

2 files changed

Lines changed: 253 additions & 245 deletions

File tree

pkg/parser/frontmatter.go

Lines changed: 0 additions & 245 deletions
Original file line numberDiff line numberDiff line change
@@ -849,248 +849,3 @@ func processIncludesForField(content, baseDir string, extractFunc func(string) (
849849

850850
return results, result.String(), nil
851851
}
852-
853-
// mergeToolsFromJSON merges multiple JSON tool objects from content
854-
func mergeToolsFromJSON(content string) (string, error) {
855-
// Clean up the content first
856-
content = strings.TrimSpace(content)
857-
858-
// Try to parse as a single JSON object first
859-
var singleObj map[string]any
860-
if err := json.Unmarshal([]byte(content), &singleObj); err == nil {
861-
if len(singleObj) > 0 {
862-
result, err := json.Marshal(singleObj)
863-
if err != nil {
864-
return "{}", err
865-
}
866-
return string(result), nil
867-
}
868-
}
869-
870-
// Find all JSON objects in the content (line by line)
871-
var jsonObjects []map[string]any
872-
873-
scanner := bufio.NewScanner(strings.NewReader(content))
874-
for scanner.Scan() {
875-
line := strings.TrimSpace(scanner.Text())
876-
if line == "" || line == "{}" {
877-
continue
878-
}
879-
880-
var toolsObj map[string]any
881-
if err := json.Unmarshal([]byte(line), &toolsObj); err == nil {
882-
if len(toolsObj) > 0 { // Only add non-empty objects
883-
jsonObjects = append(jsonObjects, toolsObj)
884-
}
885-
}
886-
}
887-
888-
// If no valid objects found, return empty
889-
if len(jsonObjects) == 0 {
890-
return "{}", nil
891-
}
892-
893-
// Merge all objects
894-
merged := make(map[string]any)
895-
for _, obj := range jsonObjects {
896-
var err error
897-
merged, err = MergeTools(merged, obj)
898-
if err != nil {
899-
return "{}", err
900-
}
901-
}
902-
903-
// Convert back to JSON
904-
result, err := json.Marshal(merged)
905-
if err != nil {
906-
return "{}", err
907-
}
908-
909-
return string(result), nil
910-
}
911-
912-
// MergeTools merges two neutral tool configurations.
913-
// Only supports merging arrays and maps for neutral tools (bash, web-fetch, web-search, edit, mcp-*).
914-
// Removes all legacy Claude tool merging logic.
915-
func MergeTools(base, additional map[string]any) (map[string]any, error) {
916-
result := make(map[string]any)
917-
918-
// Copy base
919-
for k, v := range base {
920-
result[k] = v
921-
}
922-
923-
// Merge additional
924-
for key, newValue := range additional {
925-
if existingValue, exists := result[key]; exists {
926-
// Both have the same key, merge them
927-
928-
// If both are arrays, merge and deduplicate
929-
_, existingIsArray := existingValue.([]any)
930-
_, newIsArray := newValue.([]any)
931-
if existingIsArray && newIsArray {
932-
merged := mergeAllowedArrays(existingValue, newValue)
933-
result[key] = merged
934-
continue
935-
}
936-
937-
// If both are maps, check for special merging cases
938-
existingMap, existingIsMap := existingValue.(map[string]any)
939-
newMap, newIsMap := newValue.(map[string]any)
940-
if existingIsMap && newIsMap {
941-
// Check if this is an MCP tool (has MCP-compatible type)
942-
var existingType, newType string
943-
if existingMcp, hasMcp := existingMap["mcp"]; hasMcp {
944-
if mcpMap, ok := existingMcp.(map[string]any); ok {
945-
existingType, _ = mcpMap["type"].(string)
946-
}
947-
}
948-
if newMcp, hasMcp := newMap["mcp"]; hasMcp {
949-
if mcpMap, ok := newMcp.(map[string]any); ok {
950-
newType, _ = mcpMap["type"].(string)
951-
}
952-
}
953-
954-
if isExistingMCP := IsMCPType(existingType); isExistingMCP {
955-
if isNewMCP := IsMCPType(newType); isNewMCP {
956-
// Both are MCP tools, check for conflicts
957-
mergedMap, err := mergeMCPTools(existingMap, newMap)
958-
if err != nil {
959-
return nil, fmt.Errorf("MCP tool conflict for '%s': %v", key, err)
960-
}
961-
result[key] = mergedMap
962-
continue
963-
}
964-
}
965-
966-
// Both are maps, check for 'allowed' arrays to merge
967-
if existingAllowed, hasExistingAllowed := existingMap["allowed"]; hasExistingAllowed {
968-
if newAllowed, hasNewAllowed := newMap["allowed"]; hasNewAllowed {
969-
// Merge allowed arrays
970-
merged := mergeAllowedArrays(existingAllowed, newAllowed)
971-
mergedMap := make(map[string]any)
972-
for k, v := range existingMap {
973-
mergedMap[k] = v
974-
}
975-
for k, v := range newMap {
976-
mergedMap[k] = v
977-
}
978-
mergedMap["allowed"] = merged
979-
result[key] = mergedMap
980-
continue
981-
}
982-
}
983-
984-
// No 'allowed' arrays to merge, recursively merge the maps
985-
recursiveMerged, err := MergeTools(existingMap, newMap)
986-
if err != nil {
987-
return nil, err
988-
}
989-
result[key] = recursiveMerged
990-
} else {
991-
// Not both same type, overwrite with new value
992-
result[key] = newValue
993-
}
994-
} else {
995-
// New key, just add it
996-
result[key] = newValue
997-
}
998-
}
999-
1000-
return result, nil
1001-
}
1002-
1003-
// mergeAllowedArrays merges two allowed arrays and removes duplicates
1004-
func mergeAllowedArrays(existing, new any) []any {
1005-
var result []any
1006-
seen := make(map[string]bool)
1007-
1008-
// Add existing items
1009-
if existingSlice, ok := existing.([]any); ok {
1010-
for _, item := range existingSlice {
1011-
if str, ok := item.(string); ok {
1012-
if !seen[str] {
1013-
result = append(result, str)
1014-
seen[str] = true
1015-
}
1016-
}
1017-
}
1018-
}
1019-
1020-
// Add new items
1021-
if newSlice, ok := new.([]any); ok {
1022-
for _, item := range newSlice {
1023-
if str, ok := item.(string); ok {
1024-
if !seen[str] {
1025-
result = append(result, str)
1026-
seen[str] = true
1027-
}
1028-
}
1029-
}
1030-
}
1031-
1032-
return result
1033-
}
1034-
1035-
// mergeMCPTools merges two MCP tool configurations, detecting conflicts except for 'allowed' arrays
1036-
func mergeMCPTools(existing, new map[string]any) (map[string]any, error) {
1037-
result := make(map[string]any)
1038-
1039-
// Copy existing properties
1040-
for k, v := range existing {
1041-
result[k] = v
1042-
}
1043-
1044-
// Merge new properties, checking for conflicts
1045-
for key, newValue := range new {
1046-
if existingValue, exists := result[key]; exists {
1047-
if key == "allowed" {
1048-
// Special handling for allowed arrays - merge them
1049-
if existingArray, ok := existingValue.([]any); ok {
1050-
if newArray, ok := newValue.([]any); ok {
1051-
result[key] = mergeAllowedArrays(existingArray, newArray)
1052-
continue
1053-
}
1054-
}
1055-
// If not arrays, fall through to conflict check
1056-
} else if key == "mcp" {
1057-
// Special handling for mcp sub-objects - merge them recursively
1058-
if existingMcp, ok := existingValue.(map[string]any); ok {
1059-
if newMcp, ok := newValue.(map[string]any); ok {
1060-
mergedMcp, err := mergeMCPTools(existingMcp, newMcp)
1061-
if err != nil {
1062-
return nil, fmt.Errorf("MCP config conflict: %v", err)
1063-
}
1064-
result[key] = mergedMcp
1065-
continue
1066-
}
1067-
}
1068-
// If not both maps, fall through to conflict check
1069-
}
1070-
1071-
// Check for conflicts (values must be equal)
1072-
if !areEqual(existingValue, newValue) {
1073-
return nil, fmt.Errorf("conflicting values for '%s': existing=%v, new=%v", key, existingValue, newValue)
1074-
}
1075-
// Values are equal, keep existing
1076-
} else {
1077-
// New property, add it
1078-
result[key] = newValue
1079-
}
1080-
}
1081-
1082-
return result, nil
1083-
}
1084-
1085-
// areEqual compares two values for equality, handling different types appropriately
1086-
func areEqual(a, b any) bool {
1087-
// Convert to JSON for comparison to handle different types consistently
1088-
aJSON, aErr := json.Marshal(a)
1089-
bJSON, bErr := json.Marshal(b)
1090-
1091-
if aErr != nil || bErr != nil {
1092-
return false
1093-
}
1094-
1095-
return string(aJSON) == string(bJSON)
1096-
}

0 commit comments

Comments
 (0)