@jini-ai/platform 0.5.9 → 0.5.10

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 (79) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/asset-cache.js +26 -25
  3. package/dist/blob-storage.d.ts +7 -2
  4. package/dist/blob-storage.js +99 -59
  5. package/dist/child-environment.d.ts +36 -0
  6. package/dist/child-environment.js +68 -0
  7. package/dist/download.js +192 -148
  8. package/dist/fs/atomic-write.js +105 -93
  9. package/dist/fs/boot-identity.d.ts +16 -0
  10. package/dist/fs/boot-identity.js +38 -17
  11. package/dist/fs/directory-home.js +6 -0
  12. package/dist/fs/directory-registry.d.ts +1 -1
  13. package/dist/fs/directory-registry.js +23 -16
  14. package/dist/fs/directory-running-lock.d.ts +1 -1
  15. package/dist/fs/directory-running-lock.js +18 -7
  16. package/dist/fs/directory-sync.d.ts +1 -1
  17. package/dist/fs/directory-sync.js +64 -51
  18. package/dist/fs/directory-user-config.js +108 -67
  19. package/dist/fs/directory-workspace.d.ts +11 -0
  20. package/dist/fs/directory-workspace.js +16 -11
  21. package/dist/fs/durable-json.js +49 -37
  22. package/dist/fs/file-lock.js +214 -149
  23. package/dist/fs/guarded-reader.js +47 -35
  24. package/dist/fs/user-data-directories.d.ts +1 -1
  25. package/dist/fs/user-data-directories.js +25 -9
  26. package/dist/fs/verified-directory-move.d.ts +13 -11
  27. package/dist/fs/verified-directory-move.js +105 -68
  28. package/dist/fs/verified-file-relocation.d.ts +1 -1
  29. package/dist/fs/verified-file-relocation.js +111 -94
  30. package/dist/http/guarded/client.d.ts +3 -4
  31. package/dist/http/guarded/client.js +50 -60
  32. package/dist/http/guarded/fetch-adapter.d.ts +2 -3
  33. package/dist/http/guarded/fetch-adapter.js +26 -15
  34. package/dist/mail/builtin-providers.d.ts +39 -0
  35. package/dist/mail/builtin-providers.js +60 -0
  36. package/dist/mail/feedback.d.ts +84 -0
  37. package/dist/mail/feedback.js +90 -0
  38. package/dist/mail/index.d.ts +11 -0
  39. package/dist/mail/index.js +6 -0
  40. package/dist/mail/policy.d.ts +77 -0
  41. package/dist/mail/policy.js +155 -0
  42. package/dist/mail/ports.d.ts +56 -7
  43. package/dist/mail/provider.d.ts +117 -0
  44. package/dist/mail/provider.js +74 -0
  45. package/dist/mail/rate-limit.d.ts +33 -0
  46. package/dist/mail/rate-limit.js +35 -0
  47. package/dist/mail/sender-identity.d.ts +68 -0
  48. package/dist/mail/sender-identity.js +50 -0
  49. package/dist/mail/types.d.ts +9 -0
  50. package/dist/net/address.d.ts +9 -23
  51. package/dist/net/address.js +28 -133
  52. package/dist/net/endpoint-policy.d.ts +26 -2
  53. package/dist/net/endpoint-policy.js +159 -56
  54. package/dist/object-store/index.d.ts +2 -0
  55. package/dist/object-store/index.js +2 -0
  56. package/dist/object-store/local-fs.d.ts +17 -0
  57. package/dist/object-store/local-fs.js +146 -0
  58. package/dist/process.js +2 -0
  59. package/dist/proxy-env.js +131 -135
  60. package/dist/secrets/credential-sets/contracts.d.ts +1 -0
  61. package/dist/secrets/credential-sets/contracts.js +1 -0
  62. package/dist/secrets/credential-sets/index.d.ts +1 -0
  63. package/dist/secrets/credential-sets/index.js +1 -0
  64. package/dist/secrets/credential-sets/provider-matches.d.ts +61 -0
  65. package/dist/secrets/credential-sets/provider-matches.js +61 -0
  66. package/dist/secrets/credential-sets/types.d.ts +7 -0
  67. package/dist/secrets/credential-sets/vault.meta.js +58 -20
  68. package/dist/secrets/credential-sets/vault.repo.memory.d.ts +7 -0
  69. package/dist/secrets/credential-sets/vault.repo.memory.js +43 -36
  70. package/dist/secrets/credential-sets/vault.service.d.ts +9 -0
  71. package/dist/secrets/credential-sets/vault.service.js +131 -61
  72. package/dist/secrets/credential-sets/vault.types.d.ts +11 -3
  73. package/dist/secrets/credential-sets/vault.types.js +22 -8
  74. package/dist/secrets/keyring.env.js +14 -10
  75. package/dist/toolchain.js +119 -91
  76. package/dist/web/html-document.js +73 -69
  77. package/dist/web/html-to-markdown.js +127 -102
  78. package/dist/web/web-page.js +160 -80
  79. package/package.json +11 -3
@@ -27,53 +27,60 @@ function plainText(root) {
27
27
  }
28
28
  return parts.join("").replace(/\s+/g, " ");
29
29
  }
30
+ /** First child element with this tag; the HTML parser always synthesizes html and head. */
31
+ function childElement({ parent, tagName }, _options = {}) {
32
+ return parent.childNodes.find((node) => isElement(node) && node.tagName === tagName);
33
+ }
30
34
  /** Resolve the document base before preparation drops the invisible head. O(head nodes). */
31
35
  function documentBase(document, pageUrl) {
32
- const head = document.childNodes.find(isElement)?.childNodes.find(node => isElement(node) && node.tagName === "head");
33
- const base = head && isElement(head) ? head.childNodes.find(node => isElement(node) && node.tagName === "base") : undefined;
34
- return absolute({ raw: base && isElement(base) ? attr({ element: base, name: "href" }) : undefined, base: pageUrl, protocols: ASSET_PROTOCOLS }) ?? pageUrl;
36
+ const head = childElement({ parent: document.childNodes.find(isElement), tagName: "head" });
37
+ const base = childElement({ parent: head, tagName: "base" });
38
+ return absolute({ raw: base ? attr({ element: base, name: "href" }) : undefined, base: pageUrl, protocols: ASSET_PROTOCOLS }) ?? pageUrl;
35
39
  }
36
40
  /** Normalize reader URLs using the same owner as page facts; mutate only the freshly parsed tree. */
37
41
  function prepareElement(element, base) {
38
- const href = element.tagName === "a" ? absolute({ raw: attr({ element, name: "href" }), base, protocols: LINK_PROTOCOLS }) : undefined;
39
- const src = element.tagName === "img" ? absolute({ raw: imageSource({ img: element }), base, protocols: ASSET_PROTOCOLS }) : undefined;
40
42
  // HTML must not supply the library's data-mdast override: it can inject arbitrary Markdown nodes.
41
43
  // The old reader ignored table spans; retaining that contract also prevents hostile span expansion.
42
44
  element.attrs = element.attrs.filter(attribute => !attribute.name.startsWith("data-mdast") && !["colspan", "rowspan"].includes(attribute.name));
43
45
  if (element.tagName === "table")
44
46
  promoteFirstRow(element);
45
- if (element.tagName === "a" || element.tagName === "img") {
46
- element.attrs = element.attrs.filter(attribute => !["href", "src", "title"].includes(attribute.name));
47
- if (href)
48
- element.attrs.push({ name: "href", value: href });
49
- if (src)
50
- element.attrs.push({ name: "src", value: src });
51
- if (element.tagName === "img") {
52
- const alt = element.attrs.find(attribute => attribute.name === "alt");
53
- if (alt)
54
- alt.value = alt.value.replace(/\s+/g, " ").trim();
55
- }
47
+ if (element.tagName === "a")
48
+ replaceUrlAttribute({ element, name: "href", value: absolute({ raw: attr({ element, name: "href" }), base, protocols: LINK_PROTOCOLS }) });
49
+ if (element.tagName === "img") {
50
+ replaceUrlAttribute({ element, name: "src", value: absolute({ raw: imageSource({ img: element }), base, protocols: ASSET_PROTOCOLS }) });
51
+ const alt = element.attrs.find(attribute => attribute.name === "alt");
52
+ if (alt)
53
+ alt.value = alt.value.replace(/\s+/g, " ").trim();
56
54
  }
57
55
  }
56
+ /** Links and images keep only their normalized URL: raw href/src/title never reach the bridge. */
57
+ function replaceUrlAttribute({ element, name, value }, _options = {}) {
58
+ element.attrs = element.attrs.filter(attribute => !["href", "src", "title"].includes(attribute.name));
59
+ if (value)
60
+ element.attrs.push({ name, value });
61
+ }
58
62
  /** The existing reader treats the first row as the header, including tables made only of td cells. */
59
63
  function promoteFirstRow(table) {
64
+ for (const cell of firstRow({ table })?.childNodes ?? []) {
65
+ if (isElement(cell) && cell.tagName === "td") {
66
+ cell.tagName = "th";
67
+ cell.nodeName = "th";
68
+ }
69
+ }
70
+ }
71
+ /** Document-order search that never descends into a nested table, whose rows are its own. */
72
+ function firstRow({ table }, _options = {}) {
60
73
  const pending = [...table.childNodes].reverse();
61
74
  while (pending.length) {
62
75
  const node = pending.pop();
63
76
  if (!isElement(node) || node.tagName === "table")
64
77
  continue;
65
- if (node.tagName === "tr") {
66
- for (const cell of node.childNodes) {
67
- if (isElement(cell) && cell.tagName === "td") {
68
- cell.tagName = "th";
69
- cell.nodeName = "th";
70
- }
71
- }
72
- return;
73
- }
78
+ if (node.tagName === "tr")
79
+ return node;
74
80
  for (let i = node.childNodes.length - 1; i >= 0; i--)
75
81
  pending.push(node.childNodes[i]);
76
82
  }
83
+ return undefined;
77
84
  }
78
85
  /** Drop invisible subtrees and images with no usable URL before the bridge sees them. */
79
86
  function readable(node, base) {
@@ -85,48 +92,57 @@ function readable(node, base) {
85
92
  return false;
86
93
  return node.tagName !== "img" || absolute({ raw: imageSource({ img: node }), base, protocols: ASSET_PROTOCOLS }) !== undefined;
87
94
  }
95
+ const ATTENTION = new Set(["b", "strong", "i", "em"]);
88
96
  /** Move whitespace outside attention before serialization; empty attention is plain whitespace.
89
97
  * Only this newly parsed tree is mutated. Nested inline wrappers retain their formatting.
90
98
  */
91
99
  function attentionChildren(parent) {
92
- parent.childNodes = parent.childNodes.flatMap(node => {
93
- if (!isElement(node) || !["b", "strong", "i", "em"].includes(node.tagName))
94
- return [node];
95
- const boundary = (last) => {
96
- let edge = node;
97
- while (edge && isElement(edge))
98
- edge = last ? edge.childNodes.at(-1) : edge.childNodes[0];
99
- return edge?.nodeName === "#text" && "value" in edge ? edge : undefined;
100
- };
101
- const first = boundary(false);
102
- const leading = first?.value.match(/^\s+/)?.[0] ?? "";
103
- if (first)
104
- first.value = first.value.slice(leading.length);
105
- const last = boundary(true);
106
- const trailing = last?.value.match(/\s+$/)?.[0] ?? "";
107
- if (last && trailing)
108
- last.value = last.value.slice(0, -trailing.length);
109
- const text = (value) => ({ nodeName: "#text", value, parentNode: parent });
110
- const descendants = [...node.childNodes];
111
- let image = false;
112
- while (descendants.length && !image) {
113
- const child = descendants.pop();
114
- if (isElement(child) && child.tagName === "img")
115
- image = true;
116
- else if ("childNodes" in child)
117
- descendants.push(...child.childNodes);
118
- }
119
- // No marker pair is emitted when whitespace was the entire content.
120
- return [...(leading ? [text(leading)] : []), ...(plainText(node).trim() || image ? [node] : []), ...(trailing ? [text(trailing)] : [])];
121
- });
100
+ parent.childNodes = parent.childNodes.flatMap(node => isElement(node) && ATTENTION.has(node.tagName) ? splitAttention({ node, parent }) : [node]);
101
+ }
102
+ function splitAttention({ node, parent }, _options = {}) {
103
+ const leading = takeEdgeWhitespace({ node, last: false });
104
+ const trailing = takeEdgeWhitespace({ node, last: true });
105
+ const text = (value) => ({ nodeName: "#text", value, parentNode: parent });
106
+ // No marker pair is emitted when whitespace was the entire content.
107
+ const kept = plainText(node).trim() || containsImage({ node }) ? [node] : [];
108
+ return [...(leading ? [text(leading)] : []), ...kept, ...(trailing ? [text(trailing)] : [])];
109
+ }
110
+ /** Removes and returns the whitespace at one edge of the subtree's first/last text node. */
111
+ function takeEdgeWhitespace({ node, last }, _options = {}) {
112
+ const edge = edgeText({ node, last });
113
+ if (!edge)
114
+ return "";
115
+ const whitespace = (last ? /\s+$/ : /^\s+/).exec(edge.value)?.[0] ?? "";
116
+ edge.value = last ? edge.value.slice(0, edge.value.length - whitespace.length) : edge.value.slice(whitespace.length);
117
+ return whitespace;
118
+ }
119
+ /** The first/last leaf of the subtree when it is a text node. */
120
+ function edgeText({ node, last }, _options = {}) {
121
+ let edge = node;
122
+ while (edge && isElement(edge))
123
+ edge = last ? edge.childNodes.at(-1) : edge.childNodes[0];
124
+ return edge?.nodeName === "#text" && "value" in edge ? edge : undefined;
125
+ }
126
+ /** Iterative descendant search with early exit. */
127
+ function containsImage({ node }, _options = {}) {
128
+ const descendants = [...node.childNodes];
129
+ while (descendants.length) {
130
+ const child = descendants.pop();
131
+ if (isElement(child) && child.tagName === "img")
132
+ return true;
133
+ if ("childNodes" in child)
134
+ descendants.push(...child.childNodes);
135
+ }
136
+ return false;
122
137
  }
123
138
  function hastText(root) {
124
139
  const pending = [root];
125
140
  const parts = [];
126
141
  while (pending.length) {
127
142
  const node = pending.pop();
143
+ // HAST text nodes always carry a value.
128
144
  if (node.type === "text")
129
- parts.push(node.value ?? "");
145
+ parts.push(node.value);
130
146
  else if (node.children)
131
147
  for (let i = node.children.length - 1; i >= 0; i--)
132
148
  pending.push(node.children[i]);
@@ -167,31 +183,63 @@ const readerAttention = (node, _parent, state, info) => {
167
183
  function prepare(document, base) {
168
184
  const pending = [{ node: document, depth: 0 }];
169
185
  while (pending.length) {
170
- const { node, depth, prepared } = pending.pop();
171
- if (prepared) {
172
- if (isElement(node))
173
- attentionChildren(node);
186
+ const entry = pending.pop();
187
+ // A node's attention is split only after all its children were prepared (post-order).
188
+ if (entry.prepared) {
189
+ if (isElement(entry.node))
190
+ attentionChildren(entry.node);
174
191
  continue;
175
192
  }
176
- if (isElement(node)) {
177
- prepareElement(node, base);
178
- if (depth > MAX_DEPTH) {
179
- const text = plainText(node);
180
- node.tagName = "span";
181
- node.nodeName = "span";
182
- node.attrs = [];
183
- node.childNodes = [{ nodeName: "#text", value: text, parentNode: node }];
184
- continue;
185
- }
186
- }
187
- if (!("childNodes" in node))
188
- continue;
189
- node.childNodes = node.childNodes.filter(child => readable(child, base));
190
- pending.push({ node, depth, prepared: true });
191
- for (let i = node.childNodes.length - 1; i >= 0; i--)
192
- pending.push({ node: node.childNodes[i], depth: depth + 1 });
193
+ visit({ entry, base, pending });
194
+ }
195
+ }
196
+ /** Prepares one node and schedules its readable children, or flattens it past the depth bound. */
197
+ function visit({ entry: { node, depth }, base, pending }, _options = {}) {
198
+ if (isElement(node)) {
199
+ prepareElement(node, base);
200
+ if (depth > MAX_DEPTH)
201
+ return flattenToText({ element: node });
202
+ }
203
+ if (!("childNodes" in node))
204
+ return;
205
+ node.childNodes = node.childNodes.filter(child => readable(child, base));
206
+ pending.push({ node, depth, prepared: true });
207
+ for (let i = node.childNodes.length - 1; i >= 0; i--)
208
+ pending.push({ node: node.childNodes[i], depth: depth + 1 });
209
+ }
210
+ function flattenToText({ element }, _options = {}) {
211
+ const text = plainText(element);
212
+ element.tagName = "span";
213
+ element.nodeName = "span";
214
+ element.attrs = [];
215
+ element.childNodes = [{ nodeName: "#text", value: text, parentNode: element }];
216
+ }
217
+ /** Snapshot code text with its whitespace; the bridge's handlers see a whitespace-minified clone. */
218
+ function annotateInlineCode({ hast }, _options = {}) {
219
+ const pending = [hast];
220
+ while (pending.length) {
221
+ const node = pending.pop();
222
+ if (node.type === "element" && node.tagName === "code")
223
+ node.data = { readerInlineCode: hastText(node) };
224
+ if (node.children)
225
+ pending.push(...node.children);
193
226
  }
194
227
  }
228
+ const READER_HANDLERS = {
229
+ a: (state, node) => node.properties.href && hasVisibleAnchorContent({ node }) ? defaultHandlers.a(state, node) : state.all(node),
230
+ code: (state, node) => {
231
+ // annotateInlineCode stamped every code element before the bridge ran.
232
+ const result = { type: "inlineCode", value: node.data.readerInlineCode };
233
+ state.patch(node, result);
234
+ return result;
235
+ },
236
+ // Strikethrough was plain text in this reader; no extra GFM serialization extension is needed.
237
+ del: (state, node) => state.all(node),
238
+ s: (state, node) => state.all(node),
239
+ strike: (state, node) => state.all(node),
240
+ audio: (state, node) => state.all(node),
241
+ video: (state, node) => state.all(node),
242
+ };
195
243
  /**
196
244
  * Convert untrusted HTML to readable Markdown with absolute, allowlisted link/image URLs.
197
245
  * @returns Markdown without surrounding whitespace; input is capped before parsing and errors propagate.
@@ -202,31 +250,8 @@ export function htmlToMarkdown({ html, pageUrl }, _optional = {}) {
202
250
  const document = parse(boundHtml({ html }).html);
203
251
  prepare(document, documentBase(document, pageUrl));
204
252
  const hast = fromParse5(document);
205
- const pending = [hast];
206
- while (pending.length) {
207
- const node = pending.pop();
208
- if (node.type === "element" && node.tagName === "code")
209
- node.data = { readerInlineCode: hastText(node) };
210
- if (node.children)
211
- pending.push(...node.children);
212
- }
213
- const markdown = toMdast(hast, {
214
- document: true,
215
- handlers: {
216
- a: (state, node) => node.properties.href && hasVisibleAnchorContent({ node }) ? defaultHandlers.a(state, node) : state.all(node),
217
- code: (state, node) => {
218
- const result = { type: "inlineCode", value: String(node.data?.readerInlineCode ?? "") };
219
- state.patch(node, result);
220
- return result;
221
- },
222
- // Strikethrough was plain text in this reader; no extra GFM serialization extension is needed.
223
- del: (state, node) => state.all(node),
224
- s: (state, node) => state.all(node),
225
- strike: (state, node) => state.all(node),
226
- audio: (state, node) => state.all(node),
227
- video: (state, node) => state.all(node),
228
- },
229
- });
253
+ annotateInlineCode({ hast });
254
+ const markdown = toMdast(hast, { document: true, handlers: READER_HANDLERS });
230
255
  // Preparation moves whitespace out of emphasis; handlers preserve raw reader text.
231
256
  // A nested list is indented by its parent item, including every continuation line.
232
257
  // The serializer owns blank-line runs and inline spaces; code fences/list indents stay verbatim.