ask-web-fetch 0.5.8 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d3e0494d6f9fd54b7b80b667b97d57f17d1b0e871d44c26fada9ad91669a391d
4
- data.tar.gz: e06918a4ca162ee2111715530f67ab68c8feae529c27c180cebb0cf462f13867
3
+ metadata.gz: ac8355feef3bd62bc048223f0390f79d95506f084817853a1a572be5eff4a78a
4
+ data.tar.gz: ccee31246284b6236732984c5f1d32fcf54d6ffa9203fa1f51150300b4b50702
5
5
  SHA512:
6
- metadata.gz: 4a045bb9226bb302cd60bda942acfa05a00e1f3d1aced99edc3bbd77a061c7650580b40447c385a4777b7156fa81aa0fcc57267caa4385b478e4d005c015f41f
7
- data.tar.gz: ffab19b64968c909c67dd631902b447738248c7e6a0d3da5db8851cc2ff615ec627bc8a89407b40dbeabbebb591d2cd6726c21747de34962cc9ff77c98ce16e8
6
+ metadata.gz: 181316376680bbb70d193b46dd898f48547b1846726e2d5f3d0596080ed4b41ca37ae818e3bc2963e0e2e700b47ff8a2957420222c0a737e45acedd00318e68c
7
+ data.tar.gz: 0b039ee976010c65274c956a3f5e3182164128b6a096eae1161ea42f266004fdd878dde12378899d68a29f26b56c2f454344e32abbd65d0444367f8948e330de
@@ -16,6 +16,13 @@ module Ask
16
16
  # A backend that fetched the page but found nothing usable in it.
17
17
  class EmptyContentError < Error; end
18
18
 
19
+ # A backend that fetched a REGISTRAR PARKING PAGE — an ad for a
20
+ # parked (for-sale) domain, not the site's content. Deterministic and
21
+ # terminal: retrying will never turn a parking ad into content, so
22
+ # the pipeline must classify (not retry) it. A subclass of
23
+ # EmptyContentError so existing empty-content handling still applies.
24
+ class ParkedDomainError < EmptyContentError; end
25
+
19
26
  # Network-level failure — timeout, connection refused/reset, bad
20
27
  # socket. Transient: the same URL may succeed on retry.
21
28
  class TimeoutError < Error; end
@@ -201,8 +201,9 @@ module Ask
201
201
  end
202
202
  # Browser renders the parked page a JS redirect lands on (the
203
203
  # server shell hands /lander to JS) — Local never sees it. The
204
- # shared parked-domain detector catches it here.
205
- raise EmptyContentError, "parked domain at #{url} registrar parking page, not site content" if parked_domain?(body)
204
+ # shared parked-domain detector catches it here; the distinct
205
+ # ParkedDomainError lets the pipeline classify (never retry) it.
206
+ raise ParkedDomainError, "parked domain at #{url} — registrar parking page, not site content" if parked_domain?(body)
206
207
 
207
208
  result = Markdown.generate(body, base_url: url, filter: self.class.content_filter)
208
209
  result[:outlinks] = outlink_urls(body, url)
@@ -55,7 +55,7 @@ module Ask
55
55
  # parked pages are fully server-rendered, so both Local and
56
56
  # Browser see the same ad.
57
57
  if parked_domain?(body)
58
- raise EmptyContentError, "parked domain at #{url} — registrar parking page, not site content"
58
+ raise ParkedDomainError, "parked domain at #{url} — registrar parking page, not site content"
59
59
  end
60
60
  raise EmptyContentError, "no readable content at #{url}" unless usable_content?(page[:content])
61
61
  # The completeness signal: a JS-app shell whose server HTML
@@ -21,6 +21,13 @@ module Ask
21
21
  class WebFetch < Ask::Tool
22
22
  DEFAULT_MAX_CHARS = 20_000
23
23
 
24
+ # Errors that mean "the URL is dead" — no amount of retrying changes
25
+ # the answer. When EVERY backend failed this way, the aggregate
26
+ # re-raises as FetchError so callers can fail fast; any transient
27
+ # failure in the mix (timeout, 5xx, empty render) keeps the base
28
+ # Error, which recovers on retry.
29
+ DETERMINISTIC = [Ask::WebFetch::FetchError, Ask::WebFetch::EmptyContentError].freeze
30
+
24
31
  # Backend chain, tried in order. Crawl4AI leads when configured
25
32
  # (CRAWL4AI_URL), so a present self-hosted renderer is preferred;
26
33
  # otherwise Local, with Jina as the last resort, and Browser appended
@@ -37,9 +44,34 @@ module Ask
37
44
  end
38
45
  end
39
46
 
40
- class << self
41
- attr_writer :backends
47
+ class << self
48
+ attr_writer :backends
49
+
50
+ # Collapses every backend's failure into ONE error whose class
51
+ # carries the best explanation. Precedence, most definitive first: a
52
+ # parked domain beats an empty shell (the shell IS the parking ad's
53
+ # shell — Local sees the JS redirect stub, Browser the lander),
54
+ # empty beats a dead 4xx (the page existed, it just had no content),
55
+ # and any deterministic explanation beats a transient one (transient
56
+ # keeps the retryable base Error). Clients read the class:
57
+ # ParkedDomainError / EmptyContentError / FetchError are terminal —
58
+ # retrying never changes the answer; Error may recover on retry.
59
+ def collapse(failures, url)
60
+ detail = failures.map { |backend, e| "#{backend.backend_name}: #{e.message}" }.join('; ')
61
+ message = "all web fetch backends failed for #{url} (#{detail})"
62
+
63
+ classes = failures.map { |_, e| e.class }
64
+ if classes.any? { |k| k <= Ask::WebFetch::ParkedDomainError }
65
+ raise Ask::WebFetch::ParkedDomainError, message
66
+ end
67
+ if classes.any? { |k| k <= Ask::WebFetch::EmptyContentError }
68
+ raise Ask::WebFetch::EmptyContentError, message
69
+ end
70
+
71
+ deterministic = failures.all? { |_, e| DETERMINISTIC.any? { |klass| e.is_a?(klass) } }
72
+ raise(deterministic ? Ask::WebFetch::FetchError : Ask::WebFetch::Error, message)
42
73
  end
74
+ end
43
75
 
44
76
  description 'Fetch a URL and return its content as clean markdown for LLM consumption. ' \
45
77
  'Use this to read web pages, articles, and documentation.'
@@ -68,10 +100,9 @@ module Ask
68
100
  self.class.backends.each do |backend_class|
69
101
  return backend_class.new.fetch(url)
70
102
  rescue Ask::WebFetch::Error => e
71
- failures << "#{backend_class.backend_name}: #{e.message}"
103
+ failures << [backend_class, e]
72
104
  end
73
- raise Ask::WebFetch::Error,
74
- "all web fetch backends failed for #{url} (#{failures.join('; ')})"
105
+ self.class.collapse(failures, url)
75
106
  end
76
107
 
77
108
  def format(page, url)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ask
4
4
  module WebFetch
5
- VERSION = '0.5.8'
5
+ VERSION = '0.6.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask-web-fetch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.8
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto