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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +61 -0
- data/CODE_OF_CONDUCT.md +11 -0
- data/CONTRIBUTING.md +42 -0
- data/LICENSE.txt +21 -0
- data/NOTICE.md +92 -0
- data/README.md +139 -0
- data/SECURITY.md +46 -0
- data/docs/API.md +177 -0
- data/docs/CNAME +1 -0
- data/docs/RAILS.md +167 -0
- data/docs/README.md +16 -0
- data/docs/RELEASE_ACTION_PLAN.md +81 -0
- data/docs/RELEASE_AUDIT.md +158 -0
- data/docs/RELEASING.md +131 -0
- data/docs/SECURITY_MODEL.md +117 -0
- data/docs/SPEC_BASELINE.md +107 -0
- data/docs/WORK.md +260 -0
- data/docs/_advanced/live-backend-qualification.md +74 -0
- data/docs/_advanced/rails-integration.md +56 -0
- data/docs/_config.yml +109 -0
- data/docs/_core_features/inspect-extract.md +91 -0
- data/docs/_core_features/offline-act.md +110 -0
- data/docs/_core_features/operation-lifecycle.md +63 -0
- data/docs/_core_features/source-policy.md +61 -0
- data/docs/_data/navigation.yml +12 -0
- data/docs/_data/sidebar.yml +8 -0
- data/docs/_data/social_links.yml +3 -0
- data/docs/_getting_started/configuration.md +68 -0
- data/docs/_getting_started/getting-started.md +77 -0
- data/docs/_getting_started/overview.md +52 -0
- data/docs/_plugins/versioned_docs.rb +46 -0
- data/docs/_reference/api-reference.md +29 -0
- data/docs/_reference/release-history.md +23 -0
- data/docs/_reference/upgrading.md +33 -0
- data/docs/bin/build-api.sh +23 -0
- data/docs/bin/build.sh +161 -0
- data/docs/bin/export-api-markdown.rb +53 -0
- data/docs/bin/link_versions.rb +54 -0
- data/docs/bin/postprocess_api_seo.rb +129 -0
- data/docs/bin/prepare_versions.rb +40 -0
- data/docs/bin/serve.sh +12 -0
- data/docs/bin/versions.yml +14 -0
- data/docs/index.md +82 -0
- data/examples/plain_ruby.rb +34 -0
- data/examples/rails_adapter.rb +47 -0
- data/lib/generators/ruby_llm/browser/install_generator.rb +29 -0
- data/lib/generators/ruby_llm/browser/templates/create_ruby_llm_browser_tables.rb.tt +60 -0
- data/lib/ruby_llm/browser/approval.rb +128 -0
- data/lib/ruby_llm/browser/approval_store.rb +38 -0
- data/lib/ruby_llm/browser/backend.rb +99 -0
- data/lib/ruby_llm/browser/budget.rb +90 -0
- data/lib/ruby_llm/browser/capabilities.rb +76 -0
- data/lib/ruby_llm/browser/configuration.rb +74 -0
- data/lib/ruby_llm/browser/context.rb +117 -0
- data/lib/ruby_llm/browser/doctor.rb +134 -0
- data/lib/ruby_llm/browser/engine.rb +326 -0
- data/lib/ruby_llm/browser/errors.rb +86 -0
- data/lib/ruby_llm/browser/fake_backend.rb +134 -0
- data/lib/ruby_llm/browser/immutable.rb +19 -0
- data/lib/ruby_llm/browser/in_memory_approval_store.rb +67 -0
- data/lib/ruby_llm/browser/in_memory_operation_store.rb +174 -0
- data/lib/ruby_llm/browser/operation.rb +218 -0
- data/lib/ruby_llm/browser/operation_store.rb +92 -0
- data/lib/ruby_llm/browser/policy.rb +298 -0
- data/lib/ruby_llm/browser/rails/active_record_approval_store.rb +72 -0
- data/lib/ruby_llm/browser/rails/active_record_operation_store.rb +231 -0
- data/lib/ruby_llm/browser/rails/actor_record.rb +13 -0
- data/lib/ruby_llm/browser/rails/approval_record.rb +49 -0
- data/lib/ruby_llm/browser/rails/operation_job.rb +163 -0
- data/lib/ruby_llm/browser/rails/operation_record.rb +56 -0
- data/lib/ruby_llm/browser/rails/reaper.rb +117 -0
- data/lib/ruby_llm/browser/rails/schema.rb +81 -0
- data/lib/ruby_llm/browser/rails.rb +79 -0
- data/lib/ruby_llm/browser/redactor.rb +159 -0
- data/lib/ruby_llm/browser/result.rb +102 -0
- data/lib/ruby_llm/browser/stagehand_backend.rb +332 -0
- data/lib/ruby_llm/browser/tool.rb +70 -0
- data/lib/ruby_llm/browser/tools/act.rb +91 -0
- data/lib/ruby_llm/browser/tools/extract.rb +60 -0
- data/lib/ruby_llm/browser/tools/inspect.rb +38 -0
- data/lib/ruby_llm/browser/url_guard.rb +253 -0
- data/lib/ruby_llm/browser/version.rb +9 -0
- data/lib/ruby_llm/browser.rb +79 -0
- 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,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.
|