ruby_llm-browser 0.1.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.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +61 -0
  3. data/CODE_OF_CONDUCT.md +11 -0
  4. data/CONTRIBUTING.md +42 -0
  5. data/LICENSE.txt +21 -0
  6. data/NOTICE.md +92 -0
  7. data/README.md +139 -0
  8. data/SECURITY.md +46 -0
  9. data/docs/API.md +177 -0
  10. data/docs/CNAME +1 -0
  11. data/docs/RAILS.md +167 -0
  12. data/docs/README.md +16 -0
  13. data/docs/RELEASE_ACTION_PLAN.md +81 -0
  14. data/docs/RELEASE_AUDIT.md +158 -0
  15. data/docs/RELEASING.md +131 -0
  16. data/docs/SECURITY_MODEL.md +117 -0
  17. data/docs/SPEC_BASELINE.md +107 -0
  18. data/docs/WORK.md +260 -0
  19. data/docs/_advanced/live-backend-qualification.md +74 -0
  20. data/docs/_advanced/rails-integration.md +56 -0
  21. data/docs/_config.yml +109 -0
  22. data/docs/_core_features/inspect-extract.md +91 -0
  23. data/docs/_core_features/offline-act.md +110 -0
  24. data/docs/_core_features/operation-lifecycle.md +63 -0
  25. data/docs/_core_features/source-policy.md +61 -0
  26. data/docs/_data/navigation.yml +12 -0
  27. data/docs/_data/sidebar.yml +8 -0
  28. data/docs/_data/social_links.yml +3 -0
  29. data/docs/_getting_started/configuration.md +68 -0
  30. data/docs/_getting_started/getting-started.md +77 -0
  31. data/docs/_getting_started/overview.md +52 -0
  32. data/docs/_plugins/versioned_docs.rb +46 -0
  33. data/docs/_reference/api-reference.md +29 -0
  34. data/docs/_reference/release-history.md +23 -0
  35. data/docs/_reference/upgrading.md +33 -0
  36. data/docs/bin/build-api.sh +23 -0
  37. data/docs/bin/build.sh +161 -0
  38. data/docs/bin/export-api-markdown.rb +53 -0
  39. data/docs/bin/link_versions.rb +54 -0
  40. data/docs/bin/postprocess_api_seo.rb +129 -0
  41. data/docs/bin/prepare_versions.rb +40 -0
  42. data/docs/bin/serve.sh +12 -0
  43. data/docs/bin/versions.yml +14 -0
  44. data/docs/index.md +82 -0
  45. data/examples/plain_ruby.rb +34 -0
  46. data/examples/rails_adapter.rb +47 -0
  47. data/lib/generators/ruby_llm/browser/install_generator.rb +29 -0
  48. data/lib/generators/ruby_llm/browser/templates/create_ruby_llm_browser_tables.rb.tt +60 -0
  49. data/lib/ruby_llm/browser/approval.rb +128 -0
  50. data/lib/ruby_llm/browser/approval_store.rb +38 -0
  51. data/lib/ruby_llm/browser/backend.rb +99 -0
  52. data/lib/ruby_llm/browser/budget.rb +90 -0
  53. data/lib/ruby_llm/browser/capabilities.rb +76 -0
  54. data/lib/ruby_llm/browser/configuration.rb +74 -0
  55. data/lib/ruby_llm/browser/context.rb +117 -0
  56. data/lib/ruby_llm/browser/doctor.rb +134 -0
  57. data/lib/ruby_llm/browser/engine.rb +326 -0
  58. data/lib/ruby_llm/browser/errors.rb +86 -0
  59. data/lib/ruby_llm/browser/fake_backend.rb +134 -0
  60. data/lib/ruby_llm/browser/immutable.rb +19 -0
  61. data/lib/ruby_llm/browser/in_memory_approval_store.rb +67 -0
  62. data/lib/ruby_llm/browser/in_memory_operation_store.rb +174 -0
  63. data/lib/ruby_llm/browser/operation.rb +218 -0
  64. data/lib/ruby_llm/browser/operation_store.rb +92 -0
  65. data/lib/ruby_llm/browser/policy.rb +298 -0
  66. data/lib/ruby_llm/browser/rails/active_record_approval_store.rb +72 -0
  67. data/lib/ruby_llm/browser/rails/active_record_operation_store.rb +231 -0
  68. data/lib/ruby_llm/browser/rails/actor_record.rb +13 -0
  69. data/lib/ruby_llm/browser/rails/approval_record.rb +49 -0
  70. data/lib/ruby_llm/browser/rails/operation_job.rb +163 -0
  71. data/lib/ruby_llm/browser/rails/operation_record.rb +56 -0
  72. data/lib/ruby_llm/browser/rails/reaper.rb +117 -0
  73. data/lib/ruby_llm/browser/rails/schema.rb +81 -0
  74. data/lib/ruby_llm/browser/rails.rb +79 -0
  75. data/lib/ruby_llm/browser/redactor.rb +159 -0
  76. data/lib/ruby_llm/browser/result.rb +102 -0
  77. data/lib/ruby_llm/browser/stagehand_backend.rb +332 -0
  78. data/lib/ruby_llm/browser/tool.rb +70 -0
  79. data/lib/ruby_llm/browser/tools/act.rb +91 -0
  80. data/lib/ruby_llm/browser/tools/extract.rb +60 -0
  81. data/lib/ruby_llm/browser/tools/inspect.rb +38 -0
  82. data/lib/ruby_llm/browser/url_guard.rb +253 -0
  83. data/lib/ruby_llm/browser/version.rb +9 -0
  84. data/lib/ruby_llm/browser.rb +79 -0
  85. metadata +267 -0
data/docs/_config.yml ADDED
@@ -0,0 +1,109 @@
1
+ title: "RubyLLM::Browser"
2
+ description: "An unofficial RubyLLM extension for offline bounded browser inspection and schema-checked extraction."
3
+ tagline: "Policy-bound browser tools for RubyLLM"
4
+ url: https://rubyllm-browser.wangyaodi.com
5
+ baseurl: ""
6
+ lang: en-US
7
+ repository: yorzi/ruby_llm-browser
8
+
9
+ theme: jekyll-vitepress-theme
10
+
11
+ markdown: kramdown
12
+ kramdown:
13
+ input: GFM
14
+ syntax_highlighter_opts:
15
+ block:
16
+ line_numbers: false
17
+ permalink: pretty
18
+
19
+ collections:
20
+ getting_started:
21
+ permalink: "/:name/"
22
+ output: true
23
+ core_features:
24
+ permalink: "/:name/"
25
+ output: true
26
+ advanced:
27
+ permalink: "/:name/"
28
+ output: true
29
+ reference:
30
+ permalink: "/:name/"
31
+ output: true
32
+
33
+ defaults:
34
+ - scope:
35
+ path: ""
36
+ values:
37
+ layout: default
38
+ jekyll_vitepress:
39
+ auto_title: false
40
+
41
+ jekyll_vitepress:
42
+ seo:
43
+ page_type: TechArticle
44
+ publisher:
45
+ type: Organization
46
+ name: "RubyLLM::Browser"
47
+ url: https://rubyllm-browser.wangyaodi.com
48
+ same_as:
49
+ - https://github.com/yorzi/ruby_llm-browser
50
+ llms:
51
+ title: RubyLLM::Browser Documentation
52
+ description: "An unofficial RubyLLM extension for offline bounded browser inspection and schema-checked extraction."
53
+ full: true
54
+ branding:
55
+ site_title: "RubyLLM::Browser"
56
+ typography:
57
+ body_font_family: "'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif"
58
+ code_font_family: "'IBM Plex Mono', ui-monospace, monospace"
59
+ google_fonts_url: "https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400&family=Inter:wght@300;400;500;600;700;900&display=swap"
60
+ syntax:
61
+ light_theme: github
62
+ dark_theme: github.dark
63
+ github_star:
64
+ enabled: true
65
+ repository: yorzi/ruby_llm-browser
66
+ text: Star
67
+ show_count: true
68
+ footer:
69
+ enabled: true
70
+ show_on_docs: false
71
+ message: "Released under the MIT License."
72
+ copyright: "Copyright © 2026 Andy Wang"
73
+ doc_footer:
74
+ enabled: true
75
+ previous_label: Previous page
76
+ next_label: Next page
77
+ last_updated:
78
+ enabled: true
79
+ text: Last updated
80
+ format: "%-d %b %Y"
81
+ edit_link:
82
+ enabled: true
83
+ pattern: https://github.com/yorzi/ruby_llm-browser/edit/main/docs/:path
84
+ text: Edit this page on GitHub
85
+
86
+ plugins:
87
+ - jekyll-redirect-from
88
+ - jekyll-vitepress-theme
89
+
90
+ exclude:
91
+ - bin
92
+ - vendor
93
+ - _site
94
+ - Gemfile
95
+ - Gemfile.lock
96
+ - README.md
97
+ - RELEASE_ACTION_PLAN.md
98
+ - SPEC_BASELINE.md
99
+ - WORK.md
100
+
101
+ # The language of code examples; `project-docs` uses it for new pages.
102
+ code_language: ruby
103
+
104
+ # Values that change between releases. Pages read them as {{ site.version }}
105
+ # and {{ site.examples.NAME }}. Keep `released: false` on main; the docs builder
106
+ # reads a release's version from its matching Git tag.
107
+ released: false
108
+ version: "0.1.0"
109
+ examples: {}
@@ -0,0 +1,91 @@
1
+ ---
2
+ layout: default
3
+ title: Inspecting and extracting
4
+ nav_order: 1
5
+ description: Inspect allowlisted pages and extract data with a host-pinned JSON Schema.
6
+ ---
7
+
8
+ # {{ page.title }}
9
+
10
+ {{ page.description }}
11
+ {: .fs-6 .fw-300 }
12
+
13
+ After reading this guide, you will know:
14
+
15
+ * What Inspect returns and what it does not do.
16
+ * How Extract uses a schema chosen by the host.
17
+ * How tool hashes differ from direct Engine results.
18
+
19
+ ## Create the Read-only Tools
20
+
21
+ The [offline example in Getting Started]({% link _getting_started/getting-started.md %}) creates a context and a fixed schema. Pass those objects when you construct the tools:
22
+
23
+ ```ruby
24
+ require "ruby_llm/browser"
25
+
26
+ class OfflineResolver
27
+ def getaddresses(_host) = [ "93.184.216.34" ]
28
+ end
29
+
30
+ context = RubyLLM::Browser::Context.new(
31
+ actor_id: "demo-actor", run_id: "demo-run",
32
+ backend: RubyLLM::Browser::FakeBackend.new,
33
+ source_policy: RubyLLM::Browser::Policy.new(
34
+ allowed_hosts: [ "docs.example.com" ], resolver: OfflineResolver.new
35
+ ),
36
+ operation_store: RubyLLM::Browser::InMemoryOperationStore.new
37
+ )
38
+ schema = {
39
+ "type" => "object",
40
+ "properties" => { "title" => { "type" => "string" } },
41
+ "required" => [ "title" ], "additionalProperties" => false
42
+ }
43
+
44
+ inspect_tool = RubyLLM::Browser::Inspect.new(context: context)
45
+ extract_tool = RubyLLM::Browser::Extract.new(context: context, schema: schema)
46
+
47
+ puts inspect_tool.execute(url: "https://docs.example.com/")[:status] # => ok
48
+ puts extract_tool.execute(url: "https://docs.example.com/")[:data] # => {"title"=>"Example"}
49
+ ```
50
+
51
+ The tool returns a `Hash` with string status values. The fake backend used in the linked
52
+ example supplies the exact sample output above. In an application, register these tool
53
+ instances on a configured RubyLLM chat. The host should select the context and schema;
54
+ the model supplies only the URL, optional instruction, and an optional positive
55
+ `max_bytes` limit.
56
+
57
+ ## Inspect a Page
58
+
59
+ `Inspect` navigates to one approved URL and asks the backend for observations. It does
60
+ not click, type, submit forms, run arbitrary JavaScript, or download files. Its
61
+ observations can still contain hostile instructions, false claims, or sensitive data.
62
+ Treat them as untrusted evidence.
63
+
64
+ ## Extract a Known Shape
65
+
66
+ `Extract` receives its JSON Schema when the host constructs the tool. The model cannot
67
+ replace or widen that schema. The policy checks schema size, depth, property count, and
68
+ supported keywords. Provider output is validated against the schema before it is
69
+ redacted and bounded. A mismatched response is an error, not a successful extraction.
70
+
71
+ Both tools accept an optional `instruction` to focus the operation and an optional
72
+ `max_bytes` value. That value can tighten the policy and remaining operation limit, but
73
+ cannot raise either limit. A successful status does not prove that a page claim is true.
74
+
75
+ ## Read the Result
76
+
77
+ `Tool#execute` and RubyLLM `Tool#call` return hashes. A direct call to `Engine#inspect`
78
+ or `Engine#extract` returns a `Result` object. Both forms include status and provenance.
79
+ Check the status before consuming data. `truncated` is bounded output, while
80
+ `unknown` and `cleanup_pending` need host review.
81
+
82
+ `Act` is an opt-in offline simulation. It does not dispatch a real browser action. Read
83
+ [Simulating approved actions]({% link _core_features/offline-act.md %}) to issue a host
84
+ grant and use the FakeBackend contract. The full live Act protocol is deferred to a
85
+ later version.
86
+
87
+ ## Next Steps
88
+
89
+ * [Controlling source access]({% link _core_features/source-policy.md %}) - define allowed destinations.
90
+ * [Bounding operation work]({% link _core_features/operation-lifecycle.md %}) - set budgets and store lifecycle state.
91
+ * [API and configuration reference]({% link _reference/api-reference.md %}) - review fields and error codes.
@@ -0,0 +1,110 @@
1
+ ---
2
+ layout: default
3
+ title: Simulating approved actions
4
+ nav_order: 4
5
+ description: Simulate one host-approved browser action with FakeBackend.
6
+ ---
7
+
8
+ # {{ page.title }}
9
+
10
+ {{ page.description }}
11
+ {: .fs-6 .fw-300 }
12
+
13
+ After reading this guide, you will know:
14
+
15
+ * How the host issues and revokes an action grant.
16
+ * Which action details the grant binds.
17
+ * Why this simulation does not authorize a live browser action.
18
+
19
+ A model can propose a click, but the host must decide whether that exact action is
20
+ allowed. `FakeBackend` lets you exercise that approval flow without opening a browser or
21
+ making a network request.
22
+
23
+ ## Issue a Grant and Run the Simulation
24
+
25
+ Create the approval from the same action and URL that you pass to `Act`. Use host-owned
26
+ actor and run identifiers. Do not take those values from model output.
27
+
28
+ ```ruby
29
+ require "ruby_llm/browser"
30
+
31
+ class OfflineResolver
32
+ def getaddresses(_host) = [ "93.184.216.34" ]
33
+ end
34
+
35
+ policy = RubyLLM::Browser::Policy.new(
36
+ allowed_hosts: [ "docs.example.com" ], resolver: OfflineResolver.new
37
+ )
38
+ approval_store = RubyLLM::Browser::InMemoryApprovalStore.new
39
+ context = RubyLLM::Browser::Context.new(
40
+ actor_id: "demo-actor", run_id: "demo-run",
41
+ backend: RubyLLM::Browser::FakeBackend.new,
42
+ source_policy: policy,
43
+ operation_store: RubyLLM::Browser::InMemoryOperationStore.new,
44
+ approval_store: approval_store
45
+ )
46
+
47
+ url = policy.authorize_destination!("https://docs.example.com/").to_s
48
+ action = policy.authorize_action!({ "method" => "click", "selector" => "a#docs" })
49
+ approval = RubyLLM::Browser::Approval.for_action(
50
+ actor_id: context.actor_id, run_id: context.run_id,
51
+ url: url, action: action, ttl: 60, now: context.now
52
+ )
53
+ approval_store.issue(approval)
54
+
55
+ act = RubyLLM::Browser::Act.new(context: context)
56
+ result = act.execute(
57
+ url: url, approval_id: approval.id, method: "click", selector: "a#docs"
58
+ )
59
+
60
+ puts result[:status] # => "ok"
61
+ puts result.dig(:locator, "approval_id") # => approval.id
62
+ ```
63
+
64
+ The example uses one active grant. It does not call a model. `Act` is opt-in and is not
65
+ registered by default.
66
+
67
+ ## Bind the Grant to the Request
68
+
69
+ `Policy#authorize_action!` returns a canonical hash with `description`, `method`,
70
+ `selector`, and `arguments`. The method is lowercase. Empty descriptions default to
71
+ `"#{method} #{selector}"`; arguments are strings. A grant digest uses sorted-key,
72
+ compact JSON for this normalized hash. The URL is the exact authorized URL returned by
73
+ `Policy#authorize_destination!(url).to_s`.
74
+
75
+ The host creates the grant with `Approval.for_action` and stores it with
76
+ `ApprovalStore#issue`. It can revoke the grant with `ApprovalStore#revoke(id)` before
77
+ the action consumes it. A grant expires when `now >= expires_at`. It cannot authorize a
78
+ different actor, run, URL, or normalized action. Reads by the tool's resolver do not
79
+ consume it. `consume_once` checks and marks the grant consumed atomically immediately
80
+ before FakeBackend records the action.
81
+
82
+ If two calls compete for one valid grant, one returns `ok`, one returns `APPROVAL_STALE`,
83
+ and FakeBackend records one action. A durable store must preserve this result across
84
+ workers and database connections. `InMemoryApprovalStore` is process-local; use a
85
+ durable store for a multi-worker host.
86
+
87
+ ## Read Denial Results
88
+
89
+ | Condition | Result |
90
+ | --- | --- |
91
+ | The context has no approval store or the ID is unknown | `APPROVAL_REQUIRED` |
92
+ | The grant expired, was revoked or consumed, or does not match the request | `APPROVAL_STALE` |
93
+ | Policy denies the action or URL | `NOT_AUTHORIZED` |
94
+
95
+ Each denial records no simulated action. Check the returned `status` and `error.code`
96
+ before using a result.
97
+
98
+ ## Know the Boundary
99
+
100
+ The fake backend records an in-process action fixture. It does not click a page, change a
101
+ website, or prove live authorization. First-release grants leave `page_revision`,
102
+ `operation_id`, and `session_id` unset. The full live Act protocol needs a grant bound to
103
+ the current page target and browser session; it is deferred to a later version. See
104
+ [the security model]({% link SECURITY_MODEL.md %}) and [provider qualification]({% link _advanced/live-backend-qualification.md %}).
105
+
106
+ ## Next Steps
107
+
108
+ * [Inspect and extract]({% link _core_features/inspect-extract.md %}) - read and structure offline page observations.
109
+ * [Control source access]({% link _core_features/source-policy.md %}) - define the host allowlist.
110
+ * [Review the operation lifecycle]({% link _core_features/operation-lifecycle.md %}) - handle reservations and cleanup states.
@@ -0,0 +1,63 @@
1
+ ---
2
+ layout: default
3
+ title: Bounding operation work
4
+ nav_order: 3
5
+ description: Limit browser work by time, steps, retained bytes, and actor concurrency.
6
+ ---
7
+
8
+ # {{ page.title }}
9
+
10
+ {{ page.description }}
11
+ {: .fs-6 .fw-300 }
12
+
13
+ After reading this guide, you will know:
14
+
15
+ * Which limits a `Budget` applies to an operation.
16
+ * How operation states prevent unsafe retries.
17
+ * When a durable store and independent reaper are required.
18
+
19
+ ## Set a Budget
20
+
21
+ Each context receives a `Budget`. Defaults are 60 seconds, 12 provider steps, 262,144
22
+ retained bytes, and one concurrent operation per actor. A smaller budget can cap one
23
+ workflow:
24
+
25
+ ```ruby
26
+ require "ruby_llm/browser"
27
+
28
+ budget = RubyLLM::Browser::Budget.new(
29
+ max_seconds: 30, max_steps: 6, max_bytes: 32_768,
30
+ max_concurrent: 1, max_cost_usd: 0.25
31
+ )
32
+ puts budget.to_h
33
+ ```
34
+
35
+ Steps count navigation, observation, extraction, and action calls. Byte limits count
36
+ retained sanitized result data, not all bytes the remote browser receives. The optional
37
+ `max_cost_usd` value is advisory metadata. It is not a provider billing cap.
38
+
39
+ ## Track an Operation
40
+
41
+ An operation is claimed before it starts. The Engine records state changes, step and byte
42
+ usage, session identity, and cleanup status. A duplicate start cannot run the same
43
+ operation again. SDK retries are disabled, and unknown outcomes are not automatically
44
+ replayed.
45
+
46
+ The in-memory store is useful for examples or a single process. It does not survive a
47
+ restart or coordinate workers. Use the Rails adapter or implement a durable
48
+ `OperationStore` when operations can outlive a process. Keep `unknown` and
49
+ `cleanup_pending` operations visible and retain their quota until closure is confirmed.
50
+
51
+ ## Reap Independently
52
+
53
+ Cancellation fences workers but cannot undo a request already in flight. A reaper should
54
+ run on an independent schedule so it can reconcile and close known sessions after a job
55
+ worker exits. Provider time passing is not proof that a session closed or stopped
56
+ incurring costs.
57
+
58
+ The Rails adapter and its scheduling requirements are described in [Running work in Rails]({% link _advanced/rails-integration.md %}). Result limits and status meanings are in the [API reference]({% link _reference/api-reference.md %}).
59
+
60
+ ## Next Steps
61
+
62
+ * [Running work in Rails]({% link _advanced/rails-integration.md %}) - persist operations across workers.
63
+ * [API and configuration reference]({% link _reference/api-reference.md %}) - inspect status fields and defaults.
@@ -0,0 +1,61 @@
1
+ ---
2
+ layout: default
3
+ title: Controlling source access
4
+ nav_order: 2
5
+ description: Restrict navigation to approved public hosts, schemes, and ports.
6
+ ---
7
+
8
+ # {{ page.title }}
9
+
10
+ {{ page.description }}
11
+ {: .fs-6 .fw-300 }
12
+
13
+ After reading this guide, you will know:
14
+
15
+ * How the host allowlist matches names.
16
+ * Which URL checks happen before a backend starts.
17
+ * Why a Ruby URL allowlist does not replace browser-side egress control.
18
+
19
+ ## Allow a Destination
20
+
21
+ The host creates a `Policy` and supplies it through the operation context. Exact host
22
+ names make the intended scope clear. This offline check injects a resolver so it makes no
23
+ DNS request:
24
+
25
+ ```ruby
26
+ require "ruby_llm/browser"
27
+
28
+ class OfflineResolver
29
+ def getaddresses(_host) = [ "93.184.216.34" ]
30
+ end
31
+
32
+ policy = RubyLLM::Browser::Policy.new(
33
+ allowed_hosts: [ "docs.example.com" ], resolver: OfflineResolver.new
34
+ )
35
+ puts policy.authorize_destination!("https://docs.example.com/").host
36
+ # => "docs.example.com"
37
+ ```
38
+
39
+ The default scheme allowlist is HTTP and HTTPS, and the default ports are 80 and 443.
40
+ DNS verification is enabled. Public address checks reject loopback, private, link-local,
41
+ multicast, documentation, and cloud metadata ranges. A wildcard such as
42
+ `*.example.com` matches subdomains and does not allow the bare `example.com` host.
43
+
44
+ ## Keep Host and Browser Policy Aligned
45
+
46
+ The host allowlist checks the requested destination before navigation. It cannot
47
+ sandbox a remote browser. Provider-side enforcement must also cover DNS at connection
48
+ time, redirects, frames, popups, WebSockets, scripts, and all other subresources. Pages
49
+ can initiate requests even when the gem only observes them.
50
+
51
+ The backend must report enforced egress isolation before a live session can start.
52
+ The built-in `StagehandBackend` always reports `:unverified` and refuses live calls in
53
+ the first release. No caller option can override that posture. A custom backend must
54
+ provide its own enforceable egress control; a host assertion does not install a firewall
55
+ or prove provider behavior. See [backend qualification]({% link _advanced/live-backend-qualification.md %}) before selecting a live backend.
56
+
57
+ ## Next Steps
58
+
59
+ * [Inspecting and extracting]({% link _core_features/inspect-extract.md %}) - use the read-only tools.
60
+ * [Configuration]({% link _getting_started/configuration.md %}) - keep policy and identity host-owned.
61
+ * [Security model]({% link SECURITY_MODEL.md %}) - review trust boundaries and open limits.
@@ -0,0 +1,12 @@
1
+ - title: Guides
2
+ url: /getting-started/
3
+ collections:
4
+ - getting_started
5
+ - core_features
6
+ - advanced
7
+ - title: Reference
8
+ url: /api-reference/
9
+ collections:
10
+ - reference
11
+ - title: Releases
12
+ url: https://github.com/yorzi/ruby_llm-browser/releases
@@ -0,0 +1,8 @@
1
+ - title: Start Here
2
+ collection: getting_started
3
+ - title: Core Features
4
+ collection: core_features
5
+ - title: Advanced
6
+ collection: advanced
7
+ - title: Reference
8
+ collection: reference
@@ -0,0 +1,3 @@
1
+ - icon: github
2
+ url: https://github.com/yorzi/ruby_llm-browser
3
+ label: GitHub
@@ -0,0 +1,68 @@
1
+ ---
2
+ layout: default
3
+ title: Configuration
4
+ nav_order: 3
5
+ description: Set process defaults while keeping identity, authorization, schemas, and credentials in the host.
6
+ ---
7
+
8
+ # {{ page.title }}
9
+
10
+ {{ page.description }}
11
+ {: .fs-6 .fw-300 }
12
+
13
+ After reading this guide, you will know:
14
+
15
+ * Which process-wide defaults RubyLLM::Browser can hold.
16
+ * Which trusted values belong in each `Context` and `Policy`.
17
+ * Why backend credentials and extraction schemas stay outside model arguments.
18
+
19
+ ## Set Process Defaults
20
+
21
+ `RubyLLM::Browser.configure` sets defaults for new objects. Existing contexts and
22
+ policies keep the values they received when they were created.
23
+
24
+ ```ruby
25
+ require "ruby_llm/browser"
26
+
27
+ RubyLLM::Browser.configure do |config|
28
+ config.default_budget = RubyLLM::Browser::Budget.new(max_seconds: 30, max_steps: 6)
29
+ config.max_result_bytes = 32_768
30
+ end
31
+
32
+ policy = RubyLLM::Browser::Policy.new(allowed_hosts: [ "docs.example.com" ])
33
+ ```
34
+
35
+ The gem does not load provider credentials from the environment when you require it.
36
+ The host creates the backend and passes it to every context. The `configuration.backend`
37
+ slot is reserved for host integration and is not a fallback used by `Context`.
38
+
39
+ ## Choose a Source Policy
40
+
41
+ Pass exact host names when possible. A pattern such as `*.example.com` allows matching
42
+ subdomains but not the bare `example.com` host. The defaults allow HTTP and HTTPS on
43
+ ports 80 and 443. DNS verification is enabled by default. Keep it enabled for real use;
44
+ inject a resolver only in controlled offline fixtures.
45
+
46
+ `Policy` also sets `max_result_bytes`, extraction schema limits, and the set of allowed
47
+ action methods for the experimental Act path. Browser-side egress must separately cover
48
+ redirects, frames, popups, WebSockets, assets, and other subresources before any live
49
+ backend can be used. The built-in Stagehand adapter remains unsupported in this release.
50
+
51
+ ## Defaults and Ownership
52
+
53
+ | Setting | Default | Owner |
54
+ | --- | --- | --- |
55
+ | `default_budget` | 60 seconds, 12 steps, 262,144 bytes, 1 concurrent operation, no cost estimate | Host process |
56
+ | `max_result_bytes` | 65,536 bytes | Host process and new policies |
57
+ | `clock` | `Time.now.utc` | Host process or context |
58
+ | `logger` | RubyLLM logger when present, otherwise a warning-level stderr logger | Host process |
59
+ | `backend` | `nil`, unused as a fallback | Host; pass explicitly to `Context` |
60
+
61
+ `Budget#max_cost_usd` is advisory metadata. It does not enforce provider billing. The
62
+ host must also reauthorize the actor and run when creating a context, including from a
63
+ background job. For all context and policy options, see the [API and configuration reference]({% link _reference/api-reference.md %}).
64
+
65
+ ## Next Steps
66
+
67
+ * [Controlling source access]({% link _core_features/source-policy.md %}) - review URL and DNS rules.
68
+ * [Bounding operation work]({% link _core_features/operation-lifecycle.md %}) - understand limits and cleanup.
@@ -0,0 +1,77 @@
1
+ ---
2
+ layout: default
3
+ title: Getting Started
4
+ nav_order: 1
5
+ description: Install the gem and run a read-only operation with its offline backend.
6
+ ---
7
+
8
+ # {{ page.title }}
9
+
10
+ {{ page.description }}
11
+ {: .fs-6 .fw-300 }
12
+
13
+ After reading this guide, you will know:
14
+
15
+ * How to install the published gem or the current source version.
16
+ * How to run Inspect and Extract without a provider or network request.
17
+ * Where to review live-provider limitations.
18
+
19
+ ## Installation
20
+
21
+ The stable release is `{{ site.version }}`. Add it to your Gemfile:
22
+
23
+ ```ruby
24
+ gem "ruby_llm-browser", "~> {{ site.version }}"
25
+ ```
26
+
27
+ For unreleased development changes, install the default branch instead:
28
+
29
+ ```sh
30
+ bundle add ruby_llm-browser --git https://github.com/yorzi/ruby_llm-browser --branch main
31
+ ```
32
+
33
+ Use Ruby 3.2 or later. The gem supports RubyLLM `>= 2.0, < 3` and pins Stagehand to the
34
+ 3.21.x series. Rails is optional and is not loaded by the core entry point. See
35
+ [configuration]({% link _getting_started/configuration.md %}) for host-owned settings.
36
+
37
+ ## Run a Read-only Operation Offline
38
+
39
+ This complete example needs no credentials and makes no network request. Its resolver
40
+ returns a public test address so URL policy can be exercised without DNS.
41
+
42
+ ```ruby
43
+ require "ruby_llm/browser"
44
+
45
+ class OfflineResolver
46
+ def getaddresses(_host) = [ "93.184.216.34" ]
47
+ end
48
+
49
+ context = RubyLLM::Browser::Context.new(
50
+ actor_id: "demo-actor", run_id: "demo-run",
51
+ backend: RubyLLM::Browser::FakeBackend.new,
52
+ source_policy: RubyLLM::Browser::Policy.new(
53
+ allowed_hosts: [ "docs.example.com" ], resolver: OfflineResolver.new
54
+ ),
55
+ operation_store: RubyLLM::Browser::InMemoryOperationStore.new
56
+ )
57
+ schema = {
58
+ "type" => "object",
59
+ "properties" => { "title" => { "type" => "string" } },
60
+ "required" => [ "title" ], "additionalProperties" => false
61
+ }
62
+
63
+ inspect_tool = RubyLLM::Browser::Inspect.new(context: context)
64
+ extract_tool = RubyLLM::Browser::Extract.new(context: context, schema: schema)
65
+ puts inspect_tool.execute(url: "https://docs.example.com/")[:status] # => ok
66
+ puts extract_tool.execute(url: "https://docs.example.com/")[:data] # => {"title"=>"Example"}
67
+ ```
68
+
69
+ `FakeBackend` supplies deterministic responses. The built-in Stagehand adapter is not
70
+ supported for live operations in this release. Use a custom backend only after you have
71
+ reviewed the [security boundaries]({% link _advanced/live-backend-qualification.md %}).
72
+
73
+ ## Next Steps
74
+
75
+ * [How RubyLLM::Browser Works]({% link _getting_started/overview.md %}) - follow the objects through an operation.
76
+ * [Configuration]({% link _getting_started/configuration.md %}) - set process defaults and build a trusted context.
77
+ * [Inspecting and extracting]({% link _core_features/inspect-extract.md %}) - register tools and interpret their results.
@@ -0,0 +1,52 @@
1
+ ---
2
+ layout: default
3
+ title: How RubyLLM::Browser Works
4
+ nav_order: 2
5
+ description: See how trusted host inputs, tools, policy, backends, and operation stores fit together.
6
+ ---
7
+
8
+ # {{ page.title }}
9
+
10
+ {{ page.description }}
11
+ {: .fs-6 .fw-300 }
12
+
13
+ After reading this guide, you will know:
14
+
15
+ * Which objects the host creates and which inputs the model can supply.
16
+ * How a tool call moves through policy checks and the backend.
17
+ * Which result and lifecycle guarantees the gem provides.
18
+
19
+ ## The Public API
20
+
21
+ The host creates a `Context` for an authenticated actor and run. It supplies the backend,
22
+ source policy, operation store, optional approval store, and budget. The model does not
23
+ choose those objects.
24
+
25
+ `Inspect` observes an allowlisted page. `Extract` reads fields using a schema fixed by
26
+ the host when the tool is constructed. The tools return hashes that RubyLLM can pass to
27
+ the model. Direct calls to `Engine#inspect` and `Engine#extract` return a `Result` object.
28
+
29
+ ## How It Fits Together
30
+
31
+ The Engine checks the destination and backend capability before a session starts. It
32
+ reserves and updates an operation, tracks time, steps, and retained bytes, then asks the
33
+ backend to navigate and observe or extract. The Engine attempts session cleanup before
34
+ it returns. If the remote outcome or cleanup cannot be confirmed, the operation remains
35
+ `unknown` or `cleanup_pending`; it is not treated as a successful result.
36
+
37
+ `Policy` controls URL destinations, extraction schemas, output limits, and the restricted
38
+ Act simulation. `FakeBackend` runs without network access and is the only supported
39
+ backend in the first release. `StagehandBackend` adapts the Stagehand Ruby SDK but refuses
40
+ live operations until provider egress and cleanup are qualified. In-memory stores are
41
+ useful for a single process and offline examples; use the Rails adapter or another durable
42
+ store when work must survive restarts or worker changes.
43
+
44
+ The output is evidence with provenance, not a verified statement of fact. Page content
45
+ can be adversarial and can initiate requests inside the remote browser even when the gem
46
+ does not click or submit forms.
47
+
48
+ ## Next Steps
49
+
50
+ * [Getting Started]({% link _getting_started/getting-started.md %}) - install and run the offline example.
51
+ * [Configuration]({% link _getting_started/configuration.md %}) - choose host-owned defaults.
52
+ * [API and configuration reference]({% link _reference/api-reference.md %}) - see options, statuses, and result fields.