Commit 958d50f

mo khan <mo@mokhan.ca>
2025-09-01 18:14:49
feat: add web fetch tool
1 parent a954505
lib/elelem/toolbox/web.rb
@@ -0,0 +1,143 @@
+# frozen_string_literal: true
+
+require "net/http"
+require "uri"
+
+module Elelem
+  module Toolbox
+    class Web < Tool
+      def initialize(configuration)
+        super(
+          "web",
+          "Fetch web content and search the internet",
+          {
+            type: :object,
+            properties: {
+              action: {
+                type: :string,
+                enum: ["fetch", "search"],
+                description: "Action to perform: fetch URL or search"
+              },
+              url: {
+                type: :string,
+                description: "URL to fetch (for fetch action)"
+              },
+              query: {
+                type: :string,
+                description: "Search query (for search action)"
+              }
+            },
+            required: [:action]
+          }
+        )
+      end
+
+      def call(args)
+        action = args["action"]
+        
+        case action
+        when "fetch"
+          fetch_url(args["url"])
+        when "search"
+          search_web(args["query"])
+        else
+          "Invalid action: #{action}"
+        end
+      end
+
+      private
+
+      def fetch_url(url)
+        return "URL required for fetch action" unless url
+        
+        uri = URI(url)
+        http = Net::HTTP.new(uri.host, uri.port)
+        http.use_ssl = uri.scheme == "https"
+        http.read_timeout = 10
+        http.open_timeout = 5
+        
+        request = Net::HTTP::Get.new(uri)
+        request["User-Agent"] = "Elelem Agent/1.0"
+        
+        response = http.request(request)
+        
+        if response.is_a?(Net::HTTPSuccess)
+          content_type = response["content-type"] || ""
+          
+          if content_type.include?("text/html")
+            extract_text_from_html(response.body)
+          else
+            response.body
+          end
+        else
+          "HTTP Error: #{response.code} #{response.message}"
+        end
+      rescue => e
+        "Error fetching URL: #{e.message}"
+      end
+
+      def search_web(query)
+        return "Query required for search action" unless query
+        
+        # Use DuckDuckGo instant answers API
+        search_url = "https://api.duckduckgo.com/?q=#{URI.encode_www_form_component(query)}&format=json&no_html=1"
+        
+        result = fetch_url(search_url)
+        
+        if result.start_with?("Error") || result.start_with?("HTTP Error")
+          result
+        else
+          begin
+            data = JSON.parse(result)
+            format_search_results(data, query)
+          rescue JSON::ParserError
+            "Error parsing search results"
+          end
+        end
+      end
+
+      def extract_text_from_html(html)
+        # Simple HTML tag stripping
+        text = html.gsub(/<script[^>]*>.*?<\/script>/im, "")
+                  .gsub(/<style[^>]*>.*?<\/style>/im, "")
+                  .gsub(/<[^>]*>/, " ")
+                  .gsub(/\s+/, " ")
+                  .strip
+        
+        # Limit content length
+        text.length > 5000 ? text[0...5000] + "..." : text
+      end
+
+      def format_search_results(data, query)
+        results = []
+        
+        # Instant answer
+        if data["Answer"] && !data["Answer"].empty?
+          results << "Answer: #{data["Answer"]}"
+        end
+        
+        # Abstract
+        if data["Abstract"] && !data["Abstract"].empty?
+          results << "Summary: #{data["Abstract"]}"
+        end
+        
+        # Related topics
+        if data["RelatedTopics"] && data["RelatedTopics"].any?
+          topics = data["RelatedTopics"].first(3).map do |topic|
+            topic["Text"] if topic["Text"]
+          end.compact
+          
+          if topics.any?
+            results << "Related: #{topics.join("; ")}"
+          end
+        end
+        
+        if results.empty?
+          "No direct results found for '#{query}'. Try a more specific search or use web fetch to access specific URLs."
+        else
+          results.join("\n\n")
+        end
+      end
+    end
+  end
+end
\ No newline at end of file
lib/elelem/configuration.rb
@@ -49,6 +49,7 @@ module Elelem
         [
           Toolbox::Exec.new(self),
           Toolbox::File.new(self),
+          Toolbox::Web.new(self),
           Toolbox::Prompt.new(self),
         ] + mcp_tools
       )
lib/elelem/system_prompt.erb
@@ -1,14 +1,37 @@
-You are a deterministic coding assistant.
+You are a self-improving coding assistant with the ability to modify your own source code.
 
+## Core Capabilities
 - Think: Only when emitting `thinking` content.
 - Execute: Only via `tool_calls`.
 - Talk: Provide concise assistant `content` to the user.
 
-End a turn naturally; do not print special finish phrases.
+## Self-Improvement Framework
+When working on coding tasks, especially when asked to improve yourself:
+
+1. **Analyze**: Use `file` tool to read your own source code and understand current architecture
+2. **Research**: Use `web` tool to search for best practices, patterns, and solutions
+3. **Plan**: Break complex improvements into testable steps
+4. **Implement**: Use `file` tool to write improved code
+5. **Validate**: Use `exec` tool to run tests and verify improvements work
+6. **Iterate**: Continue improving until goals are met
+
+## Available Tools
+- `exec`: Run shell commands, tests, git operations
+- `file`: Read and write any file, including your own source code  
+- `web`: Search internet and fetch documentation for research
+- `prompt`: Ask user for clarification when needed
+
+## Self-Modification Guidelines
+- Always understand existing code before changing it
+- Make incremental, testable changes
+- Research best practices before implementing new patterns
+- Run tests after each significant change
+- Use git to track improvements
+- Learn from errors and adapt approach
 
 Context:
 - Time: <%= Time.now.strftime("%Y-%m-%d %H:%M:%S") %>
 - CWD: <%= Dir.pwd %>
 - System: <%= `uname -a`.strip %>
 
-Stay focused on the user's request.
+Stay focused on the user's request and continuously improve your capabilities.
lib/elelem/toolbox.rb
@@ -2,5 +2,6 @@
 
 require_relative "toolbox/exec"
 require_relative "toolbox/file"
+require_relative "toolbox/web"
 require_relative "toolbox/mcp"
 require_relative "toolbox/prompt"
elelem.gemspec
@@ -53,6 +53,7 @@ Gem::Specification.new do |spec|
     "lib/elelem/toolbox.rb",
     "lib/elelem/toolbox/exec.rb",
     "lib/elelem/toolbox/file.rb",
+    "lib/elelem/toolbox/web.rb",
     "lib/elelem/toolbox/mcp.rb",
     "lib/elelem/toolbox/prompt.rb",
     "lib/elelem/tools.rb",