@juspay/neurolink 10.12.4 → 10.12.5
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 +6 -0
- package/dist/browser/neurolink.min.js +408 -408
- package/dist/lib/processors/archive/ArchiveProcessor.js +13 -67
- package/dist/lib/processors/archive/zipEntryReader.d.ts +51 -0
- package/dist/lib/processors/archive/zipEntryReader.js +81 -0
- package/dist/lib/processors/document/OpenDocumentProcessor.js +13 -1
- package/dist/lib/processors/document/PptxProcessor.js +58 -12
- package/dist/lib/types/processor.d.ts +15 -0
- package/dist/processors/archive/ArchiveProcessor.js +13 -67
- package/dist/processors/archive/zipEntryReader.d.ts +51 -0
- package/dist/processors/archive/zipEntryReader.js +80 -0
- package/dist/processors/document/OpenDocumentProcessor.js +13 -1
- package/dist/processors/document/PptxProcessor.js +58 -12
- package/dist/types/processor.d.ts +15 -0
- package/package.json +4 -3
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* @module processors/document/OpenDocumentProcessor
|
|
8
8
|
*/
|
|
9
9
|
import { createRequire } from "node:module";
|
|
10
|
+
import * as zlib from "node:zlib";
|
|
10
11
|
import { BaseFileProcessor } from "../base/BaseFileProcessor.js";
|
|
12
|
+
import { readZipEntryWithinLimit } from "../archive/zipEntryReader.js";
|
|
11
13
|
import { SIZE_LIMITS } from "../config/index.js";
|
|
12
14
|
const require = createRequire(import.meta.url);
|
|
13
15
|
// Re-import for local use within this file
|
|
@@ -64,7 +66,17 @@ export class OpenDocumentProcessor extends BaseFileProcessor {
|
|
|
64
66
|
// Try to get content.xml
|
|
65
67
|
const contentEntry = zip.getEntry("content.xml");
|
|
66
68
|
if (contentEntry) {
|
|
67
|
-
|
|
69
|
+
// Bounded rather than `getData()`: an ODF file is a ZIP, so its
|
|
70
|
+
// content.xml can declare any uncompressed size it likes, and
|
|
71
|
+
// `maxSizeMB` only ever saw the compressed archive on the way in.
|
|
72
|
+
const read = readZipEntryWithinLimit(contentEntry, SIZE_LIMITS.DOCUMENT_MAX_MB * 1024 * 1024, zlib);
|
|
73
|
+
if (read.status === "too-large") {
|
|
74
|
+
throw new Error(`content.xml exceeds the ${SIZE_LIMITS.DOCUMENT_MAX_MB}MB limit for OpenDocument content`);
|
|
75
|
+
}
|
|
76
|
+
if (read.status !== "ok") {
|
|
77
|
+
throw new Error("content.xml could not be read from the archive");
|
|
78
|
+
}
|
|
79
|
+
const xmlContent = read.buffer.toString("utf-8");
|
|
68
80
|
const extracted = this.extractTextFromXml(xmlContent);
|
|
69
81
|
textContent = extracted.text;
|
|
70
82
|
paragraphCount = extracted.paragraphCount;
|
|
@@ -27,6 +27,47 @@
|
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
29
|
import AdmZip from "adm-zip";
|
|
30
|
+
import * as zlib from "node:zlib";
|
|
31
|
+
import { readZipEntryWithinLimit } from "../archive/zipEntryReader.js";
|
|
32
|
+
import { SIZE_LIMITS } from "../config/index.js";
|
|
33
|
+
/**
|
|
34
|
+
* Total budget for everything read out of one presentation.
|
|
35
|
+
*
|
|
36
|
+
* This class is a static utility with no `BaseFileProcessor` behind it, so
|
|
37
|
+
* there is no `maxSizeMB` to inherit and nothing else was bounding these
|
|
38
|
+
* reads at all — not even a late check. A .pptx is a ZIP, so any entry in it
|
|
39
|
+
* can declare whatever uncompressed size its author likes.
|
|
40
|
+
*/
|
|
41
|
+
const PPTX_MAX_TOTAL_BYTES = SIZE_LIMITS.DOCUMENT_MAX_MB * 1024 * 1024;
|
|
42
|
+
/**
|
|
43
|
+
* Read entries out of one presentation, refusing once the total passes budget.
|
|
44
|
+
*
|
|
45
|
+
* The budget spans the call rather than each entry: a deck of two hundred
|
|
46
|
+
* slides that each sit just under a per-entry limit is the obvious way around
|
|
47
|
+
* one. Returns null for an absent or unreadable entry, which every call site
|
|
48
|
+
* already treats as "no content here".
|
|
49
|
+
*/
|
|
50
|
+
function createBoundedEntryReader() {
|
|
51
|
+
let spent = 0;
|
|
52
|
+
return (entry) => {
|
|
53
|
+
if (!entry) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const remaining = PPTX_MAX_TOTAL_BYTES - spent;
|
|
57
|
+
if (remaining <= 0) {
|
|
58
|
+
throw new Error(`PPTX content exceeds the ${SIZE_LIMITS.DOCUMENT_MAX_MB}MB limit`);
|
|
59
|
+
}
|
|
60
|
+
const read = readZipEntryWithinLimit(entry, remaining, zlib);
|
|
61
|
+
if (read.status === "too-large") {
|
|
62
|
+
throw new Error(`PPTX content exceeds the ${SIZE_LIMITS.DOCUMENT_MAX_MB}MB limit`);
|
|
63
|
+
}
|
|
64
|
+
if (read.status !== "ok") {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
spent += read.buffer.length;
|
|
68
|
+
return read.buffer.toString("utf-8");
|
|
69
|
+
};
|
|
70
|
+
}
|
|
30
71
|
/**
|
|
31
72
|
* Regex to match text content within PowerPoint XML `<a:t>` elements.
|
|
32
73
|
* These elements contain the actual visible text on slides.
|
|
@@ -63,14 +104,17 @@ export class PptxProcessor {
|
|
|
63
104
|
static async extractText(content) {
|
|
64
105
|
const zip = new AdmZip(content);
|
|
65
106
|
const entries = zip.getEntries();
|
|
107
|
+
const readEntry = createBoundedEntryReader();
|
|
66
108
|
// Collect slide entries with their slide numbers for sorting
|
|
67
109
|
const slides = [];
|
|
68
110
|
for (const entry of entries) {
|
|
69
111
|
const match = entry.entryName.match(SLIDE_ENTRY_REGEX);
|
|
70
112
|
if (match) {
|
|
71
113
|
const slideNumber = parseInt(match[1], 10);
|
|
72
|
-
const xmlContent = entry
|
|
73
|
-
|
|
114
|
+
const xmlContent = readEntry(entry);
|
|
115
|
+
if (xmlContent !== null) {
|
|
116
|
+
slides.push({ slideNumber, xml: xmlContent });
|
|
117
|
+
}
|
|
74
118
|
}
|
|
75
119
|
}
|
|
76
120
|
// Sort slides by number (slide1, slide2, ...)
|
|
@@ -82,7 +126,7 @@ export class PptxProcessor {
|
|
|
82
126
|
parts.push(`Presentation: ${slides.length} slide(s)\n`);
|
|
83
127
|
for (const slide of slides) {
|
|
84
128
|
const texts = PptxProcessor.extractTextFromXml(slide.xml);
|
|
85
|
-
const notes = PptxProcessor.extractNotesForSlide(zip, slide.slideNumber);
|
|
129
|
+
const notes = PptxProcessor.extractNotesForSlide(zip, slide.slideNumber, readEntry);
|
|
86
130
|
// Emit a slide section when it has either body text or speaker notes.
|
|
87
131
|
if (texts.length > 0 || notes) {
|
|
88
132
|
parts.push(`### Slide ${slide.slideNumber}`);
|
|
@@ -111,12 +155,11 @@ export class PptxProcessor {
|
|
|
111
155
|
* @param slideNumber - 1-indexed slide number
|
|
112
156
|
* @returns The notes text (runs joined by a space), or null when absent
|
|
113
157
|
*/
|
|
114
|
-
static extractNotesForSlide(zip, slideNumber) {
|
|
115
|
-
const
|
|
116
|
-
if (
|
|
158
|
+
static extractNotesForSlide(zip, slideNumber, readEntry) {
|
|
159
|
+
const relsXml = readEntry(zip.getEntry(SLIDE_RELS_NAME(slideNumber)));
|
|
160
|
+
if (relsXml === null) {
|
|
117
161
|
return null;
|
|
118
162
|
}
|
|
119
|
-
const relsXml = relsEntry.getData().toString("utf-8");
|
|
120
163
|
let notesTarget = null;
|
|
121
164
|
RELATIONSHIP_TAG_REGEX.lastIndex = 0;
|
|
122
165
|
for (let match = RELATIONSHIP_TAG_REGEX.exec(relsXml); match !== null; match = RELATIONSHIP_TAG_REGEX.exec(relsXml)) {
|
|
@@ -134,11 +177,11 @@ export class PptxProcessor {
|
|
|
134
177
|
const normalized = notesTarget
|
|
135
178
|
.replace(/^\.\.\//, "ppt/")
|
|
136
179
|
.replace(/^\/+/, "");
|
|
137
|
-
const
|
|
138
|
-
if (
|
|
180
|
+
const notesXml = readEntry(zip.getEntry(normalized));
|
|
181
|
+
if (notesXml === null) {
|
|
139
182
|
return null;
|
|
140
183
|
}
|
|
141
|
-
const notes = PptxProcessor.extractTextFromXml(
|
|
184
|
+
const notes = PptxProcessor.extractTextFromXml(notesXml).join(" ");
|
|
142
185
|
return notes.trim() || null;
|
|
143
186
|
}
|
|
144
187
|
/**
|
|
@@ -175,6 +218,7 @@ export class PptxProcessor {
|
|
|
175
218
|
static async extractSlides(content, slideNumbers) {
|
|
176
219
|
const zip = new AdmZip(content);
|
|
177
220
|
const entries = zip.getEntries();
|
|
221
|
+
const readEntry = createBoundedEntryReader();
|
|
178
222
|
// Collect all slides
|
|
179
223
|
const slides = [];
|
|
180
224
|
for (const entry of entries) {
|
|
@@ -182,8 +226,10 @@ export class PptxProcessor {
|
|
|
182
226
|
if (match) {
|
|
183
227
|
const slideNumber = parseInt(match[1], 10);
|
|
184
228
|
if (slideNumbers.includes(slideNumber)) {
|
|
185
|
-
const xmlContent = entry
|
|
186
|
-
|
|
229
|
+
const xmlContent = readEntry(entry);
|
|
230
|
+
if (xmlContent !== null) {
|
|
231
|
+
slides.push({ slideNumber, xml: xmlContent });
|
|
232
|
+
}
|
|
187
233
|
}
|
|
188
234
|
}
|
|
189
235
|
}
|
|
@@ -774,6 +774,21 @@ export type ArchiveDecompressionResult = {
|
|
|
774
774
|
* perfectly well-formed, and telling the user it is damaged would send them to
|
|
775
775
|
* re-create a file that was never broken.
|
|
776
776
|
*/
|
|
777
|
+
/**
|
|
778
|
+
* The slice of an adm-zip entry the bounded reader depends on.
|
|
779
|
+
*
|
|
780
|
+
* Structural rather than adm-zip's own `IZipEntry` so the reader states what it
|
|
781
|
+
* actually needs — the compressed bytes and the header fields it refuses to
|
|
782
|
+
* trust — instead of importing a library type it would then have to satisfy in
|
|
783
|
+
* full when building a test double.
|
|
784
|
+
*/
|
|
785
|
+
export type BoundedZipEntry = {
|
|
786
|
+
getCompressedData: () => Buffer;
|
|
787
|
+
header: {
|
|
788
|
+
method: number;
|
|
789
|
+
crc: number;
|
|
790
|
+
};
|
|
791
|
+
};
|
|
777
792
|
export type ArchiveEntryReadResult = {
|
|
778
793
|
readonly status: "ok";
|
|
779
794
|
readonly buffer: Buffer;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "10.12.
|
|
3
|
+
"version": "10.12.5",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
|
|
6
6
|
"author": {
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
"test:system-messages": "npx tsx test/continuous-test-suite-system-messages.ts",
|
|
164
164
|
"test:test-stubs": "npx tsx test/continuous-test-suite-test-stubs.ts",
|
|
165
165
|
"test:tool-routing-semantic": "npx tsx test/continuous-test-suite-tool-routing-semantic.ts",
|
|
166
|
-
"test:unit": "pnpm run test:envguard && pnpm run test:bugfixes && pnpm run test:file-detector-extension && pnpm run test:file-detector-magic-bytes && pnpm run test:mcp:infra && pnpm run test:mcp:bash && pnpm run test:mcp:limits && pnpm run test:mcp:spans && pnpm run test:autoresearch:redis && pnpm run test:tool-routing && pnpm run test:tool-routing-cli && pnpm run test:tool-dedup && pnpm run test:model-pool && pnpm run test:litellm-context && pnpm run test:dedup-execute-map && pnpm run test:step-budget-guard && pnpm run test:agent-plumbing && pnpm run test:tool-execution-recorder && pnpm run test:proxy-terminal-errors && pnpm run test:proxy-usage-refresh && pnpm run test:system-messages && pnpm run test:tool-routing-semantic && pnpm run test:anthropic-tools-policy && pnpm run test:anthropic-structured && pnpm run test:sagemaker-tools && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop && pnpm run test:model-capabilities && pnpm run test:agent-runtime:vitest && pnpm run test:agent-delegation && pnpm run test:retry-after:vitest && pnpm run test:sampling-params && pnpm run test:structured-recovery && pnpm run test:prompt-redaction && pnpm run test:mcp-result-cache && pnpm run test:test-stubs && pnpm run test:model-not-found-retryable && pnpm run test:websearch-grounding && pnpm run test:archive:security",
|
|
166
|
+
"test:unit": "pnpm run test:envguard && pnpm run test:bugfixes && pnpm run test:file-detector-extension && pnpm run test:file-detector-magic-bytes && pnpm run test:mcp:infra && pnpm run test:mcp:bash && pnpm run test:mcp:limits && pnpm run test:mcp:spans && pnpm run test:autoresearch:redis && pnpm run test:tool-routing && pnpm run test:tool-routing-cli && pnpm run test:tool-dedup && pnpm run test:model-pool && pnpm run test:litellm-context && pnpm run test:dedup-execute-map && pnpm run test:step-budget-guard && pnpm run test:agent-plumbing && pnpm run test:tool-execution-recorder && pnpm run test:proxy-terminal-errors && pnpm run test:proxy-usage-refresh && pnpm run test:system-messages && pnpm run test:tool-routing-semantic && pnpm run test:anthropic-tools-policy && pnpm run test:anthropic-structured && pnpm run test:sagemaker-tools && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop && pnpm run test:model-capabilities && pnpm run test:agent-runtime:vitest && pnpm run test:agent-delegation && pnpm run test:retry-after:vitest && pnpm run test:sampling-params && pnpm run test:structured-recovery && pnpm run test:prompt-redaction && pnpm run test:mcp-result-cache && pnpm run test:test-stubs && pnpm run test:model-not-found-retryable && pnpm run test:websearch-grounding && pnpm run test:archive:security && pnpm run test:office:security",
|
|
167
167
|
"// CI tier — live providers, runs only when API keys are present (test:credentials and test:dynamic make real provider calls when keys are set, so they live here, not in test:unit)": "",
|
|
168
168
|
"test:live": "pnpm run test:providers && pnpm run test:mcp:http && pnpm run test:mcp:sdk && pnpm run test:mcp:cli && pnpm run test:observability && pnpm run test:context && pnpm run test:memory && pnpm run test:tool-reliability && pnpm run test:evaluation && pnpm run test:autoresearch && pnpm run test:credentials && pnpm run test:dynamic",
|
|
169
169
|
"// CI tier — product output (image/video/TTS/PPT) — costs $$ per run": "",
|
|
@@ -246,7 +246,8 @@
|
|
|
246
246
|
"test:structured-recovery": "npx tsx test/continuous-test-suite-structured-recovery.ts",
|
|
247
247
|
"test:prompt-redaction": "npx tsx test/continuous-test-suite-prompt-redaction.ts",
|
|
248
248
|
"test:mcp-result-cache": "npx tsx test/continuous-test-suite-mcp-result-cache.ts",
|
|
249
|
-
"test:archive:security": "npx tsx test/continuous-test-suite-archive-security.ts"
|
|
249
|
+
"test:archive:security": "npx tsx test/continuous-test-suite-archive-security.ts",
|
|
250
|
+
"test:office:security": "npx tsx test/continuous-test-suite-office-security.ts"
|
|
250
251
|
},
|
|
251
252
|
"files": [
|
|
252
253
|
"dist",
|