Class: Clacky::Utils::ArgumentsParser
- Inherits:
-
Object
- Object
- Clacky::Utils::ArgumentsParser
- Defined in:
- lib/clacky/utils/arguments_parser.rb
Class Method Summary collapse
- .build_error_message(call, tool, original_error) ⇒ Object
-
.extract_provided_params(json_str) ⇒ Object
Extract parameter names from incomplete JSON.
-
.format_tool_definition(tool) ⇒ Object
Format tool definition (concise version).
-
.parse_and_validate(call, tool_registry) ⇒ Object
Parse and validate tool call arguments with JSON repair capability.
-
.raise_helpful_error(call, tool_registry, original_error) ⇒ Object
Generate error message with tool definition.
-
.repair_json(json_str) ⇒ Object
Simple JSON repair: complete brackets and quotes, and remove XML contamination.
-
.validate_required_params(call, args, tool_registry) ⇒ Object
Validate required parameters and filter unknown parameters.
Class Method Details
.build_error_message(call, tool, original_error) ⇒ Object
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/clacky/utils/arguments_parser.rb', line 119 def self.(call, tool, original_error) # Extract tool information required_params = tool.parameters&.dig(:required) || [] # Try to parse provided parameters from incomplete JSON provided_params = extract_provided_params(call[:arguments]) # Build clear error message msg = [] msg << "Failed to parse arguments for tool '#{call[:name]}'." msg << "" msg << "Error: #{original_error.}" msg << "" if provided_params.any? msg << "Provided parameters: #{provided_params.join(', ')}" else msg << "No valid parameters could be extracted." end msg << "Required parameters: #{required_params.join(', ')}" msg << "" msg << "Tool definition:" msg << format_tool_definition(tool) msg << "" msg << "Suggestions:" msg << "- If the parameter value is too large (e.g., large file content), consider breaking it into smaller operations" msg << "- Ensure all required parameters are provided" msg << "- Simplify complex parameter values" msg.join("\n") end |
.extract_provided_params(json_str) ⇒ Object
Extract parameter names from incomplete JSON
153 154 155 156 |
# File 'lib/clacky/utils/arguments_parser.rb', line 153 def self.extract_provided_params(json_str) # Simple extraction: find all "key": patterns json_str.scan(/"(\w+)"\s*:/).flatten.uniq end |
.format_tool_definition(tool) ⇒ Object
Format tool definition (concise version)
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/clacky/utils/arguments_parser.rb', line 159 def self.format_tool_definition(tool) lines = [] lines << " Name: #{tool.name}" lines << " Description: #{tool.description}" if tool.parameters[:properties] lines << " Parameters:" tool.parameters[:properties].each do |param, spec| required_mark = tool.parameters[:required]&.include?(param.to_s) ? " (required)" : "" lines << " - #{param}#{required_mark}: #{spec[:description]}" end end lines.join("\n") end |
.parse_and_validate(call, tool_registry) ⇒ Object
Parse and validate tool call arguments with JSON repair capability
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/clacky/utils/arguments_parser.rb', line 9 def self.parse_and_validate(call, tool_registry) # 1. Try standard parsing begin args = JSON.parse(call[:arguments], symbolize_names: true) # Check if any key contains XML tags (< or >) indicating contamination # Even though JSON.parse succeeded, the keys might be malformed has_xml_contamination = args.keys.any? { |k| k.to_s.include?('<') || k.to_s.include?('>') } if has_xml_contamination # Force repair even though JSON.parse succeeded raise JSON::ParserError.new("Keys contain XML contamination") end return validate_required_params(call, args, tool_registry) rescue JSON::ParserError => e # Continue to repair end # 2. Try simple repair repaired = repair_json(call[:arguments]) begin args = JSON.parse(repaired, symbolize_names: true) return validate_required_params(call, args, tool_registry) rescue JSON::ParserError, MissingRequiredParamsError => e # 3. Repair failed or missing params, return helpful error raise_helpful_error(call, tool_registry, e) end end |
.raise_helpful_error(call, tool_registry, original_error) ⇒ Object
Generate error message with tool definition
113 114 115 116 117 |
# File 'lib/clacky/utils/arguments_parser.rb', line 113 def self.raise_helpful_error(call, tool_registry, original_error) tool = tool_registry.get(call[:name]) error_msg = (call, tool, original_error) raise StandardError, error_msg end |
.repair_json(json_str) ⇒ Object
Simple JSON repair: complete brackets and quotes, and remove XML contamination
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/clacky/utils/arguments_parser.rb', line 42 def self.repair_json(json_str) result = json_str.strip # Step 0: Convert literal \n (backslash+n) to real newlines result = result.gsub(/\\n/, "\n") # Step 0.5: Unescape quotes in JSON keys and values (\" -> ") # This handles cases like {"end_line\":550 or name=\"path\" result = result.gsub(/\\"/, '"') # Step 1: Remove XML-style parameter tags that Claude might mix in # Pattern 1: </parameter> closing tags - remove completely result = result.gsub(/<\/parameter>/, '') # Pattern 2: <parameter name="key"> or <parameter name="key": opening tags -> convert to JSON key # Example: \n<parameter name="end_line"> 330 -> , "end_line": 330 # Also handles: \n<parameter name="end_line": 330 -> , "end_line": 330 # result = result.gsub(/<parameter\s+name="([^"\\]+)":\s*/) { |match| ", \"#{$1}\": " } # result = result.gsub(/<parameter\s+name="([^"\\]+)">/) { |match| ", \"#{$1}\":" } result = result.gsub(/<parameter\s+name=\\?"([^"\\]+)\\?"[>:]?\s*/) { |match| ", \"#{$1}\": " } # Pattern 3: Remove any remaining XML-like tags result = result.gsub(/<[^>]+>/, '') # Step 2: Clean up newlines with commas # Example: 315\n, "end_line" -> 315, "end_line" result = result.gsub(/\n\s*,/, ',') result = result.gsub(/\n,/, ',') result = result.gsub(/,\s*\n/, ',') # Step 3: Clean up formatting issues # Remove multiple consecutive commas result = result.gsub(/,+/, ',') # Remove trailing commas before closing braces/brackets result = result.gsub(/,\s*}/, '}') result = result.gsub(/,\s*\]/, ']') # Remove leading commas after opening braces/brackets result = result.gsub(/\{\s*,/, '{') result = result.gsub(/\[\s*,/, '[') # Step 4: Complete unclosed strings result += '"' if result.count('"').odd? # Step 5: Complete unclosed braces depth = 0 result.each_char { |c| depth += 1 if c == '{'; depth -= 1 if c == '}' } result += '}' * depth if depth > 0 result end |
.validate_required_params(call, args, tool_registry) ⇒ Object
Validate required parameters and filter unknown parameters
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/clacky/utils/arguments_parser.rb', line 92 def self.validate_required_params(call, args, tool_registry) tool = tool_registry.get(call[:name]) required = tool.parameters&.dig(:required) || [] properties = tool.parameters&.dig(:properties) || {} missing = required.reject { |param| args.key?(param.to_sym) || args.key?(param.to_s) } if missing.any? raise MissingRequiredParamsError.new(call[:name], missing, args.keys) end # Filter out unknown parameters to prevent errors when LLM sends extra arguments known_params = properties.keys.map(&:to_sym) + properties.keys.map(&:to_s) filtered_args = args.select { |key, _| known_params.include?(key) } filtered_args end |