@jini-ai/platform 0.5.6 → 0.5.8
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @jini-ai/platform
|
|
2
2
|
|
|
3
|
+
## 0.5.8 — 2026-10-15
|
|
4
|
+
|
|
5
|
+
- SECURITY: the guarded HTTP client forwards only `accept`, `accept-language` and `user-agent` on a cross-origin redirect (4de69b67). It used to strip only Authorization/Cookie, so custom credential headers (`x-api-key`, `X-Shopify-Access-Token`, …) followed a redirect to another origin. Same-origin hops keep all headers.
|
|
6
|
+
- `./web` page fetch applies `maxChars` to the readable text, not the raw HTML (0625844e): raw HTML uses the fixed 1,000,000-char parser ceiling, and the result says `truncated: true` when cut. A page with a large script/style head no longer returns almost no text.
|
|
7
|
+
|
|
8
|
+
## 0.5.7 — 2026-10-14
|
|
9
|
+
|
|
10
|
+
- The asset cache's optional `undici` load moves behind `asset-cache.adapter.ts` (64eafe35); injected dispatchers, DNS validation and the MODULE_NOT_FOUND message are unchanged. No API change.
|
|
11
|
+
|
|
3
12
|
## 0.5.6 — 2026-10-13
|
|
4
13
|
|
|
5
14
|
- `undici` and `hast-util-from-parse5`, `hast-util-to-mdast`, `mdast-util-to-markdown`, `mdast-util-gfm-table` (with `parse5`) are now optional peers instead of dependencies. Only `./web` needs the HTML/Markdown set; only the default `createAssetCacheDispatcher` needs undici, loaded lazily (a missing peer throws `AssetCacheError` 500; a host-supplied dispatcher needs none). Hosts using those must list the packages themselves.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LookupFunction } from 'node:net';
|
|
2
|
+
import type { Agent } from 'undici';
|
|
3
|
+
/** Load the optional vendor only for a default dispatcher; the cache owns lookup validation.
|
|
4
|
+
* @throws When the optional peer cannot be loaded or its Agent cannot be constructed.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createUndiciAssetCacheDispatcher({ lookup }: {
|
|
7
|
+
lookup: LookupFunction;
|
|
8
|
+
}, _options?: Record<string, never>): Agent;
|
|
9
|
+
//# sourceMappingURL=asset-cache.adapter.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
const require = createRequire(import.meta.url);
|
|
3
|
+
/** Load the optional vendor only for a default dispatcher; the cache owns lookup validation.
|
|
4
|
+
* @throws When the optional peer cannot be loaded or its Agent cannot be constructed.
|
|
5
|
+
*/
|
|
6
|
+
export function createUndiciAssetCacheDispatcher({ lookup }, _options = {}) {
|
|
7
|
+
const { Agent } = require('undici');
|
|
8
|
+
return new Agent({ connect: { lookup } });
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=asset-cache.adapter.js.map
|
package/dist/asset-cache.js
CHANGED
|
@@ -23,10 +23,9 @@ import { createHash } from 'node:crypto';
|
|
|
23
23
|
import { lookup as dnsLookupCb } from 'node:dns';
|
|
24
24
|
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
25
25
|
import { isIP } from 'node:net';
|
|
26
|
-
import { createRequire } from 'node:module';
|
|
27
26
|
import path from 'node:path';
|
|
27
|
+
import { createUndiciAssetCacheDispatcher } from './asset-cache.adapter.js';
|
|
28
28
|
import { isPrivateAddress } from './net/address.js';
|
|
29
|
-
const require = createRequire(import.meta.url);
|
|
30
29
|
/** Media extensions we are willing to cache + proxy. Anything else (HTML,
|
|
31
30
|
* CSS, JS, fonts) is intentionally left alone so this surface never becomes a
|
|
32
31
|
* general-purpose open proxy. */
|
|
@@ -133,9 +132,8 @@ export function createValidatingLookup(lookupImpl = dnsLookupCb) {
|
|
|
133
132
|
*/
|
|
134
133
|
export function createAssetCacheDispatcher(_required, { lookupImpl } = {}) {
|
|
135
134
|
// Keep the factory synchronous while root imports and host-supplied dispatchers need no undici.
|
|
136
|
-
let undici;
|
|
137
135
|
try {
|
|
138
|
-
|
|
136
|
+
return createUndiciAssetCacheDispatcher({ lookup: createValidatingLookup(lookupImpl) });
|
|
139
137
|
}
|
|
140
138
|
catch (error) {
|
|
141
139
|
if (error instanceof Error && error.code === 'MODULE_NOT_FOUND'
|
|
@@ -144,7 +142,6 @@ export function createAssetCacheDispatcher(_required, { lookupImpl } = {}) {
|
|
|
144
142
|
}
|
|
145
143
|
throw error;
|
|
146
144
|
}
|
|
147
|
-
return new undici.Agent({ connect: { lookup: createValidatingLookup(lookupImpl) } });
|
|
148
145
|
}
|
|
149
146
|
/** Build a disk-backed cache for external sandboxed-content media. Concurrent
|
|
150
147
|
* requests for the same URL share one in-flight fetch; completed fetches
|
|
@@ -38,7 +38,7 @@ export function classifyAddress({ ip }) {
|
|
|
38
38
|
// it is not globally routable and belongs with RFC 1918 private ranges for egress decisions.
|
|
39
39
|
return "private";
|
|
40
40
|
}
|
|
41
|
-
const
|
|
41
|
+
const CROSS_ORIGIN_HEADERS = new Set(["accept", "accept-language", "user-agent"]);
|
|
42
42
|
// HTTP header names are case-insensitive; preserve the caller's explicit User-Agent in any casing.
|
|
43
43
|
function hasUserAgentHeader(headers) {
|
|
44
44
|
return Object.keys(headers).some((key) => key.toLowerCase() === "user-agent");
|
|
@@ -105,10 +105,10 @@ async function resolvePinnedPeer(url, policy, dns) {
|
|
|
105
105
|
tlsServerName: isIP(hostname) === 0 ? url.hostname : undefined,
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
|
-
function
|
|
108
|
+
function withCrossOriginHeaders(headers) {
|
|
109
109
|
const next = {};
|
|
110
110
|
for (const [key, value] of Object.entries(headers)) {
|
|
111
|
-
if (
|
|
111
|
+
if (!CROSS_ORIGIN_HEADERS.has(key.toLowerCase()))
|
|
112
112
|
continue;
|
|
113
113
|
next[key] = value;
|
|
114
114
|
}
|
|
@@ -179,8 +179,9 @@ async function sendWithPolicy(transport, policy, request, redirectsFollowed, dns
|
|
|
179
179
|
return cappedResponse;
|
|
180
180
|
const nextUrl = new URL(location, url);
|
|
181
181
|
// Cross-origin hops must not inherit authorization or cookies entrusted to the original origin.
|
|
182
|
+
// Custom headers can carry credentials too; forward only the explicit noncredential allowlist.
|
|
182
183
|
const nextHeaders = isCrossOrigin(url, nextUrl)
|
|
183
|
-
?
|
|
184
|
+
? withCrossOriginHeaders(request.headers)
|
|
184
185
|
: request.headers;
|
|
185
186
|
return sendWithPolicy(transport, policy, { ...request, url: nextUrl.toString(), headers: nextHeaders }, redirectsFollowed + 1, dns, redirect);
|
|
186
187
|
}
|
package/dist/web/web-page.js
CHANGED
|
@@ -245,8 +245,8 @@ function project({ input, ports, url, response }) {
|
|
|
245
245
|
const body = decodeBody(response, contentType, isHtml);
|
|
246
246
|
if (body.includes("\u0000"))
|
|
247
247
|
throw new ToolInputError({ message: "web_fetch_page: the response is binary, not a text page." });
|
|
248
|
-
//
|
|
249
|
-
const bounded = isHtml ? boundHtml({ html: body }
|
|
248
|
+
// A fixed ceiling limits synchronous parsing without letting a large head consume the output budget.
|
|
249
|
+
const bounded = isHtml ? boundHtml({ html: body }) : { html: body, truncated: false };
|
|
250
250
|
const facts = isHtml ? readHtmlDocument({ html: bounded.html, pageUrl: finalUrl }) : /xml/.test(mediaType) ? sitemapFacts(body, finalUrl) : EMPTY_FACTS;
|
|
251
251
|
const rendered = render(input.format, input.format === "raw" ? body : bounded.html, isHtml, finalUrl, ports);
|
|
252
252
|
const content = clip(rendered, input.maxChars);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/platform",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.8",
|
|
4
4
|
"description": "Generic OS/platform primitives: process exec and lifecycle, filesystem containment, HTTP readiness polling, toolchain discovery, blob storage, AWS signing, and an SSRF-safe asset cache.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|