@qilin-agent/web-fetch-http 0.0.0-stage → 3.1.3

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.
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Anonymous public HTTP(S) `WebFetchProvider` plugin. It contributes to the
3
+ * `ctx.web` registry without owning the service.
4
+ *
5
+ * @module @qilin-agent/web-fetch-http
6
+ */
7
+ import type { Context } from '@qilin-agent/kylin';
8
+ import z from '@qilin-agent/schemastery';
9
+ export { LOCAL_FETCH_PROVIDER_ID, HttpFetchProvider, } from './provider.ts';
10
+ export type { HttpFetchLimits, HttpFetchResolver } from './provider.ts';
11
+ /** Default `User-Agent`: an explicit product agent, never a browser disguise. */
12
+ export declare const DEFAULT_USER_AGENT = "deepseek-harness/0.0.1 (+https://github.com/deepseek-ai)";
13
+ /** Cordis plugin name used by loader diagnostics. */
14
+ export declare const name = "web-fetch-http";
15
+ /** The web seam this provider registers into. */
16
+ export declare const inject: string[];
17
+ /** Plugin config: the provider's transport and size limits plus its `User-Agent` (all defaulted). */
18
+ export interface Config {
19
+ /** Maximum response body size in bytes. */
20
+ maxResponseBytes?: number;
21
+ /** Maximum decoded body length in characters. */
22
+ maxBodyChars?: number;
23
+ /** Default fetch timeout in milliseconds, within Node's timer range. */
24
+ timeoutMs?: number;
25
+ /** Maximum number of same-origin redirect hops to follow. */
26
+ maxRedirects?: number;
27
+ /** `User-Agent` header sent on every request. */
28
+ userAgent?: string;
29
+ }
30
+ export declare const Config: z<Config>;
31
+ /** Register the local HTTP(S) fetch provider with `ctx.web`. */
32
+ export declare function apply(ctx: Context, config: Config): void;
33
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Public-network resolution and address-pinned HTTP transport for `web-fetch-http`.
3
+ * One DNS answer set is validated before Undici receives it through a custom lookup,
4
+ * so the connection cannot resolve the hostname again to a private address.
5
+ *
6
+ * @module @qilin-agent/web-fetch-http/network
7
+ */
8
+ import type { LookupAddress, LookupOptions } from 'node:dns';
9
+ import type { Dispatcher, Response } from 'undici';
10
+ /** One address resolved and retained for the subsequent pinned connection. */
11
+ export interface PublicAddress {
12
+ /** Canonical textual IPv4 or IPv6 address. */
13
+ readonly address: string;
14
+ /** Address family accepted by Node's connection lookup callback. */
15
+ readonly family: 4 | 6;
16
+ }
17
+ /** The result of one address-pinned request; closing releases its private pool. */
18
+ export interface PinnedResponse {
19
+ /** HTTP response whose body remains readable until `close()` is called. */
20
+ readonly response: Response;
21
+ /** Release the request's dispatcher after the response body is consumed or cancelled. */
22
+ close(): Promise<void>;
23
+ }
24
+ /** Resolver signature used to test public-address policy without process DNS changes. */
25
+ export type AddressResolver = (hostname: string, options: {
26
+ all: true;
27
+ order: 'verbatim';
28
+ }) => Promise<LookupAddress[]>;
29
+ /**
30
+ * Return whether an address is globally reachable unicast. IPv4-mapped IPv6 is
31
+ * classified by its embedded IPv4 address; transition and translation prefixes
32
+ * remain blocked because their eventual IPv4 destination cannot be pinned here.
33
+ *
34
+ * @param input - textual IPv4 or IPv6 address.
35
+ * @returns true only for a public unicast destination.
36
+ */
37
+ export declare function isPublicIpAddress(input: string): boolean;
38
+ /**
39
+ * Resolve a hostname once and reject the complete answer set if any destination
40
+ * is not public. The returned addresses are the only ones the transport may use.
41
+ *
42
+ * @param hostname - URL hostname, including brackets when it is an IPv6 literal.
43
+ * @param signal - aborts the wait for system resolution; an in-flight OS lookup may finish unused.
44
+ * @param resolver - lookup implementation, overridden only by focused tests.
45
+ * @returns the validated, non-empty address set.
46
+ */
47
+ export declare function resolvePublicAddresses(hostname: string, signal: AbortSignal, resolver?: AddressResolver): Promise<PublicAddress[]>;
48
+ /**
49
+ * Whether a hostname is an IP literal that {@link resolvePublicAddresses} would refuse.
50
+ *
51
+ * A proxied hop skips those checks because the proxy resolves the origin, but a literal needs no
52
+ * resolution: the address is already stated, and handing it to a proxy running on this machine
53
+ * would reach exactly the loopback or private service the checks exist to keep out of reach.
54
+ *
55
+ * @param hostname - a URL's hostname, bracketed or not.
56
+ * @returns true when the host is a literal address no request may be sent to.
57
+ */
58
+ export declare function isNonPublicIpLiteral(hostname: string): boolean;
59
+ /**
60
+ * Fetch through an agent whose lookup callback returns only the already validated address set. The
61
+ * URL hostname remains intact for HTTP Host and TLS SNI.
62
+ *
63
+ * The agent is this request's own because the address set is: pinning is how this package refuses a
64
+ * DNS answer that changes between validation and connection, and it may not apply process-wide —
65
+ * an operator-configured MCP server or model endpoint on loopback is a supported destination, and
66
+ * only the URLs this tool fetches are the model's to choose.
67
+ *
68
+ * @param url - validated HTTP(S) URL the policy does not route through a proxy.
69
+ * @param addresses - public addresses returned by {@link resolvePublicAddresses}.
70
+ * @param headers - request headers.
71
+ * @param signal - request and body-read cancellation signal.
72
+ * @returns a response plus the disposer its consumer must call.
73
+ */
74
+ export declare function requestPinned(url: URL, addresses: readonly PublicAddress[], headers: Record<string, string>, signal: AbortSignal): Promise<PinnedResponse>;
75
+ /**
76
+ * Fetch through the dispatcher the proxy policy already installed, letting the proxy resolve the
77
+ * origin.
78
+ *
79
+ * No address set is pinned because none exists to pin: the proxy performs the lookup, and a
80
+ * connection pinned to a locally resolved address would reach the origin directly and defeat the
81
+ * proxy. The dispatcher is the process-wide one, so hops share its connection pool and no caller
82
+ * closes it.
83
+ *
84
+ * @param dispatcher - the route's dispatcher, from `proxyRouteFor`.
85
+ * @param url - validated HTTP(S) URL the policy routes through a proxy.
86
+ * @param headers - request headers.
87
+ * @param signal - request and body-read cancellation signal.
88
+ * @returns a response plus a disposer that releases nothing, so both paths close alike.
89
+ */
90
+ export declare function requestVia(dispatcher: Dispatcher, url: URL, headers: Record<string, string>, signal: AbortSignal): Promise<PinnedResponse>;
91
+ /** Production network operations kept as an object so provider tests can replace resolution only. */
92
+ export declare const publicHttpNetwork: {
93
+ resolve: typeof resolvePublicAddresses;
94
+ request: typeof requestPinned;
95
+ requestVia: typeof requestVia;
96
+ };
97
+ type LookupCallback = (error: NodeJS.ErrnoException | null, address: string | LookupAddress[], family?: number) => void;
98
+ /**
99
+ * Build the connector lookup that serves a fixed validated answer set.
100
+ *
101
+ * @param addresses - public addresses retained from the preceding resolution.
102
+ * @returns a Node-compatible lookup callback that performs no network resolution.
103
+ */
104
+ export declare function createPinnedLookup(addresses: readonly PublicAddress[]): (hostname: string, options: LookupOptions, callback: LookupCallback) => void;
105
+ export {};
106
+ //# sourceMappingURL=network.d.ts.map
@@ -0,0 +1,72 @@
1
+ /**
2
+ * URL validation and content-type classification for the local HTTP(S) fetch
3
+ * provider — the pure, network-free half. The provider's `fetch()` composes
4
+ * these with transport (redirect following, byte caps, decoding).
5
+ *
6
+ * @module @qilin-agent/web-fetch-http/policy
7
+ */
8
+ /** Maximum accepted request URL length enforced by the public fetch provider. */
9
+ export declare const WEB_FETCH_MAX_URL_LENGTH = 2048;
10
+ /** The body kinds this provider decodes. */
11
+ export type FetchableKind = 'html' | 'text';
12
+ /**
13
+ * Parse a request URL and enforce network-independent transport restrictions:
14
+ * HTTP(S) only and no embedded credentials. The provider applies this before
15
+ * resolving a destination.
16
+ *
17
+ * @param input - the raw URL string from the fetch request.
18
+ * @returns the parsed `URL`.
19
+ */
20
+ export declare function parseFetchUrl(input: string): URL;
21
+ /**
22
+ * Validate a request URL against the provider's complete pre-network policy:
23
+ * bounded length plus the restrictions enforced by {@link parseFetchUrl}.
24
+ * Public-address resolution and connection pinning run after this check.
25
+ *
26
+ * @param input - the raw URL string from the fetch request.
27
+ * @returns the parsed `URL`.
28
+ */
29
+ export declare function validateFetchUrl(input: string): URL;
30
+ /**
31
+ * Two URLs are same-origin when scheme, hostname, and port match. A redirect
32
+ * that crosses origins is refused so each new origin requires a fresh tool call
33
+ * and public-address validation.
34
+ *
35
+ * @param a - one of the two URLs to compare.
36
+ * @param b - the other URL to compare.
37
+ * @returns true when `a` and `b` share scheme, hostname, and port.
38
+ */
39
+ export declare function isSameOrigin(a: URL, b: URL): boolean;
40
+ /**
41
+ * Classify a response `Content-Type` into a decodable body kind, or `undefined`
42
+ * for an unsupported (e.g. binary) type. `text/html` and `application/xhtml+xml`
43
+ * are `html`; other `text/*` plus a few structured text types are `text`.
44
+ *
45
+ * @param contentType - the raw `Content-Type` header, or `null` when the
46
+ * response carries none (unsupported).
47
+ * @returns the decodable kind, or `undefined` for an unsupported type.
48
+ */
49
+ export declare function classifyContentType(contentType: string | null): FetchableKind | undefined;
50
+ /**
51
+ * Extract the `charset` parameter from a response `Content-Type`, lower-cased,
52
+ * or `undefined` when absent. The provider feeds this label to `TextDecoder`
53
+ * so a non-UTF-8 response is decoded with its declared encoding rather than
54
+ * silently mangled into replacement characters.
55
+ *
56
+ * @param contentType - the raw `Content-Type` header, or `null` when the
57
+ * response carries none.
58
+ * @returns the lower-cased charset label, or `undefined` when none is declared.
59
+ */
60
+ export declare function parseCharset(contentType: string | null): string | undefined;
61
+ /**
62
+ * Build a `TextDecoder` for the declared charset, falling back to UTF-8 when
63
+ * none is declared. Throws {@link WebError} `WEB_UNSUPPORTED_CONTENT_TYPE` when
64
+ * the label is present but not a charset `TextDecoder` recognizes — better to
65
+ * fail loudly than return mojibake.
66
+ *
67
+ * @param charset - the declared charset label (from {@link parseCharset}), or
68
+ * `undefined` to default to UTF-8.
69
+ * @returns a decoder for the declared (or defaulted) encoding.
70
+ */
71
+ export declare function decoderForCharset(charset: string | undefined): TextDecoder;
72
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Safe HTTP(S) retrieval for `ctx.web`: validates and pins public IP destinations, follows
3
+ * only same-origin redirects, enforces time and size limits, classifies and decodes text,
4
+ * and leaves presentation to `@qilin-agent/tool-web`. Requests carry no browser cookies
5
+ * or ambient credentials.
6
+ * @module @qilin-agent/web-fetch-http/provider
7
+ */
8
+ import type { WebFetchProvider, WebFetchRequest, WebFetchResult } from '@qilin-agent/web';
9
+ import type { PublicAddress } from './network.ts';
10
+ /** Resolved provider limits (the plugin's schemastery Config supplies defaults). */
11
+ export interface HttpFetchLimits {
12
+ /** Maximum response body size in bytes (read is aborted past this). */
13
+ maxResponseBytes: number;
14
+ /** Maximum decoded body length in characters (truncated past this). */
15
+ maxBodyChars: number;
16
+ /** Default fetch timeout in milliseconds. */
17
+ timeoutMs: number;
18
+ /** Maximum number of (same-origin) redirect hops to follow. */
19
+ maxRedirects: number;
20
+ /** `User-Agent` header sent on every request. */
21
+ userAgent: string;
22
+ }
23
+ /** Resolve one hostname to an already policy-validated address set. */
24
+ export type HttpFetchResolver = (hostname: string, signal: AbortSignal) => Promise<PublicAddress[]>;
25
+ /** Stable id this provider registers under. */
26
+ export declare const LOCAL_FETCH_PROVIDER_ID = "http";
27
+ /** The anonymous public HTTP(S) fetch provider. */
28
+ export declare class HttpFetchProvider implements WebFetchProvider {
29
+ private readonly limits;
30
+ private readonly resolveAddresses;
31
+ readonly id = "http";
32
+ /**
33
+ * @param limits - resolved transport and response limits.
34
+ * @param resolveAddresses - resolver that rejects non-public destinations before returning.
35
+ */
36
+ constructor(limits: HttpFetchLimits, resolveAddresses?: HttpFetchResolver);
37
+ /** No credentials to check — an anonymous public fetcher is always usable. */
38
+ available(): boolean;
39
+ fetch(request: WebFetchRequest, signal?: AbortSignal): Promise<WebFetchResult>;
40
+ /** Follow same-origin redirects up to the hop cap, then read the final response. */
41
+ private followAndRead;
42
+ private requestOnce;
43
+ /** Read, byte-cap, classify, and decode the final response body. */
44
+ private readBody;
45
+ /**
46
+ * Read the response stream up to `maxResponseBytes`. A `Content-Length` over
47
+ * the cap rejects immediately with `WEB_FETCH_TOO_LARGE`; a stream that grows
48
+ * past the cap is cut short (`truncatedByBytes`) rather than rejected, so a
49
+ * server that under-reports still yields a bounded usable body.
50
+ */
51
+ private readCapped;
52
+ }
53
+ //# sourceMappingURL=provider.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,49 @@
1
1
  {
2
2
  "name": "@qilin-agent/web-fetch-http",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "description": "Anonymous public HTTP(S) fetch provider for the QiLin web capability seam (ctx.web)",
4
+ "version": "3.1.3",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/kkutysllb/QiLin.git",
11
+ "directory": "packages/web/web-fetch-http"
12
+ },
13
+ "engines": {
14
+ "node": "^22.19.0 || >=24.0.0"
15
+ },
16
+ "type": "module",
17
+ "main": "lib/index.js",
18
+ "types": "lib/types/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./lib/types/index.d.ts",
22
+ "default": "./lib/index.js"
23
+ },
24
+ "./src/*": "./src/*",
25
+ "./package.json": "./package.json"
26
+ },
27
+ "files": [
28
+ "lib/index.js",
29
+ "lib/types/**/*.d.ts"
30
+ ],
31
+ "license": "MIT",
32
+ "peerDependencies": {
33
+ "@qilin-agent/kylin": "~4.0.3",
34
+ "@qilin-agent/timeout": "3.1.3",
35
+ "@qilin-agent/web": "3.1.3",
36
+ "@qilin-agent/http-proxy": "3.1.3"
37
+ },
38
+ "dependencies": {
39
+ "ipaddr.js": "^2.5.0",
40
+ "undici": "^8.10.0",
41
+ "@qilin-agent/schemastery": "~3.18.3"
42
+ },
43
+ "devDependencies": {
44
+ "@qilin-agent/kylin": "~4.0.3",
45
+ "@qilin-agent/timeout": "3.1.3",
46
+ "@qilin-agent/web": "3.1.3",
47
+ "@qilin-agent/http-proxy": "3.1.3"
48
+ }
6
49
  }