@juspay/neurolink 12.51.2 → 12.51.4
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 +2 -2
- package/dist/browser/neurolink.min.js +408 -409
- package/dist/rag/chunkers/HTMLChunker.js +7 -4
- package/dist/rag/chunking/htmlChunker.js +19 -10
- package/dist/rag/document/loaders.js +20 -11
- package/dist/server/middleware/auth.js +8 -4
- package/dist/server/voice/voiceServerApp.js +36 -1
- package/dist/utils/htmlText.d.ts +45 -0
- package/dist/utils/htmlText.js +148 -0
- package/package.json +3 -1
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Splits HTML content by semantic tags.
|
|
5
5
|
*/
|
|
6
|
+
import { removeHtmlMarkup } from "../../utils/htmlText.js";
|
|
6
7
|
import { BaseChunker, DEFAULT_CHUNKER_CONFIG } from "./BaseChunker.js";
|
|
7
8
|
/**
|
|
8
9
|
* HTML Chunker
|
|
@@ -28,10 +29,12 @@ export class HTMLChunker extends BaseChunker {
|
|
|
28
29
|
* Strip HTML tags from content
|
|
29
30
|
*/
|
|
30
31
|
stripHtml(html) {
|
|
31
|
-
return html
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
return removeHtmlMarkup(html, {
|
|
33
|
+
tagReplacement: " ",
|
|
34
|
+
commentReplacement: " ",
|
|
35
|
+
lineBreakEndTags: [],
|
|
36
|
+
lineBreakVoidTags: [],
|
|
37
|
+
})
|
|
35
38
|
.replace(/\s+/g, " ")
|
|
36
39
|
.trim();
|
|
37
40
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { randomUUID } from "crypto";
|
|
8
8
|
import { decodeHtmlTextEntitiesOnce } from "../../utils/htmlEntities.js";
|
|
9
|
+
import { removeHtmlMarkup } from "../../utils/htmlText.js";
|
|
9
10
|
/**
|
|
10
11
|
* HTML-aware chunker implementation
|
|
11
12
|
* Splits based on HTML structure (tags, elements)
|
|
@@ -171,16 +172,24 @@ export class HTMLChunker {
|
|
|
171
172
|
* Extract plain text from HTML
|
|
172
173
|
*/
|
|
173
174
|
extractText(html) {
|
|
174
|
-
return decodeHtmlTextEntitiesOnce(html
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
175
|
+
return decodeHtmlTextEntitiesOnce(removeHtmlMarkup(html, {
|
|
176
|
+
tagReplacement: "",
|
|
177
|
+
commentReplacement: "",
|
|
178
|
+
lineBreakEndTags: [
|
|
179
|
+
"p",
|
|
180
|
+
"div",
|
|
181
|
+
"br",
|
|
182
|
+
"h1",
|
|
183
|
+
"h2",
|
|
184
|
+
"h3",
|
|
185
|
+
"h4",
|
|
186
|
+
"h5",
|
|
187
|
+
"h6",
|
|
188
|
+
"li",
|
|
189
|
+
"tr",
|
|
190
|
+
],
|
|
191
|
+
lineBreakVoidTags: [],
|
|
192
|
+
}), { nbsp: " ", amp: "&", lt: "<", gt: ">", quot: '"', "#039": "'" })
|
|
184
193
|
.replace(/\s+/g, " ")
|
|
185
194
|
.trim();
|
|
186
195
|
}
|
|
@@ -28,6 +28,7 @@ import { readFile } from "fs/promises";
|
|
|
28
28
|
import { basename, extname } from "path";
|
|
29
29
|
import { logger } from "../../utils/logger.js";
|
|
30
30
|
import { decodeHtmlTextEntitiesOnce } from "../../utils/htmlEntities.js";
|
|
31
|
+
import { removeHtmlMarkup } from "../../utils/htmlText.js";
|
|
31
32
|
import { CSVProcessor, stripBom, stripMetadataLine, splitCsvLines, dedupeColumnNames, sanitizeColumnName, } from "../../utils/csvProcessor.js";
|
|
32
33
|
import { splitCSVFields } from "../../utils/csvUtils.js";
|
|
33
34
|
import { MDocument } from "./MDocument.js";
|
|
@@ -429,17 +430,25 @@ export class WebLoader {
|
|
|
429
430
|
* Convert HTML to plain text
|
|
430
431
|
*/
|
|
431
432
|
htmlToText(html) {
|
|
432
|
-
return decodeHtmlTextEntitiesOnce(html
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
433
|
+
return decodeHtmlTextEntitiesOnce(removeHtmlMarkup(html, {
|
|
434
|
+
tagReplacement: "",
|
|
435
|
+
commentReplacement: "",
|
|
436
|
+
lineBreakEndTags: [
|
|
437
|
+
"p",
|
|
438
|
+
"div",
|
|
439
|
+
"h1",
|
|
440
|
+
"h2",
|
|
441
|
+
"h3",
|
|
442
|
+
"h4",
|
|
443
|
+
"h5",
|
|
444
|
+
"h6",
|
|
445
|
+
"br",
|
|
446
|
+
"li",
|
|
447
|
+
"tr",
|
|
448
|
+
"blockquote",
|
|
449
|
+
],
|
|
450
|
+
lineBreakVoidTags: ["br", "hr"],
|
|
451
|
+
}), {
|
|
443
452
|
nbsp: " ",
|
|
444
453
|
amp: "&",
|
|
445
454
|
lt: "<",
|
|
@@ -135,13 +135,17 @@ function extractToken(ctx, type, headerName, customExtractor) {
|
|
|
135
135
|
}
|
|
136
136
|
switch (type) {
|
|
137
137
|
case "bearer": {
|
|
138
|
-
// Extract token from "Bearer <token>"
|
|
139
|
-
|
|
138
|
+
// Extract token from "Bearer <token>". The token must open with a
|
|
139
|
+
// non-whitespace character. With `\s+(.+)` the whitespace run and the
|
|
140
|
+
// token could both claim the same tabs, so a header that failed to match
|
|
141
|
+
// (a line terminator after a long run of tabs) was retried at every
|
|
142
|
+
// split, which is quadratic in the header length.
|
|
143
|
+
const match = headerValue.match(/^Bearer\s+(\S.*)$/i);
|
|
140
144
|
return match ? match[1] : null;
|
|
141
145
|
}
|
|
142
146
|
case "basic": {
|
|
143
|
-
// Extract credentials from "Basic <base64>"
|
|
144
|
-
const match = headerValue.match(/^Basic\s+(
|
|
147
|
+
// Extract credentials from "Basic <base64>" (same shape as above).
|
|
148
|
+
const match = headerValue.match(/^Basic\s+(\S.*)$/i);
|
|
145
149
|
return match ? match[1] : null;
|
|
146
150
|
}
|
|
147
151
|
case "api-key":
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import express from "express";
|
|
2
|
+
import rateLimit from "express-rate-limit";
|
|
2
3
|
import fs from "fs";
|
|
3
4
|
import http from "http";
|
|
4
5
|
import path from "path";
|
|
@@ -29,6 +30,24 @@ function resolvePublicPath() {
|
|
|
29
30
|
}
|
|
30
31
|
return compiled; // let express.static handle the 404
|
|
31
32
|
}
|
|
33
|
+
// Per-client request budget for the HTTP surface (the page and its static
|
|
34
|
+
// assets). One page load is a handful of requests, so the default leaves normal
|
|
35
|
+
// use untouched and only bounds a client that is hammering the file-serving
|
|
36
|
+
// routes. Both values are operator settings, read once at startup.
|
|
37
|
+
const DEFAULT_RATE_LIMIT_WINDOW_MS = 60_000;
|
|
38
|
+
const DEFAULT_RATE_LIMIT_MAX = 300;
|
|
39
|
+
function positiveIntegerFromEnv(name, fallback) {
|
|
40
|
+
const raw = process.env[name];
|
|
41
|
+
if (raw === undefined || raw.trim() === "") {
|
|
42
|
+
return fallback;
|
|
43
|
+
}
|
|
44
|
+
const parsed = Number(raw);
|
|
45
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1) {
|
|
46
|
+
logger.warn(`[SERVER] Ignoring ${name}: expected a positive integer, using ${fallback}`);
|
|
47
|
+
return fallback;
|
|
48
|
+
}
|
|
49
|
+
return parsed;
|
|
50
|
+
}
|
|
32
51
|
export async function startVoiceServer(port = 3000) {
|
|
33
52
|
const app = express();
|
|
34
53
|
// NEW11: refuse to bind to non-loopback interfaces unless the operator
|
|
@@ -46,6 +65,22 @@ export async function startVoiceServer(port = 3000) {
|
|
|
46
65
|
// set custom headers — see voiceWebSocketHandler.verifyClient. HTTP routes
|
|
47
66
|
// intentionally reject `?token=` (would leak via Referer + access logs).
|
|
48
67
|
const authToken = process.env.VOICE_SERVER_AUTH_TOKEN;
|
|
68
|
+
/* ---------- RATE LIMIT ---------- */
|
|
69
|
+
// Registered first so it also bounds failed-auth attempts and oversized
|
|
70
|
+
// bodies. /health stays exempt for the same reason the auth middleware skips
|
|
71
|
+
// it: a load balancer probes it on a fixed cadence with no credentials.
|
|
72
|
+
const rateLimitWindowMs = positiveIntegerFromEnv("VOICE_SERVER_RATE_LIMIT_WINDOW_MS", DEFAULT_RATE_LIMIT_WINDOW_MS);
|
|
73
|
+
const rateLimitMax = positiveIntegerFromEnv("VOICE_SERVER_RATE_LIMIT_MAX", DEFAULT_RATE_LIMIT_MAX);
|
|
74
|
+
app.use(rateLimit({
|
|
75
|
+
windowMs: rateLimitWindowMs,
|
|
76
|
+
limit: rateLimitMax,
|
|
77
|
+
standardHeaders: "draft-6",
|
|
78
|
+
legacyHeaders: false,
|
|
79
|
+
skip: (req) => req.path === "/health",
|
|
80
|
+
handler: (_req, res) => {
|
|
81
|
+
res.status(429).json({ error: "Too many requests" });
|
|
82
|
+
},
|
|
83
|
+
}));
|
|
49
84
|
/* ---------- BODY LIMITS + AUTH ---------- */
|
|
50
85
|
// NEW11: cap JSON / urlencoded body to 100kb. Express's default is 100kb
|
|
51
86
|
// for json() but only when explicitly registered; without this any future
|
|
@@ -108,7 +143,7 @@ export async function startVoiceServer(port = 3000) {
|
|
|
108
143
|
const exposure = allowPublic
|
|
109
144
|
? `bound publicly on ${host}:${port} (VOICE_SERVER_ALLOW_PUBLIC=1)`
|
|
110
145
|
: `bound to loopback ${host}:${port} (set VOICE_SERVER_ALLOW_PUBLIC=1 to expose externally)`;
|
|
111
|
-
logger.info(`[SERVER] Voice server running — ${exposure}${authToken ? " (auth required)" : " (no auth — token via VOICE_SERVER_AUTH_TOKEN recommended)"}`);
|
|
146
|
+
logger.info(`[SERVER] Voice server running — ${exposure}${authToken ? " (auth required)" : " (no auth — token via VOICE_SERVER_AUTH_TOKEN recommended)"}; rate limit ${rateLimitMax} requests per ${rateLimitWindowMs}ms per client`);
|
|
112
147
|
resolve();
|
|
113
148
|
});
|
|
114
149
|
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-pass markup removal for turning HTML into indexable text.
|
|
3
|
+
*
|
|
4
|
+
* This is text conversion, not output sanitization. The result is plain text
|
|
5
|
+
* for retrieval and chunking; it is not meant to be written back into a page,
|
|
6
|
+
* and the caller's entity decoding can legitimately turn `<b>` into
|
|
7
|
+
* `<b>`.
|
|
8
|
+
*
|
|
9
|
+
* Why one pass: the previous implementation chained several global regex
|
|
10
|
+
* replacements (script, style, comment, block end, generic tag). Each removal
|
|
11
|
+
* could join the text on either side of it into a new tag for the next
|
|
12
|
+
* replacement (`<scr<!---->ipt>`), and the lazy `[\s\S]*?` bodies rescanned
|
|
13
|
+
* the rest of the input once per unclosed `<script`, which is quadratic.
|
|
14
|
+
* Here the input is read left to right exactly once. Every character of the
|
|
15
|
+
* output is copied from an unremoved stretch of the input, a removed stretch
|
|
16
|
+
* is never re-read, and every search either consumes what it scans or is
|
|
17
|
+
* answered in constant time from an index computed up front, so the work is
|
|
18
|
+
* linear in the input length.
|
|
19
|
+
*
|
|
20
|
+
* Invariant: the output never matches `<[^>]+>`. A `<` survives only when no
|
|
21
|
+
* `>` follows it in the input, or when the next character is the `>` itself
|
|
22
|
+
* (`<>`, which is not a tag). A removed span therefore cannot leave behind,
|
|
23
|
+
* or assemble, a tag.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Remove HTML markup from `html` in one linear pass.
|
|
27
|
+
*
|
|
28
|
+
* - `<script>` / `<style>` elements are removed with their bodies. The end
|
|
29
|
+
* tag may carry whitespace or attributes (`</script >`). An element with
|
|
30
|
+
* no usable end tag loses only its start tag, as before.
|
|
31
|
+
* - `<!-- ... -->` is replaced with `commentReplacement`. An unterminated
|
|
32
|
+
* comment is treated like any other tag and ends at the next `>`.
|
|
33
|
+
* - A closing tag named in `lineBreakEndTags` and a void tag named in
|
|
34
|
+
* `lineBreakVoidTags` (`<br>`, `<hr />`) become a newline.
|
|
35
|
+
* - Any other `<...>` becomes `tagReplacement`. A `<` with no `>` after it,
|
|
36
|
+
* and the empty `<>`, are literal text and are kept.
|
|
37
|
+
*
|
|
38
|
+
* Tag-name lists are lower case.
|
|
39
|
+
*/
|
|
40
|
+
export declare function removeHtmlMarkup(html: string, options: {
|
|
41
|
+
readonly tagReplacement: string;
|
|
42
|
+
readonly commentReplacement: string;
|
|
43
|
+
readonly lineBreakEndTags: readonly string[];
|
|
44
|
+
readonly lineBreakVoidTags: readonly string[];
|
|
45
|
+
}): string;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-pass markup removal for turning HTML into indexable text.
|
|
3
|
+
*
|
|
4
|
+
* This is text conversion, not output sanitization. The result is plain text
|
|
5
|
+
* for retrieval and chunking; it is not meant to be written back into a page,
|
|
6
|
+
* and the caller's entity decoding can legitimately turn `<b>` into
|
|
7
|
+
* `<b>`.
|
|
8
|
+
*
|
|
9
|
+
* Why one pass: the previous implementation chained several global regex
|
|
10
|
+
* replacements (script, style, comment, block end, generic tag). Each removal
|
|
11
|
+
* could join the text on either side of it into a new tag for the next
|
|
12
|
+
* replacement (`<scr<!---->ipt>`), and the lazy `[\s\S]*?` bodies rescanned
|
|
13
|
+
* the rest of the input once per unclosed `<script`, which is quadratic.
|
|
14
|
+
* Here the input is read left to right exactly once. Every character of the
|
|
15
|
+
* output is copied from an unremoved stretch of the input, a removed stretch
|
|
16
|
+
* is never re-read, and every search either consumes what it scans or is
|
|
17
|
+
* answered in constant time from an index computed up front, so the work is
|
|
18
|
+
* linear in the input length.
|
|
19
|
+
*
|
|
20
|
+
* Invariant: the output never matches `<[^>]+>`. A `<` survives only when no
|
|
21
|
+
* `>` follows it in the input, or when the next character is the `>` itself
|
|
22
|
+
* (`<>`, which is not a tag). A removed span therefore cannot leave behind,
|
|
23
|
+
* or assemble, a tag.
|
|
24
|
+
*/
|
|
25
|
+
const SLASH = 0x2f;
|
|
26
|
+
const GREATER_THAN = 0x3e;
|
|
27
|
+
const WHITESPACE = /\s/;
|
|
28
|
+
function isAsciiAlphanumeric(code) {
|
|
29
|
+
return ((code >= 0x30 && code <= 0x39) ||
|
|
30
|
+
(code >= 0x41 && code <= 0x5a) ||
|
|
31
|
+
(code >= 0x61 && code <= 0x7a));
|
|
32
|
+
}
|
|
33
|
+
// The characters after which an HTML tokenizer ends a tag name.
|
|
34
|
+
function endsTagName(code) {
|
|
35
|
+
return (code === 0x09 ||
|
|
36
|
+
code === 0x0a ||
|
|
37
|
+
code === 0x0c ||
|
|
38
|
+
code === 0x0d ||
|
|
39
|
+
code === 0x20 ||
|
|
40
|
+
code === SLASH ||
|
|
41
|
+
code === GREATER_THAN);
|
|
42
|
+
}
|
|
43
|
+
function scanTagName(html, from) {
|
|
44
|
+
let end = from;
|
|
45
|
+
while (end < html.length && isAsciiAlphanumeric(html.charCodeAt(end))) {
|
|
46
|
+
end++;
|
|
47
|
+
}
|
|
48
|
+
return end;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Index of the next `</name` that is followed by whitespace, `/` or `>`
|
|
52
|
+
* (so `</script >` and `</SCRIPT foo>` count), or -1. Case-insensitive.
|
|
53
|
+
*/
|
|
54
|
+
function findEndTag(html, name, from) {
|
|
55
|
+
let at = html.indexOf("</", from);
|
|
56
|
+
while (at !== -1) {
|
|
57
|
+
const nameEnd = at + 2 + name.length;
|
|
58
|
+
if (html.slice(at + 2, nameEnd).toLowerCase() === name &&
|
|
59
|
+
endsTagName(html.charCodeAt(nameEnd))) {
|
|
60
|
+
return at;
|
|
61
|
+
}
|
|
62
|
+
at = html.indexOf("</", at + 2);
|
|
63
|
+
}
|
|
64
|
+
return -1;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Remove HTML markup from `html` in one linear pass.
|
|
68
|
+
*
|
|
69
|
+
* - `<script>` / `<style>` elements are removed with their bodies. The end
|
|
70
|
+
* tag may carry whitespace or attributes (`</script >`). An element with
|
|
71
|
+
* no usable end tag loses only its start tag, as before.
|
|
72
|
+
* - `<!-- ... -->` is replaced with `commentReplacement`. An unterminated
|
|
73
|
+
* comment is treated like any other tag and ends at the next `>`.
|
|
74
|
+
* - A closing tag named in `lineBreakEndTags` and a void tag named in
|
|
75
|
+
* `lineBreakVoidTags` (`<br>`, `<hr />`) become a newline.
|
|
76
|
+
* - Any other `<...>` becomes `tagReplacement`. A `<` with no `>` after it,
|
|
77
|
+
* and the empty `<>`, are literal text and are kept.
|
|
78
|
+
*
|
|
79
|
+
* Tag-name lists are lower case.
|
|
80
|
+
*/
|
|
81
|
+
export function removeHtmlMarkup(html, options) {
|
|
82
|
+
const { tagReplacement, commentReplacement } = options;
|
|
83
|
+
const lineBreakEndTags = new Set(options.lineBreakEndTags);
|
|
84
|
+
const lineBreakVoidTags = new Set(options.lineBreakVoidTags);
|
|
85
|
+
// Answers "is there any `>` / `-->` after here" without a scan. Once an
|
|
86
|
+
// element finds no usable end tag, no later element of that name can.
|
|
87
|
+
const lastGreaterThan = html.lastIndexOf(">");
|
|
88
|
+
const lastCommentEnd = html.lastIndexOf("-->");
|
|
89
|
+
const withoutEndTag = new Set();
|
|
90
|
+
const markupAt = (at) => {
|
|
91
|
+
if (html.startsWith("<!--", at) && lastCommentEnd >= at + 4) {
|
|
92
|
+
return {
|
|
93
|
+
end: html.indexOf("-->", at + 4) + 3,
|
|
94
|
+
replacement: commentReplacement,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const closing = html.charCodeAt(at + 1) === SLASH;
|
|
98
|
+
const nameStart = closing ? at + 2 : at + 1;
|
|
99
|
+
const nameEnd = scanTagName(html, nameStart);
|
|
100
|
+
const name = html.slice(nameStart, nameEnd).toLowerCase();
|
|
101
|
+
if (!closing &&
|
|
102
|
+
(name === "script" || name === "style") &&
|
|
103
|
+
endsTagName(html.charCodeAt(nameEnd)) &&
|
|
104
|
+
!withoutEndTag.has(name)) {
|
|
105
|
+
const startTagEnd = html.indexOf(">", nameEnd);
|
|
106
|
+
const endTag = findEndTag(html, name, startTagEnd + 1);
|
|
107
|
+
if (endTag !== -1 && endTag < lastGreaterThan) {
|
|
108
|
+
return { end: html.indexOf(">", endTag) + 1, replacement: "" };
|
|
109
|
+
}
|
|
110
|
+
withoutEndTag.add(name);
|
|
111
|
+
}
|
|
112
|
+
if (closing &&
|
|
113
|
+
html.charCodeAt(nameEnd) === GREATER_THAN &&
|
|
114
|
+
lineBreakEndTags.has(name)) {
|
|
115
|
+
return { end: nameEnd + 1, replacement: "\n" };
|
|
116
|
+
}
|
|
117
|
+
if (!closing && lineBreakVoidTags.has(name)) {
|
|
118
|
+
let probe = nameEnd;
|
|
119
|
+
while (WHITESPACE.test(html.charAt(probe))) {
|
|
120
|
+
probe++;
|
|
121
|
+
}
|
|
122
|
+
if (html.charCodeAt(probe) === SLASH) {
|
|
123
|
+
probe++;
|
|
124
|
+
}
|
|
125
|
+
if (html.charCodeAt(probe) === GREATER_THAN) {
|
|
126
|
+
return { end: probe + 1, replacement: "\n" };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const end = html.indexOf(">", at + 1);
|
|
130
|
+
return end === at + 1
|
|
131
|
+
? null
|
|
132
|
+
: { end: end + 1, replacement: tagReplacement };
|
|
133
|
+
};
|
|
134
|
+
let output = "";
|
|
135
|
+
let copied = 0;
|
|
136
|
+
let open = html.indexOf("<");
|
|
137
|
+
while (open !== -1 && open < lastGreaterThan) {
|
|
138
|
+
const markup = markupAt(open);
|
|
139
|
+
if (markup === null) {
|
|
140
|
+
open = html.indexOf("<", open + 1);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
output += html.slice(copied, open) + markup.replacement;
|
|
144
|
+
copied = markup.end;
|
|
145
|
+
open = html.indexOf("<", copied);
|
|
146
|
+
}
|
|
147
|
+
return output + html.slice(copied);
|
|
148
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.51.
|
|
3
|
+
"version": "12.51.4",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · TypeSafe Jev and more.",
|
|
6
6
|
"author": {
|
|
@@ -164,11 +164,13 @@
|
|
|
164
164
|
"test:embedding-response-validation": "pnpm exec tsx test/continuous-test-suite-embedding-response-validation.ts",
|
|
165
165
|
"test:pdf-image-streaming": "pnpm exec tsx test/continuous-test-suite-pdf-image-streaming.ts",
|
|
166
166
|
"test:rag": "pnpm exec tsx test/continuous-test-suite-rag.ts",
|
|
167
|
+
"test:rag-html-markup": "pnpm exec tsx test/continuous-test-suite-rag-html-markup.ts",
|
|
167
168
|
"test:skills": "pnpm exec tsx test/continuous-test-suite-skills.ts",
|
|
168
169
|
"test:decide": "pnpm exec tsx test/continuous-test-suite-decide.ts",
|
|
169
170
|
"test:decide-catalog": "pnpm exec tsx test/continuous-test-suite-decide-catalog.ts",
|
|
170
171
|
"test:agent-mode": "pnpm exec tsx test/continuous-test-suite-agent-mode.ts",
|
|
171
172
|
"test:servers": "pnpm exec tsx test/continuous-test-suite-servers.ts",
|
|
173
|
+
"test:server-boundaries": "pnpm exec tsx test/continuous-test-suite-server-boundaries.ts",
|
|
172
174
|
"test:websocket-agent-handler": "pnpm exec tsx test/continuous-test-suite-websocket-agent-handler.ts",
|
|
173
175
|
"test:tool-reliability": "pnpm exec tsx test/continuous-test-suite-tool-reliability.ts",
|
|
174
176
|
"test:video-abort": "pnpm exec tsx test/continuous-test-suite-video-abort.ts",
|