Class: Kodo::Tools::FetchUrl

Inherits:
RubyLLM::Tool
  • Object
show all
Extended by:
PromptContributor
Defined in:
lib/kodo/tools/fetch_url.rb

Constant Summary collapse

MAX_PER_TURN =
3
MAX_CONTENT_LENGTH =
50_000
MAX_REDIRECTS =
5
READ_TIMEOUT =
15
OPEN_TIMEOUT =
10
USER_AGENT =
"Kodo/#{VERSION} (bot; +https://kodo.bot)".freeze
BLOCKED_RANGES =

RFC 1918, loopback, link-local

[
  IPAddr.new('10.0.0.0/8'),
  IPAddr.new('172.16.0.0/12'),
  IPAddr.new('192.168.0.0/16'),
  IPAddr.new('127.0.0.0/8'),
  IPAddr.new('169.254.0.0/16'),
  IPAddr.new('0.0.0.0/8'),
  IPAddr.new('::1/128'),
  IPAddr.new('fc00::/7'),
  IPAddr.new('fe80::/10')
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from PromptContributor

capability_name, capability_primary, disabled_guidance, enabled_guidance

Constructor Details

#initialize(audit:) ⇒ FetchUrl

Returns a new instance of FetchUrl.



46
47
48
49
50
51
# File 'lib/kodo/tools/fetch_url.rb', line 46

def initialize(audit:)
  super()
  @audit = audit
  @turn_count = 0
  @turn_context = nil
end

Instance Attribute Details

#turn_context=(value) ⇒ Object (writeonly)

Sets the attribute turn_context

Parameters:

  • value —

    the value to set the attribute turn_context to.



44
45
46
# File 'lib/kodo/tools/fetch_url.rb', line 44

def turn_context=(value)
  @turn_context = value
end

Instance Method Details

#execute(url:) ⇒ Object



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
90
91
92
93
94
95
96
# File 'lib/kodo/tools/fetch_url.rb', line 57

def execute(url:)
  @turn_count += 1
  if @turn_count > MAX_PER_TURN
    return "Rate limit reached (max #{MAX_PER_TURN} fetches per message). Try again next message."
  end

  uri = validate_url(url)
  return uri if uri.is_a?(String) # error message

  content = fetch_with_redirects(uri)
  return content if content.is_a?(String) && content.start_with?('Error:')

  text = extract_text(content)
  text = text[0...MAX_CONTENT_LENGTH] if text.length > MAX_CONTENT_LENGTH

  # Audit-log injection signals (detection only — not a security boundary)
  if Kodo.config.web_injection_scan?
    scan = Web::InjectionScanner.scan(text)
    if scan.suspicious?
      @audit.log(
        event: 'injection_suspected',
        detail: "url:#{Kodo.config.web_audit_urls? ? url : '[redacted]'} signals:#{scan.signal_count}"
      )
    end
  end

  audit_url = Kodo.config.web_audit_urls? ? url : '[redacted]'
  @audit.log(
    event: 'url_fetched',
    detail: "url:#{audit_url} len:#{text.length}"
  )

  # Mark that web content was fetched this turn (used by RememberFact gate)
  @turn_context&.web_fetched!

  result = text.empty? ? "No readable content found at #{url}" : text
  wrap_as_untrusted(url, result)
rescue Kodo::Error => e
  e.message
end

#name ⇒ Object



98
99
100
# File 'lib/kodo/tools/fetch_url.rb', line 98

def name
  'fetch_url'
end

#reset_turn_count! ⇒ Object



53
54
55
# File 'lib/kodo/tools/fetch_url.rb', line 53

def reset_turn_count!
  @turn_count = 0
end