@juspay/neurolink 9.88.4 → 9.88.6

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.
@@ -121,6 +121,7 @@ export declare const CLI_LIMITS: {
121
121
  export declare const PDF_LIMITS: {
122
122
  MAX_SIZE_MB: number;
123
123
  DEFAULT_MAX_PAGES: number;
124
+ MAX_SCALE: number;
124
125
  };
125
126
  export declare const SYSTEM_LIMITS: {
126
127
  MAX_PROMPT_LENGTH: number;
@@ -217,6 +217,9 @@ export const PDF_LIMITS = {
217
217
  MAX_SIZE_MB: 20,
218
218
  // Default maximum pages for image conversion
219
219
  DEFAULT_MAX_PAGES: 20,
220
+ // Upper bound for the render scale factor. Above this, a single page can
221
+ // allocate hundreds of MB; scale <= 0 produces a degenerate viewport.
222
+ MAX_SCALE: 10,
220
223
  };
221
224
  // Performance and System Limits
222
225
  export const SYSTEM_LIMITS = {
@@ -121,6 +121,7 @@ export declare const CLI_LIMITS: {
121
121
  export declare const PDF_LIMITS: {
122
122
  MAX_SIZE_MB: number;
123
123
  DEFAULT_MAX_PAGES: number;
124
+ MAX_SCALE: number;
124
125
  };
125
126
  export declare const SYSTEM_LIMITS: {
126
127
  MAX_PROMPT_LENGTH: number;
@@ -217,6 +217,9 @@ export const PDF_LIMITS = {
217
217
  MAX_SIZE_MB: 20,
218
218
  // Default maximum pages for image conversion
219
219
  DEFAULT_MAX_PAGES: 20,
220
+ // Upper bound for the render scale factor. Above this, a single page can
221
+ // allocate hundreds of MB; scale <= 0 produces a degenerate viewport.
222
+ MAX_SCALE: 10,
220
223
  };
221
224
  // Performance and System Limits
222
225
  export const SYSTEM_LIMITS = {
@@ -42,6 +42,20 @@ export declare class PptxProcessor {
42
42
  * @throws Error if the buffer is not a valid ZIP/PPTX file
43
43
  */
44
44
  static extractText(content: Buffer): Promise<string | null>;
45
+ /**
46
+ * Extract the speaker notes for one slide.
47
+ *
48
+ * Speaker notes live in a separate `ppt/notesSlides/notesSlideN.xml` part
49
+ * whose number is NOT tied to the slide number — the link is declared in the
50
+ * slide's own relationships file (`ppt/slides/_rels/slideN.xml.rels`) via a
51
+ * relationship of type `.../notesSlide`. We resolve that target, then pull the
52
+ * `<a:t>` runs from the notes part. Returns null when the slide has no notes.
53
+ *
54
+ * @param zip - The opened PPTX archive
55
+ * @param slideNumber - 1-indexed slide number
56
+ * @returns The notes text (runs joined by a space), or null when absent
57
+ */
58
+ private static extractNotesForSlide;
45
59
  /**
46
60
  * Extract text strings from a slide XML document.
47
61
  * Finds all `<a:t>` elements and returns their text content.
@@ -37,6 +37,14 @@ const TEXT_ELEMENT_REGEX = /<a:t[^>]*>([\s\S]*?)<\/a:t>/g;
37
37
  * Matches entries like "ppt/slides/slide1.xml", "ppt/slides/slide12.xml".
38
38
  */
39
39
  const SLIDE_ENTRY_REGEX = /^ppt\/slides\/slide(\d+)\.xml$/;
40
+ /**
41
+ * A slide's per-slide relationships file, e.g.
42
+ * "ppt/slides/_rels/slide1.xml.rels", which links the slide to its speaker
43
+ * notes part (among other relationships).
44
+ */
45
+ const SLIDE_RELS_NAME = (n) => `ppt/slides/_rels/slide${n}.xml.rels`;
46
+ /** Matches a single `<Relationship .../>` tag inside a .rels document. */
47
+ const RELATIONSHIP_TAG_REGEX = /<Relationship\b[^>]*\/?>/g;
40
48
  /**
41
49
  * Static utility class for extracting text from PPTX files.
42
50
  *
@@ -74,15 +82,65 @@ export class PptxProcessor {
74
82
  parts.push(`Presentation: ${slides.length} slide(s)\n`);
75
83
  for (const slide of slides) {
76
84
  const texts = PptxProcessor.extractTextFromXml(slide.xml);
77
- if (texts.length > 0) {
85
+ const notes = PptxProcessor.extractNotesForSlide(zip, slide.slideNumber);
86
+ // Emit a slide section when it has either body text or speaker notes.
87
+ if (texts.length > 0 || notes) {
78
88
  parts.push(`### Slide ${slide.slideNumber}`);
79
- parts.push(texts.join("\n"));
89
+ if (texts.length > 0) {
90
+ parts.push(texts.join("\n"));
91
+ }
92
+ if (notes) {
93
+ parts.push(`**Speaker notes:** ${notes}`);
94
+ }
80
95
  parts.push(""); // blank line between slides
81
96
  }
82
97
  }
83
98
  const result = parts.join("\n").trim();
84
99
  return result || null;
85
100
  }
101
+ /**
102
+ * Extract the speaker notes for one slide.
103
+ *
104
+ * Speaker notes live in a separate `ppt/notesSlides/notesSlideN.xml` part
105
+ * whose number is NOT tied to the slide number — the link is declared in the
106
+ * slide's own relationships file (`ppt/slides/_rels/slideN.xml.rels`) via a
107
+ * relationship of type `.../notesSlide`. We resolve that target, then pull the
108
+ * `<a:t>` runs from the notes part. Returns null when the slide has no notes.
109
+ *
110
+ * @param zip - The opened PPTX archive
111
+ * @param slideNumber - 1-indexed slide number
112
+ * @returns The notes text (runs joined by a space), or null when absent
113
+ */
114
+ static extractNotesForSlide(zip, slideNumber) {
115
+ const relsEntry = zip.getEntry(SLIDE_RELS_NAME(slideNumber));
116
+ if (!relsEntry) {
117
+ return null;
118
+ }
119
+ const relsXml = relsEntry.getData().toString("utf-8");
120
+ let notesTarget = null;
121
+ RELATIONSHIP_TAG_REGEX.lastIndex = 0;
122
+ for (let match = RELATIONSHIP_TAG_REGEX.exec(relsXml); match !== null; match = RELATIONSHIP_TAG_REGEX.exec(relsXml)) {
123
+ const tag = match[0];
124
+ if (/Type="[^"]*\/notesSlide"/.test(tag)) {
125
+ notesTarget = tag.match(/Target="([^"]+)"/)?.[1] ?? null;
126
+ break;
127
+ }
128
+ }
129
+ if (!notesTarget) {
130
+ return null;
131
+ }
132
+ // Targets are relative to the slide's folder (ppt/slides/), e.g.
133
+ // "../notesSlides/notesSlide1.xml" → "ppt/notesSlides/notesSlide1.xml".
134
+ const normalized = notesTarget
135
+ .replace(/^\.\.\//, "ppt/")
136
+ .replace(/^\/+/, "");
137
+ const notesEntry = zip.getEntry(normalized);
138
+ if (!notesEntry) {
139
+ return null;
140
+ }
141
+ const notes = PptxProcessor.extractTextFromXml(notesEntry.getData().toString("utf-8")).join(" ");
142
+ return notes.trim() || null;
143
+ }
86
144
  /**
87
145
  * Extract text strings from a slide XML document.
88
146
  * Finds all `<a:t>` elements and returns their text content.
@@ -267,6 +267,12 @@ export class PDFProcessor {
267
267
  if (format !== "png") {
268
268
  throw new Error(`Invalid format: "${format}". Only "png" format is currently supported.`);
269
269
  }
270
+ // 0b. Validate the render scale. A non-finite or non-positive scale yields a
271
+ // degenerate viewport (blank/zero-dimension render), and an excessive scale
272
+ // can allocate hundreds of MB per page — reject both with a clear message.
273
+ if (!Number.isFinite(scale) || scale <= 0 || scale > PDF_LIMITS.MAX_SCALE) {
274
+ throw new Error(`Invalid scale: ${scale}. Scale must be a finite number greater than 0 and at most ${PDF_LIMITS.MAX_SCALE}.`);
275
+ }
270
276
  // 1. Validate buffer is not empty or too small
271
277
  if (!pdfBuffer || pdfBuffer.length < 5) {
272
278
  throw new Error("Invalid PDF: Buffer is too small or empty. " +
@@ -42,6 +42,20 @@ export declare class PptxProcessor {
42
42
  * @throws Error if the buffer is not a valid ZIP/PPTX file
43
43
  */
44
44
  static extractText(content: Buffer): Promise<string | null>;
45
+ /**
46
+ * Extract the speaker notes for one slide.
47
+ *
48
+ * Speaker notes live in a separate `ppt/notesSlides/notesSlideN.xml` part
49
+ * whose number is NOT tied to the slide number — the link is declared in the
50
+ * slide's own relationships file (`ppt/slides/_rels/slideN.xml.rels`) via a
51
+ * relationship of type `.../notesSlide`. We resolve that target, then pull the
52
+ * `<a:t>` runs from the notes part. Returns null when the slide has no notes.
53
+ *
54
+ * @param zip - The opened PPTX archive
55
+ * @param slideNumber - 1-indexed slide number
56
+ * @returns The notes text (runs joined by a space), or null when absent
57
+ */
58
+ private static extractNotesForSlide;
45
59
  /**
46
60
  * Extract text strings from a slide XML document.
47
61
  * Finds all `<a:t>` elements and returns their text content.
@@ -37,6 +37,14 @@ const TEXT_ELEMENT_REGEX = /<a:t[^>]*>([\s\S]*?)<\/a:t>/g;
37
37
  * Matches entries like "ppt/slides/slide1.xml", "ppt/slides/slide12.xml".
38
38
  */
39
39
  const SLIDE_ENTRY_REGEX = /^ppt\/slides\/slide(\d+)\.xml$/;
40
+ /**
41
+ * A slide's per-slide relationships file, e.g.
42
+ * "ppt/slides/_rels/slide1.xml.rels", which links the slide to its speaker
43
+ * notes part (among other relationships).
44
+ */
45
+ const SLIDE_RELS_NAME = (n) => `ppt/slides/_rels/slide${n}.xml.rels`;
46
+ /** Matches a single `<Relationship .../>` tag inside a .rels document. */
47
+ const RELATIONSHIP_TAG_REGEX = /<Relationship\b[^>]*\/?>/g;
40
48
  /**
41
49
  * Static utility class for extracting text from PPTX files.
42
50
  *
@@ -74,15 +82,65 @@ export class PptxProcessor {
74
82
  parts.push(`Presentation: ${slides.length} slide(s)\n`);
75
83
  for (const slide of slides) {
76
84
  const texts = PptxProcessor.extractTextFromXml(slide.xml);
77
- if (texts.length > 0) {
85
+ const notes = PptxProcessor.extractNotesForSlide(zip, slide.slideNumber);
86
+ // Emit a slide section when it has either body text or speaker notes.
87
+ if (texts.length > 0 || notes) {
78
88
  parts.push(`### Slide ${slide.slideNumber}`);
79
- parts.push(texts.join("\n"));
89
+ if (texts.length > 0) {
90
+ parts.push(texts.join("\n"));
91
+ }
92
+ if (notes) {
93
+ parts.push(`**Speaker notes:** ${notes}`);
94
+ }
80
95
  parts.push(""); // blank line between slides
81
96
  }
82
97
  }
83
98
  const result = parts.join("\n").trim();
84
99
  return result || null;
85
100
  }
101
+ /**
102
+ * Extract the speaker notes for one slide.
103
+ *
104
+ * Speaker notes live in a separate `ppt/notesSlides/notesSlideN.xml` part
105
+ * whose number is NOT tied to the slide number — the link is declared in the
106
+ * slide's own relationships file (`ppt/slides/_rels/slideN.xml.rels`) via a
107
+ * relationship of type `.../notesSlide`. We resolve that target, then pull the
108
+ * `<a:t>` runs from the notes part. Returns null when the slide has no notes.
109
+ *
110
+ * @param zip - The opened PPTX archive
111
+ * @param slideNumber - 1-indexed slide number
112
+ * @returns The notes text (runs joined by a space), or null when absent
113
+ */
114
+ static extractNotesForSlide(zip, slideNumber) {
115
+ const relsEntry = zip.getEntry(SLIDE_RELS_NAME(slideNumber));
116
+ if (!relsEntry) {
117
+ return null;
118
+ }
119
+ const relsXml = relsEntry.getData().toString("utf-8");
120
+ let notesTarget = null;
121
+ RELATIONSHIP_TAG_REGEX.lastIndex = 0;
122
+ for (let match = RELATIONSHIP_TAG_REGEX.exec(relsXml); match !== null; match = RELATIONSHIP_TAG_REGEX.exec(relsXml)) {
123
+ const tag = match[0];
124
+ if (/Type="[^"]*\/notesSlide"/.test(tag)) {
125
+ notesTarget = tag.match(/Target="([^"]+)"/)?.[1] ?? null;
126
+ break;
127
+ }
128
+ }
129
+ if (!notesTarget) {
130
+ return null;
131
+ }
132
+ // Targets are relative to the slide's folder (ppt/slides/), e.g.
133
+ // "../notesSlides/notesSlide1.xml" → "ppt/notesSlides/notesSlide1.xml".
134
+ const normalized = notesTarget
135
+ .replace(/^\.\.\//, "ppt/")
136
+ .replace(/^\/+/, "");
137
+ const notesEntry = zip.getEntry(normalized);
138
+ if (!notesEntry) {
139
+ return null;
140
+ }
141
+ const notes = PptxProcessor.extractTextFromXml(notesEntry.getData().toString("utf-8")).join(" ");
142
+ return notes.trim() || null;
143
+ }
86
144
  /**
87
145
  * Extract text strings from a slide XML document.
88
146
  * Finds all `<a:t>` elements and returns their text content.
@@ -267,6 +267,12 @@ export class PDFProcessor {
267
267
  if (format !== "png") {
268
268
  throw new Error(`Invalid format: "${format}". Only "png" format is currently supported.`);
269
269
  }
270
+ // 0b. Validate the render scale. A non-finite or non-positive scale yields a
271
+ // degenerate viewport (blank/zero-dimension render), and an excessive scale
272
+ // can allocate hundreds of MB per page — reject both with a clear message.
273
+ if (!Number.isFinite(scale) || scale <= 0 || scale > PDF_LIMITS.MAX_SCALE) {
274
+ throw new Error(`Invalid scale: ${scale}. Scale must be a finite number greater than 0 and at most ${PDF_LIMITS.MAX_SCALE}.`);
275
+ }
270
276
  // 1. Validate buffer is not empty or too small
271
277
  if (!pdfBuffer || pdfBuffer.length < 5) {
272
278
  throw new Error("Invalid PDF: Buffer is too small or empty. " +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "9.88.4",
3
+ "version": "9.88.6",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (58+ servers), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
6
6
  "author": {