@stratusagent/tool-web 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.
- package/LICENSE +21 -0
- package/README.md +76 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +135 -0
- package/dist/index.js.map +1 -0
- package/dist/readability.d.ts +19 -0
- package/dist/readability.d.ts.map +1 -0
- package/dist/readability.js +87 -0
- package/dist/readability.js.map +1 -0
- package/package.json +91 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stratus Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# @stratusagent/tool-web
|
|
2
|
+
|
|
3
|
+
`web.fetch`: retrieve a URL and get back the text a reader would keep. No
|
|
4
|
+
browser, no JavaScript, no Chromium — this is what an agent reaches for
|
|
5
|
+
twenty times for every once it needs `browser.*`.
|
|
6
|
+
|
|
7
|
+
## Install and enable
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @stratusagent/tool-web
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```jsonc
|
|
14
|
+
// ~/.stratus/config.json — a trusted config only
|
|
15
|
+
{
|
|
16
|
+
"plugins": {
|
|
17
|
+
"@stratusagent/tool-web": { "enabled": true }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then the agent's soul decides, per identity:
|
|
23
|
+
|
|
24
|
+
```markdown
|
|
25
|
+
---
|
|
26
|
+
id: ava
|
|
27
|
+
tools: [web.fetch]
|
|
28
|
+
---
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Risk model
|
|
32
|
+
|
|
33
|
+
| Tool | Risk | What approval mode does |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| `web.fetch` | `gated` | `interactive` asks at the terminal, `remote` asks in Slack, `headless` refuses. It reaches a service outside Stratus at an address the agent chose, which is the line 03 draws. |
|
|
36
|
+
|
|
37
|
+
Approval decides *whether*; the address policy decides *where*, and it is
|
|
38
|
+
not the same question. An approver looking at `https://example.com/report`
|
|
39
|
+
has approved that URL — not the redirect it answers with.
|
|
40
|
+
|
|
41
|
+
## Settings
|
|
42
|
+
|
|
43
|
+
| Key | Default | What |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| `allowedHosts` | none | Hosts exempt from the address check, by name or literal address. The narrow override: one internal service an agent is meant to reach. |
|
|
46
|
+
| `allowPrivateAddresses` | `false` | Reach any non-global address. The trusted-workstation posture — it turns the SSRF protection off rather than adjusting it. |
|
|
47
|
+
| `maxBytes` | `400000` | Stop reading here; the result says `truncated`. |
|
|
48
|
+
| `timeoutMs` | `20000` | Give up on the whole exchange. |
|
|
49
|
+
| `maxRedirects` | `5` | Hops to follow before refusing. |
|
|
50
|
+
| `userAgent` | `StratusAgent/0.5 …` | What to send. |
|
|
51
|
+
|
|
52
|
+
All of them can be set per agent under `agents`, and `allowedHosts` in
|
|
53
|
+
particular should be: an exemption written once at the top level is an
|
|
54
|
+
exemption every agent gets.
|
|
55
|
+
|
|
56
|
+
## What it refuses, and where
|
|
57
|
+
|
|
58
|
+
The address policy is `@stratusagent/egress`, **the same module
|
|
59
|
+
`@stratusagent/tool-browser` uses** — not a copy of it. A second
|
|
60
|
+
implementation would not drift into a style difference; the stale one would
|
|
61
|
+
be an SSRF hole. Both packs are tested against the same table of hostile
|
|
62
|
+
URLs, which is what fails if the module is ever forked.
|
|
63
|
+
|
|
64
|
+
- **Schemes**: `http:` and `https:`. `file:`, `data:`, and `javascript:` are
|
|
65
|
+
refused before anything is fetched — this process can read the files a
|
|
66
|
+
`file:` URL names.
|
|
67
|
+
- **Addresses**: every non-global address in both families — loopback,
|
|
68
|
+
RFC 1918, carrier-NAT, link-local (where cloud instance metadata lives),
|
|
69
|
+
IPv6 unique-local and link-local, multicast, and the IPv4-mapped, NAT64,
|
|
70
|
+
and 6to4 forms that write the same addresses a different way.
|
|
71
|
+
- **Every hop**: redirects are followed one at a time and each one faces the
|
|
72
|
+
policy from scratch, so a public URL that answers `302 Location:
|
|
73
|
+
http://169.254.169.254/` is a refusal rather than a fetch.
|
|
74
|
+
- **The connection itself**: the socket's own DNS resolution is the one that
|
|
75
|
+
gets checked. Resolving a name to validate it and letting the client
|
|
76
|
+
resolve it again to connect is DNS rebinding with extra steps.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { JsonObject, Plugin } from '@stratusagent/core';
|
|
2
|
+
import { type EgressPolicy } from '@stratusagent/egress';
|
|
3
|
+
export { decodeEntities, extractTitle, htmlToText } from './readability.ts';
|
|
4
|
+
export interface WebPluginConfig extends JsonObject {
|
|
5
|
+
/** Reach addresses that are not globally routable. See the README. */
|
|
6
|
+
allowPrivateAddresses?: boolean;
|
|
7
|
+
/** Hosts exempt from the address check, by name or literal address. */
|
|
8
|
+
allowedHosts?: string[];
|
|
9
|
+
maxBytes?: number;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
maxRedirects?: number;
|
|
12
|
+
userAgent?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Fetch a URL, following redirects **one validated hop at a time**.
|
|
16
|
+
*
|
|
17
|
+
* The loop is here rather than in the HTTP client for one reason: every hop
|
|
18
|
+
* is a new request to a new host, and a client that followed redirects for
|
|
19
|
+
* us would resolve and connect to hosts nothing checked. An approved public
|
|
20
|
+
* URL that answers `302 Location: http://169.254.169.254/` is exactly the
|
|
21
|
+
* attack, and it is defeated by the second hop facing the policy the same
|
|
22
|
+
* way the first one did.
|
|
23
|
+
*/
|
|
24
|
+
export declare const fetchThroughPolicy: (rawUrl: string, options?: {
|
|
25
|
+
policy?: EgressPolicy;
|
|
26
|
+
maxBytes?: number;
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
maxRedirects?: number;
|
|
29
|
+
userAgent?: string;
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
}) => Promise<{
|
|
32
|
+
url: string;
|
|
33
|
+
status: number;
|
|
34
|
+
contentType: string;
|
|
35
|
+
body: string;
|
|
36
|
+
truncated: boolean;
|
|
37
|
+
hops: string[];
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* The `web` toolset: one tool, deliberately.
|
|
41
|
+
*
|
|
42
|
+
* This is the capability an agent reaches for twenty times for every once
|
|
43
|
+
* it needs a browser, and paying Chromium's startup and memory for it is
|
|
44
|
+
* the wrong trade. Search is not here and is not coming: every backend
|
|
45
|
+
* needs a vendor key and a commercial relationship, so `web.search` belongs
|
|
46
|
+
* to the ecosystem.
|
|
47
|
+
*/
|
|
48
|
+
export declare const createWebPlugin: (config?: JsonObject) => Plugin;
|
|
49
|
+
/** The loader's ABI. See `docs/architecture/plugins.md`. */
|
|
50
|
+
export declare const createPlugin: (config?: JsonObject) => Plugin;
|
|
51
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAiB,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,sBAAsB,CAAC;AAK9B,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAO5E,MAAM,WAAW,eAAgB,SAAQ,UAAU;IACjD,sEAAsE;IACtE,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA8BD;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,MAAM,EACd,UAAS;IACP,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACjB,KACL,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAmChH,CAAC;AAkDF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,SAAQ,UAAe,KAAG,MAKxD,CAAC;AAEH,4DAA4D;AAC5D,eAAO,MAAM,YAAY,YARe,UAAU,KAAQ,MAQf,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { assertRequestAllowed, requestThroughPolicy, } from '@stratusagent/egress';
|
|
2
|
+
import { resolvePluginAgentConfig } from '@stratusagent/plugins';
|
|
3
|
+
import { extractTitle, htmlToText } from "./readability.js";
|
|
4
|
+
export { decodeEntities, extractTitle, htmlToText } from "./readability.js";
|
|
5
|
+
const DEFAULT_MAX_BYTES = 400_000;
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 20_000;
|
|
7
|
+
const DEFAULT_MAX_REDIRECTS = 5;
|
|
8
|
+
const DEFAULT_USER_AGENT = 'StratusAgent/0.5 (+https://github.com/stratuslabs/agent)';
|
|
9
|
+
const asNumber = (value, fallback) => typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
10
|
+
const settingsFor = (config, session) => {
|
|
11
|
+
// Per call, from the session: an agent allowed to reach an internal host
|
|
12
|
+
// is a decision about that agent, and closing over it at setup would
|
|
13
|
+
// give the exemption to everyone.
|
|
14
|
+
const resolved = resolvePluginAgentConfig(config, session.agent.id);
|
|
15
|
+
const policy = {
|
|
16
|
+
...(resolved.allowPrivateAddresses === true ? { allowPrivateAddresses: true } : {}),
|
|
17
|
+
...(Array.isArray(resolved.allowedHosts)
|
|
18
|
+
? { allowedHosts: resolved.allowedHosts.filter((entry) => typeof entry === 'string') }
|
|
19
|
+
: {}),
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
policy,
|
|
23
|
+
maxBytes: asNumber(resolved.maxBytes, DEFAULT_MAX_BYTES),
|
|
24
|
+
timeoutMs: asNumber(resolved.timeoutMs, DEFAULT_TIMEOUT_MS),
|
|
25
|
+
maxRedirects: asNumber(resolved.maxRedirects, DEFAULT_MAX_REDIRECTS),
|
|
26
|
+
userAgent: typeof resolved.userAgent === 'string' ? resolved.userAgent : DEFAULT_USER_AGENT,
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
const isRedirect = (status) => status >= 300 && status < 400;
|
|
30
|
+
const contentTypeOf = (headers) => String(headers['content-type'] ?? '').toLowerCase();
|
|
31
|
+
/**
|
|
32
|
+
* Fetch a URL, following redirects **one validated hop at a time**.
|
|
33
|
+
*
|
|
34
|
+
* The loop is here rather than in the HTTP client for one reason: every hop
|
|
35
|
+
* is a new request to a new host, and a client that followed redirects for
|
|
36
|
+
* us would resolve and connect to hosts nothing checked. An approved public
|
|
37
|
+
* URL that answers `302 Location: http://169.254.169.254/` is exactly the
|
|
38
|
+
* attack, and it is defeated by the second hop facing the policy the same
|
|
39
|
+
* way the first one did.
|
|
40
|
+
*/
|
|
41
|
+
export const fetchThroughPolicy = async (rawUrl, options = {}) => {
|
|
42
|
+
const maxRedirects = options.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
|
|
43
|
+
const hops = [];
|
|
44
|
+
let current = assertRequestAllowed(rawUrl, options.policy ?? {}).href;
|
|
45
|
+
for (let hop = 0; hop <= maxRedirects; hop += 1) {
|
|
46
|
+
hops.push(current);
|
|
47
|
+
const response = await requestThroughPolicy(current, {
|
|
48
|
+
...(options.policy ? { policy: options.policy } : {}),
|
|
49
|
+
headers: { 'user-agent': options.userAgent ?? DEFAULT_USER_AGENT, accept: 'text/html,text/plain;q=0.9,*/*;q=0.5' },
|
|
50
|
+
...(options.maxBytes === undefined ? {} : { maxBytes: options.maxBytes }),
|
|
51
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
52
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
53
|
+
});
|
|
54
|
+
const location = response.headers.location;
|
|
55
|
+
if (isRedirect(response.status) && typeof location === 'string' && location.length > 0) {
|
|
56
|
+
// Resolved against the current URL, then checked from scratch — a
|
|
57
|
+
// relative redirect is still a new destination.
|
|
58
|
+
const next = new URL(location, current).href;
|
|
59
|
+
current = assertRequestAllowed(next, options.policy ?? {}).href;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
url: current,
|
|
64
|
+
status: response.status,
|
|
65
|
+
contentType: contentTypeOf(response.headers),
|
|
66
|
+
body: response.body,
|
|
67
|
+
truncated: response.truncated,
|
|
68
|
+
hops,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
throw new Error(`Too many redirects (${maxRedirects}) starting at ${rawUrl}`);
|
|
72
|
+
};
|
|
73
|
+
const createFetchTool = (config) => ({
|
|
74
|
+
name: 'web.fetch',
|
|
75
|
+
description: 'Retrieve a URL and return its readable text. No browser, no JavaScript.',
|
|
76
|
+
// Gated: it reaches a service outside Stratus, on an address an agent
|
|
77
|
+
// chose. The address policy decides *where*; approval decides *whether*.
|
|
78
|
+
risk: 'gated',
|
|
79
|
+
parameters: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {
|
|
82
|
+
url: { type: 'string' },
|
|
83
|
+
maxBytes: { type: 'number' },
|
|
84
|
+
raw: { type: 'boolean', description: 'Return the body as received, without text extraction.' },
|
|
85
|
+
},
|
|
86
|
+
required: ['url'],
|
|
87
|
+
},
|
|
88
|
+
async execute(input, session, context) {
|
|
89
|
+
const url = typeof input.url === 'string' ? input.url : '';
|
|
90
|
+
if (!url) {
|
|
91
|
+
throw new Error('url is required.');
|
|
92
|
+
}
|
|
93
|
+
const settings = settingsFor(config, session);
|
|
94
|
+
const response = await fetchThroughPolicy(url, {
|
|
95
|
+
policy: settings.policy,
|
|
96
|
+
maxBytes: asNumber(input.maxBytes, settings.maxBytes),
|
|
97
|
+
timeoutMs: settings.timeoutMs,
|
|
98
|
+
maxRedirects: settings.maxRedirects,
|
|
99
|
+
userAgent: settings.userAgent,
|
|
100
|
+
...(context?.signal ? { signal: context.signal } : {}),
|
|
101
|
+
});
|
|
102
|
+
const html = response.contentType.includes('html');
|
|
103
|
+
const text = input.raw === true || !html ? response.body : htmlToText(response.body);
|
|
104
|
+
const title = html ? extractTitle(response.body) : undefined;
|
|
105
|
+
return {
|
|
106
|
+
url: response.url,
|
|
107
|
+
status: response.status,
|
|
108
|
+
contentType: response.contentType,
|
|
109
|
+
...(title ? { title } : {}),
|
|
110
|
+
// Named `redirects` rather than left implicit: an agent that followed
|
|
111
|
+
// a link somewhere else should be able to say where it ended up.
|
|
112
|
+
...(response.hops.length > 1 ? { redirects: response.hops } : {}),
|
|
113
|
+
text,
|
|
114
|
+
truncated: response.truncated,
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
/**
|
|
119
|
+
* The `web` toolset: one tool, deliberately.
|
|
120
|
+
*
|
|
121
|
+
* This is the capability an agent reaches for twenty times for every once
|
|
122
|
+
* it needs a browser, and paying Chromium's startup and memory for it is
|
|
123
|
+
* the wrong trade. Search is not here and is not coming: every backend
|
|
124
|
+
* needs a vendor key and a commercial relationship, so `web.search` belongs
|
|
125
|
+
* to the ecosystem.
|
|
126
|
+
*/
|
|
127
|
+
export const createWebPlugin = (config = {}) => ({
|
|
128
|
+
name: '@stratusagent/tool-web',
|
|
129
|
+
setup(context) {
|
|
130
|
+
context.tools.register(createFetchTool(config));
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
/** The loader's ABI. See `docs/architecture/plugins.md`. */
|
|
134
|
+
export const createPlugin = createWebPlugin;
|
|
135
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML to the text a reader would keep.
|
|
3
|
+
*
|
|
4
|
+
* Not a parser and not trying to be one: an agent asked for a page's
|
|
5
|
+
* content, and what it must not receive is fifty kilobytes of navigation,
|
|
6
|
+
* script, and styling with three sentences somewhere inside. So the
|
|
7
|
+
* elements that never carry the article are dropped whole, block
|
|
8
|
+
* boundaries become newlines, and everything else collapses to text.
|
|
9
|
+
*
|
|
10
|
+
* A real DOM would do better on a hostile page. It would also be a
|
|
11
|
+
* dependency, a parse of untrusted markup, and a second answer to what a
|
|
12
|
+
* page "is" — for a fetcher whose output is fed to a model as prose, this
|
|
13
|
+
* trade is the right one, and the failure mode is a worse extraction
|
|
14
|
+
* rather than an incorrect one.
|
|
15
|
+
*/
|
|
16
|
+
export declare const decodeEntities: (value: string) => string;
|
|
17
|
+
export declare const extractTitle: (html: string) => string | undefined;
|
|
18
|
+
export declare const htmlToText: (html: string) => string;
|
|
19
|
+
//# sourceMappingURL=readability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readability.d.ts","sourceRoot":"","sources":["../src/readability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAuCH,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,KAAG,MAW3C,CAAC;AAEL,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MAAM,GAAG,SAGpD,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,MAwBzC,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML to the text a reader would keep.
|
|
3
|
+
*
|
|
4
|
+
* Not a parser and not trying to be one: an agent asked for a page's
|
|
5
|
+
* content, and what it must not receive is fifty kilobytes of navigation,
|
|
6
|
+
* script, and styling with three sentences somewhere inside. So the
|
|
7
|
+
* elements that never carry the article are dropped whole, block
|
|
8
|
+
* boundaries become newlines, and everything else collapses to text.
|
|
9
|
+
*
|
|
10
|
+
* A real DOM would do better on a hostile page. It would also be a
|
|
11
|
+
* dependency, a parse of untrusted markup, and a second answer to what a
|
|
12
|
+
* page "is" — for a fetcher whose output is fed to a model as prose, this
|
|
13
|
+
* trade is the right one, and the failure mode is a worse extraction
|
|
14
|
+
* rather than an incorrect one.
|
|
15
|
+
*/
|
|
16
|
+
const DROPPED_ELEMENTS = [
|
|
17
|
+
'script',
|
|
18
|
+
'style',
|
|
19
|
+
'noscript',
|
|
20
|
+
'template',
|
|
21
|
+
'svg',
|
|
22
|
+
'canvas',
|
|
23
|
+
'iframe',
|
|
24
|
+
'nav',
|
|
25
|
+
'header',
|
|
26
|
+
'footer',
|
|
27
|
+
'aside',
|
|
28
|
+
'form',
|
|
29
|
+
];
|
|
30
|
+
const BLOCK_ELEMENTS = [
|
|
31
|
+
'p', 'div', 'section', 'article', 'main', 'br', 'hr',
|
|
32
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
33
|
+
'ul', 'ol', 'table', 'tr', 'blockquote', 'pre',
|
|
34
|
+
];
|
|
35
|
+
const NAMED_ENTITIES = {
|
|
36
|
+
amp: '&',
|
|
37
|
+
lt: '<',
|
|
38
|
+
gt: '>',
|
|
39
|
+
quot: '"',
|
|
40
|
+
apos: "'",
|
|
41
|
+
nbsp: ' ',
|
|
42
|
+
mdash: '—',
|
|
43
|
+
ndash: '–',
|
|
44
|
+
hellip: '…',
|
|
45
|
+
rsquo: '’',
|
|
46
|
+
lsquo: '‘',
|
|
47
|
+
ldquo: '“',
|
|
48
|
+
rdquo: '”',
|
|
49
|
+
};
|
|
50
|
+
export const decodeEntities = (value) => value.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (match, entity) => {
|
|
51
|
+
if (entity.startsWith('#x') || entity.startsWith('#X')) {
|
|
52
|
+
const code = Number.parseInt(entity.slice(2), 16);
|
|
53
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : match;
|
|
54
|
+
}
|
|
55
|
+
if (entity.startsWith('#')) {
|
|
56
|
+
const code = Number.parseInt(entity.slice(1), 10);
|
|
57
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : match;
|
|
58
|
+
}
|
|
59
|
+
return NAMED_ENTITIES[entity.toLowerCase()] ?? match;
|
|
60
|
+
});
|
|
61
|
+
export const extractTitle = (html) => {
|
|
62
|
+
const match = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
|
|
63
|
+
return match?.[1] ? decodeEntities(match[1]).trim() : undefined;
|
|
64
|
+
};
|
|
65
|
+
export const htmlToText = (html) => {
|
|
66
|
+
let text = html.replace(/<!--[\s\S]*?-->/g, '');
|
|
67
|
+
for (const element of DROPPED_ELEMENTS) {
|
|
68
|
+
text = text.replace(new RegExp(`<${element}\\b[^>]*>[\\s\\S]*?</${element}>`, 'gi'), ' ');
|
|
69
|
+
// Unclosed or self-closing forms of the same elements.
|
|
70
|
+
text = text.replace(new RegExp(`<${element}\\b[^>]*/?>`, 'gi'), ' ');
|
|
71
|
+
}
|
|
72
|
+
text = text.replace(/<head\b[^>]*>[\s\S]*?<\/head>/gi, ' ');
|
|
73
|
+
text = text.replace(/<li\b[^>]*>/gi, '\n- ');
|
|
74
|
+
for (const element of BLOCK_ELEMENTS) {
|
|
75
|
+
text = text.replace(new RegExp(`</?${element}\\b[^>]*>`, 'gi'), '\n');
|
|
76
|
+
}
|
|
77
|
+
text = text.replace(/<[^>]+>/g, ' ');
|
|
78
|
+
text = decodeEntities(text);
|
|
79
|
+
return text
|
|
80
|
+
.split('\n')
|
|
81
|
+
.map((line) => line.replace(/[ \t ]+/g, ' ').trim())
|
|
82
|
+
.filter((line, index, lines) => line.length > 0 || (lines[index - 1] ?? '').length > 0)
|
|
83
|
+
.join('\n')
|
|
84
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
85
|
+
.trim();
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=readability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readability.js","sourceRoot":"","sources":["../src/readability.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stratusagent/tool-web",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22.13 <23 || >=23.4"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@stratusagent/core": "0.6.0",
|
|
22
|
+
"@stratusagent/egress": "0.6.0",
|
|
23
|
+
"@stratusagent/plugins": "0.6.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^24.6.0",
|
|
27
|
+
"typescript": "^5.9.3"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/stratuslabs/agent.git",
|
|
33
|
+
"directory": "packages/tool-web"
|
|
34
|
+
},
|
|
35
|
+
"description": "Web fetching for Stratus agents: retrieve a URL as readable text, with no browser and a shared address policy",
|
|
36
|
+
"keywords": [
|
|
37
|
+
"stratus",
|
|
38
|
+
"agent",
|
|
39
|
+
"ai",
|
|
40
|
+
"llm",
|
|
41
|
+
"stratus-plugin",
|
|
42
|
+
"tools",
|
|
43
|
+
"web"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"stratus": {
|
|
49
|
+
"pluginVersion": 1,
|
|
50
|
+
"contributes": {
|
|
51
|
+
"tools": [
|
|
52
|
+
{
|
|
53
|
+
"name": "web.fetch",
|
|
54
|
+
"risk": "gated"
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
"config": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"properties": {
|
|
61
|
+
"allowPrivateAddresses": {
|
|
62
|
+
"type": "boolean"
|
|
63
|
+
},
|
|
64
|
+
"allowedHosts": {
|
|
65
|
+
"type": "array",
|
|
66
|
+
"items": {
|
|
67
|
+
"type": "string"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"maxBytes": {
|
|
71
|
+
"type": "integer"
|
|
72
|
+
},
|
|
73
|
+
"timeoutMs": {
|
|
74
|
+
"type": "integer"
|
|
75
|
+
},
|
|
76
|
+
"maxRedirects": {
|
|
77
|
+
"type": "integer"
|
|
78
|
+
},
|
|
79
|
+
"userAgent": {
|
|
80
|
+
"type": "string"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"additionalProperties": false
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"scripts": {
|
|
87
|
+
"build": "tsc -b",
|
|
88
|
+
"typecheck": "tsc --noEmit --project tsconfig.test.json",
|
|
89
|
+
"test": "node --test --experimental-strip-types"
|
|
90
|
+
}
|
|
91
|
+
}
|