@juspay/neurolink 9.94.2 → 9.94.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/agent/directTools.js +3 -1
  3. package/dist/browser/neurolink.min.js +400 -397
  4. package/dist/cli/factories/commandFactory.d.ts +6 -0
  5. package/dist/cli/factories/commandFactory.js +36 -8
  6. package/dist/lib/agent/directTools.js +3 -1
  7. package/dist/lib/neurolink.js +5 -0
  8. package/dist/lib/rag/document/loaders.js +20 -4
  9. package/dist/lib/types/file.d.ts +43 -0
  10. package/dist/lib/types/generate.d.ts +3 -11
  11. package/dist/lib/types/rag.d.ts +7 -0
  12. package/dist/lib/types/stream.d.ts +2 -6
  13. package/dist/lib/utils/csvProcessor.d.ts +68 -10
  14. package/dist/lib/utils/csvProcessor.js +499 -135
  15. package/dist/lib/utils/errorHandling.d.ts +32 -0
  16. package/dist/lib/utils/errorHandling.js +86 -0
  17. package/dist/lib/utils/imageProcessor.d.ts +42 -4
  18. package/dist/lib/utils/imageProcessor.js +96 -19
  19. package/dist/lib/utils/messageBuilder.js +49 -11
  20. package/dist/lib/utils/multimodalOptionsBuilder.d.ts +1 -5
  21. package/dist/lib/utils/textEncoding.d.ts +25 -0
  22. package/dist/lib/utils/textEncoding.js +141 -0
  23. package/dist/neurolink.js +5 -0
  24. package/dist/rag/document/loaders.js +20 -4
  25. package/dist/types/file.d.ts +43 -0
  26. package/dist/types/generate.d.ts +3 -11
  27. package/dist/types/rag.d.ts +7 -0
  28. package/dist/types/stream.d.ts +2 -6
  29. package/dist/utils/csvProcessor.d.ts +68 -10
  30. package/dist/utils/csvProcessor.js +498 -134
  31. package/dist/utils/errorHandling.d.ts +32 -0
  32. package/dist/utils/errorHandling.js +86 -0
  33. package/dist/utils/imageProcessor.d.ts +42 -4
  34. package/dist/utils/imageProcessor.js +96 -19
  35. package/dist/utils/messageBuilder.js +49 -11
  36. package/dist/utils/multimodalOptionsBuilder.d.ts +1 -5
  37. package/dist/utils/textEncoding.d.ts +25 -0
  38. package/dist/utils/textEncoding.js +140 -0
  39. package/package.json +3 -1
@@ -178,6 +178,12 @@ export declare class CLICommandFactory {
178
178
  * Build multimodal input from CLI arguments
179
179
  */
180
180
  private static buildGenerateMultimodalInput;
181
+ /**
182
+ * Build CSV processor options from CLI argv (#1199). Shared by executeGenerate
183
+ * and executeRealStream, which previously built byte-for-byte identical
184
+ * objects independently.
185
+ */
186
+ private static buildCsvOptionsFromArgv;
181
187
  /**
182
188
  * Build output configuration for generate request
183
189
  */
@@ -198,6 +198,25 @@ export class CLICommandFactory {
198
198
  " • markdown: Formatted table (readable, best for small files <100 rows)\n" +
199
199
  " • json: Structured JSON array (best for programmatic use, higher tokens)",
200
200
  },
201
+ "csv-encoding": {
202
+ type: "string",
203
+ description: "Character encoding for CSV files (e.g. utf-8, utf-16le, windows-1252). Auto-detected when omitted (#362).",
204
+ },
205
+ "csv-sanitize-names": {
206
+ type: "boolean",
207
+ default: false,
208
+ description: "Rewrite CSV column headers into valid identifiers (e.g. 'Price ($)' → 'price') (#378).",
209
+ },
210
+ "csv-name-case": {
211
+ type: "string",
212
+ choices: ["snake_case", "camelCase"],
213
+ default: "snake_case",
214
+ description: "Case style for sanitized CSV column names (used with --csv-sanitize-names) (#378).",
215
+ },
216
+ "csv-parse-timeout-ms": {
217
+ type: "number",
218
+ description: "Wall-clock cap (ms) for CSV parsing; returns partial rows on timeout (#379).",
219
+ },
201
220
  model: {
202
221
  type: "string",
203
222
  description: "Specific model to use (e.g. gemini-2.5-pro, gemini-2.5-flash)",
@@ -2380,6 +2399,21 @@ export class CLICommandFactory {
2380
2399
  ...(files && { files }),
2381
2400
  };
2382
2401
  }
2402
+ /**
2403
+ * Build CSV processor options from CLI argv (#1199). Shared by executeGenerate
2404
+ * and executeRealStream, which previously built byte-for-byte identical
2405
+ * objects independently.
2406
+ */
2407
+ static buildCsvOptionsFromArgv(argv) {
2408
+ return {
2409
+ maxRows: argv.csvMaxRows,
2410
+ formatStyle: argv.csvFormat,
2411
+ encoding: argv.csvEncoding,
2412
+ sanitizeColumnNames: argv.csvSanitizeNames,
2413
+ columnNameCase: argv.csvNameCase,
2414
+ parseTimeoutMs: argv.csvParseTimeoutMs,
2415
+ };
2416
+ }
2383
2417
  /**
2384
2418
  * Build output configuration for generate request
2385
2419
  */
@@ -2614,10 +2648,7 @@ export class CLICommandFactory {
2614
2648
  const inputAudioFormat = inferAudioFormatFromPath(inputAudioPath);
2615
2649
  const runGenerate = () => sdk.generate({
2616
2650
  input: generateInput,
2617
- csvOptions: {
2618
- maxRows: argv.csvMaxRows,
2619
- formatStyle: argv.csvFormat,
2620
- },
2651
+ csvOptions: CLICommandFactory.buildCsvOptionsFromArgv(argv),
2621
2652
  videoOptions: {
2622
2653
  frames: argv.videoFrames,
2623
2654
  quality: argv.videoQuality,
@@ -2869,10 +2900,7 @@ export class CLICommandFactory {
2869
2900
  ...(videoFiles && { videoFiles }),
2870
2901
  ...(files && { files }),
2871
2902
  },
2872
- csvOptions: {
2873
- maxRows: argv.csvMaxRows,
2874
- formatStyle: argv.csvFormat,
2875
- },
2903
+ csvOptions: CLICommandFactory.buildCsvOptionsFromArgv(argv),
2876
2904
  videoOptions: {
2877
2905
  frames: argv.videoFrames,
2878
2906
  quality: argv.videoQuality,
@@ -357,7 +357,9 @@ export const directAgentTools = {
357
357
  logger.debug(`[analyzeCSV] Resolved path: ${resolvedPath}`);
358
358
  // Parse CSV using streaming from disk (memory efficient)
359
359
  logger.info(`[analyzeCSV] Starting CSV parsing (max ${maxRows} rows)...`);
360
- const rows = (await CSVProcessor.parseCSVFile(resolvedPath, maxRows));
360
+ // #384: parseCSVFile now returns validated Record<string, string |
361
+ // undefined>[] rows, so the previous unchecked cast is unnecessary.
362
+ const rows = await CSVProcessor.parseCSVFile(resolvedPath, maxRows);
361
363
  logger.info(`[analyzeCSV] ✅ CSV parsing complete: ${rows.length} rows`);
362
364
  if (rows.length === 0) {
363
365
  logger.warn(`[analyzeCSV] No data rows found`);
@@ -3820,6 +3820,11 @@ Current user's request: ${currentInput}`;
3820
3820
  evaluationDomain: options.evaluationDomain,
3821
3821
  toolUsageContext: options.toolUsageContext,
3822
3822
  input: options.input,
3823
+ // CSV processing options must survive the reconstruction into
3824
+ // TextGenerationOptions — the message builder reads them (encoding,
3825
+ // formatting, sanitization, parse timeout). Omitting them silently
3826
+ // dropped every csvOptions field (incl. the CLI --csv-* flags).
3827
+ csvOptions: options.csvOptions,
3823
3828
  region: options.region,
3824
3829
  tts: options.tts,
3825
3830
  stt: options.stt,
@@ -27,7 +27,7 @@ import { existsSync } from "fs";
27
27
  import { readFile } from "fs/promises";
28
28
  import { basename, extname } from "path";
29
29
  import { logger } from "../../utils/logger.js";
30
- import { CSVProcessor, stripBom, stripMetadataLine, splitCsvLines, } from "../../utils/csvProcessor.js";
30
+ import { CSVProcessor, stripBom, stripMetadataLine, splitCsvLines, dedupeColumnNames, sanitizeColumnName, } from "../../utils/csvProcessor.js";
31
31
  import { splitCSVFields } from "../../utils/csvUtils.js";
32
32
  import { MDocument } from "./MDocument.js";
33
33
  /**
@@ -117,7 +117,7 @@ export class JSONLoader extends TextLoader {
117
117
  export class CSVLoader extends TextLoader {
118
118
  async load(source, options) {
119
119
  const content = await this.loadContent(source, options?.encoding);
120
- const { delimiter, hasHeader = true, columns, outputFormat = "text", } = options || {};
120
+ const { delimiter, hasHeader = true, columns, outputFormat = "text", sanitizeColumnNames = false, columnNameCase = "snake_case", } = options || {};
121
121
  // #361: an explicit delimiter always wins; otherwise detect it from the
122
122
  // content + extension so a .tsv fed through RAG isn't collapsed into one
123
123
  // comma-column (CSVLoader.canHandle already accepts .tsv).
@@ -133,16 +133,32 @@ export class CSVLoader extends TextLoader {
133
133
  // escaping or quoted delimiters in the headerless-columns branch.
134
134
  const { dataLines: strippedLines } = stripMetadataLine(splitCsvLines(stripBom(content)));
135
135
  const lines = strippedLines.filter((line) => line.trim());
136
- const headers = hasHeader
136
+ const rawHeaders = hasHeader
137
137
  ? splitCSVFields(lines[0] ?? "", effectiveDelimiter).map((h) => h.trim())
138
138
  : columns ||
139
139
  splitCSVFields(lines[0] ?? "", effectiveDelimiter).map((_, i) => `col${i + 1}`);
140
+ // #378: apply the same opt-in header sanitization CSVProcessor offers, so
141
+ // the RAG ingestion path doesn't corrupt "Price ($)"-style headers as keys.
142
+ const headers = sanitizeColumnNames
143
+ ? dedupeColumnNames(rawHeaders.map((h, i) => sanitizeColumnName(h, columnNameCase, i)))
144
+ : rawHeaders;
140
145
  const dataLines = hasHeader ? lines.slice(1) : lines;
141
146
  const rows = dataLines.map((line) => splitCSVFields(line, effectiveDelimiter).map((field) => field.trim()));
142
147
  let formattedContent;
143
148
  switch (outputFormat) {
144
149
  case "json":
145
- formattedContent = JSON.stringify(rows.map((row) => Object.fromEntries(headers.map((h, i) => [h, row[i]]))), null, 2);
150
+ formattedContent = JSON.stringify(rows.map((row) => {
151
+ // Defense-in-depth against a caller-controlled header literally
152
+ // named "__proto__"/"constructor"/"prototype" (#1199);
153
+ // Object.fromEntries can't actually pollute Object.prototype here
154
+ // (spec semantics), but a null-prototype target avoids relying on
155
+ // that for future readers.
156
+ const record = Object.create(null);
157
+ headers.forEach((h, i) => {
158
+ record[h] = row[i];
159
+ });
160
+ return record;
161
+ }), null, 2);
146
162
  break;
147
163
  case "markdown":
148
164
  formattedContent = this.toMarkdownTable(headers, rows);
@@ -80,6 +80,17 @@ export type FileProcessingResult = {
80
80
  hasHeaders?: boolean;
81
81
  /** Detected delimiter */
82
82
  detectedDelimiter?: string;
83
+ /** Detected (or overridden) character encoding used to decode the CSV (#362) */
84
+ detectedEncoding?: string;
85
+ /** Confidence (0-100) of the detected encoding (#362) */
86
+ encodingConfidence?: number;
87
+ /** Original→sanitized column-name mapping when sanitizeColumnNames is on (#378) */
88
+ columnNameMapping?: Array<{
89
+ original: string;
90
+ sanitized: string;
91
+ }>;
92
+ /** True when the parse hit its time budget and returned partial rows (#379) */
93
+ parseTimedOut?: boolean;
83
94
  version?: string;
84
95
  estimatedPages?: number | null;
85
96
  provider?: string;
@@ -125,6 +136,8 @@ export type CSVDataQualityWarning = {
125
136
  */
126
137
  export type CSVColumnMetadata = {
127
138
  name: string;
139
+ /** Original header text before sanitization, when sanitizeColumnNames rewrote it (#378) */
140
+ originalName?: string;
128
141
  index: number;
129
142
  detectedType: CSVColumnDataType;
130
143
  /** Confidence of type detection (0-100) */
@@ -146,6 +159,17 @@ export type CSVColumnMetadata = {
146
159
  /** Column name validation issues */
147
160
  nameIssues?: string[];
148
161
  };
162
+ /** A parsed CSV row: string-keyed with string (or missing) cell values (#384). */
163
+ export type CSVRow = Record<string, string | undefined>;
164
+ /** Result of decoding a buffer with encoding detection (#362). */
165
+ export type DecodedBuffer = {
166
+ /** Decoded text with any BOM removed. */
167
+ text: string;
168
+ /** iconv-lite label actually used to decode. */
169
+ encoding: string;
170
+ /** Detection confidence 0-100 (100 for BOM/override, 0 for the UTF-8 fallback). */
171
+ confidence: number;
172
+ };
149
173
  /**
150
174
  * CSV processor options
151
175
  */
@@ -155,6 +179,25 @@ export type CSVProcessorOptions = {
155
179
  includeHeaders?: boolean;
156
180
  sampleDataFormat?: SampleDataFormat;
157
181
  extension?: string | null;
182
+ /**
183
+ * Character encoding override (#362). When omitted, the encoding is detected
184
+ * from a BOM then `chardet`, falling back to UTF-8. Accepts any label
185
+ * `iconv-lite` supports (e.g. "utf-8", "utf-16le", "windows-1252", "latin1").
186
+ */
187
+ encoding?: string;
188
+ /**
189
+ * Rewrite column headers into valid identifiers (#378). Opt-in; default false
190
+ * preserves the raw header strings as object keys.
191
+ */
192
+ sanitizeColumnNames?: boolean;
193
+ /** Case style used when `sanitizeColumnNames` is on (#378). Default "snake_case". */
194
+ columnNameCase?: "camelCase" | "snake_case";
195
+ /**
196
+ * Wall-clock cap for the streaming parse in milliseconds (#379). On timeout the
197
+ * parse returns the rows collected so far and flags `metadata.parseTimedOut`,
198
+ * rather than hanging forever. Defaults: 30s for strings, 5min for files.
199
+ */
200
+ parseTimeoutMs?: number;
158
201
  };
159
202
  /**
160
203
  * PDF API types for different providers
@@ -15,7 +15,7 @@ import type { AvatarOptions, AvatarResult } from "./avatar.js";
15
15
  import type { MusicOptions, MusicResult } from "./music.js";
16
16
  import type { StandardRecord, ValidationSchema, ZodUnknownSchema } from "./aliases.js";
17
17
  import type { NeurolinkCredentials } from "./providers.js";
18
- import type { FileWithMetadata } from "./file.js";
18
+ import type { CSVProcessorOptions, FileWithMetadata } from "./file.js";
19
19
  import type { WorkflowConfig } from "./workflow.js";
20
20
  import type { Schema, Tool, ToolChoice } from "./tools.js";
21
21
  import type { StepResult, LanguageModel } from "./providers.js";
@@ -123,11 +123,7 @@ export type GenerateOptions = {
123
123
  */
124
124
  music?: MusicOptions;
125
125
  };
126
- csvOptions?: {
127
- maxRows?: number;
128
- formatStyle?: "raw" | "markdown" | "json";
129
- includeHeaders?: boolean;
130
- };
126
+ csvOptions?: CSVProcessorOptions;
131
127
  videoOptions?: {
132
128
  frames?: number;
133
129
  quality?: number;
@@ -1133,11 +1129,7 @@ export type TextGenerationOptions = {
1133
1129
  onError?: OnErrorCallback;
1134
1130
  expectedOutcome?: string;
1135
1131
  evaluationCriteria?: string[];
1136
- csvOptions?: {
1137
- maxRows?: number;
1138
- formatStyle?: "raw" | "markdown" | "json";
1139
- includeHeaders?: boolean;
1140
- };
1132
+ csvOptions?: CSVProcessorOptions;
1141
1133
  enableSummarization?: boolean;
1142
1134
  /**
1143
1135
  * Skip injecting tool schemas into the system prompt.
@@ -500,6 +500,13 @@ export type CSVLoaderOptions = LoaderOptions & {
500
500
  columns?: string[];
501
501
  /** Output format */
502
502
  outputFormat?: "text" | "json" | "markdown";
503
+ /**
504
+ * Rewrite headers into valid identifiers (#378). Opt-in; default false keeps
505
+ * the raw header strings as JSON keys / table columns.
506
+ */
507
+ sanitizeColumnNames?: boolean;
508
+ /** Case style used when `sanitizeColumnNames` is on (#378). Default "snake_case". */
509
+ columnNameCase?: "camelCase" | "snake_case";
503
510
  };
504
511
  /**
505
512
  * Abstract document loader type
@@ -14,7 +14,7 @@ import type { AIModelProviderConfig, NeurolinkCredentials } from "./providers.js
14
14
  import type { TTSChunk, TTSOptions, TTSResult } from "./tts.js";
15
15
  import type { STTOptions, STTResult } from "./stt.js";
16
16
  import type { StandardRecord, ValidationSchema } from "./aliases.js";
17
- import type { FileWithMetadata } from "./file.js";
17
+ import type { CSVProcessorOptions, FileWithMetadata } from "./file.js";
18
18
  import type { WorkflowConfig } from "./workflow.js";
19
19
  import type { LanguageModel, StepResult } from "./providers.js";
20
20
  import type { Tool, ToolChoice } from "./tools.js";
@@ -205,11 +205,7 @@ export type StreamOptions = {
205
205
  enableProgress?: boolean;
206
206
  };
207
207
  };
208
- csvOptions?: {
209
- maxRows?: number;
210
- formatStyle?: "raw" | "markdown" | "json";
211
- includeHeaders?: boolean;
212
- };
208
+ csvOptions?: CSVProcessorOptions;
213
209
  videoOptions?: {
214
210
  frames?: number;
215
211
  quality?: number;
@@ -3,7 +3,33 @@
3
3
  * Converts CSV files to LLM-friendly text formats
4
4
  * Uses streaming for memory efficiency with large files
5
5
  */
6
- import type { FileProcessingResult, CSVProcessorOptions } from "../types/index.js";
6
+ import type { FileProcessingResult, CSVProcessorOptions, CSVRow } from "../types/index.js";
7
+ /**
8
+ * Rewrite a raw header into a valid identifier. Trims, replaces non-identifier
9
+ * runs with `_`, prefixes a `col_` when it starts with a digit or is empty,
10
+ * then applies the requested case style. Never returns an empty string.
11
+ */
12
+ export declare function sanitizeColumnName(name: string, style?: "camelCase" | "snake_case", index?: number): string;
13
+ /**
14
+ * Deduplicate a list of (already sanitized) names by suffixing `_2`, `_3`, … on
15
+ * collisions, preserving order. The suffixed candidate is bumped until it does
16
+ * not collide with ANY already-emitted name — so an existing `name_2` can't be
17
+ * silently overwritten by a generated `name_2` (which the naive counter did for
18
+ * `["name","name","name_2"]`).
19
+ */
20
+ export declare function dedupeColumnNames(names: string[]): string[];
21
+ /**
22
+ * Predicate form of the row-shape invariant: a non-null, non-array object whose
23
+ * every value is a string or undefined. csv-parser's default output always
24
+ * satisfies this; the guard is defense-in-depth at the parse boundary.
25
+ */
26
+ export declare function isValidCsvRow(row: unknown): row is CSVRow;
27
+ /**
28
+ * Assertion form used inside the streaming `data` handlers. Throws a
29
+ * `[CSVProcessor]`-prefixed error (so existing `includes("CSV")` catches still
30
+ * match) when a row violates the invariant.
31
+ */
32
+ export declare function assertValidCsvRow(row: unknown, rowNumber: number): asserts row is CSVRow;
7
33
  /**
8
34
  * Detect if first line is CSV metadata (not actual data/headers)
9
35
  * Common patterns:
@@ -94,25 +120,57 @@ export declare class CSVProcessor {
94
120
  * @param maxRows - Maximum rows to parse (default: 1000)
95
121
  * @returns Array of row objects
96
122
  */
97
- static parseCSVFile(filePath: string, maxRows?: number): Promise<unknown[]>;
123
+ static parseCSVFile(filePath: string, maxRows?: number, timeoutMs?: number): Promise<CSVRow[]>;
124
+ /**
125
+ * File-parse variant that also reports whether the parse timed out, used by
126
+ * `process()` to surface `metadata.parseTimedOut`.
127
+ */
128
+ static parseCSVFileWithMeta(filePath: string, maxRows?: number, timeoutMs?: number, encoding?: string): Promise<{
129
+ rows: CSVRow[];
130
+ timedOut: boolean;
131
+ }>;
132
+ /**
133
+ * Peek the head of a raw file stream to resolve encoding, metadata-line
134
+ * skipping, and the delimiter (#361), then return a decoded text stream
135
+ * that still delivers the whole file from byte 0 (via `unshift`) — a
136
+ * single disk read (#368/#362).
137
+ */
138
+ private static prepareFileSource;
98
139
  /**
99
140
  * Parse CSV string to array of row objects
100
141
  * Exposed for use by tools that need direct CSV parsing
101
142
  *
102
143
  * @param csvString - CSV data as string
103
144
  * @param maxRows - Maximum rows to parse (default: 1000)
145
+ * @param timeoutMs - Wall-clock parse cap; returns partial rows on timeout
104
146
  * @returns Array of row objects
105
147
  */
106
- static parseCSVString(csvString: string, maxRows?: number, delimiter?: string): Promise<unknown[]>;
148
+ static parseCSVString(csvString: string, maxRows?: number, timeoutMs?: number): Promise<CSVRow[]>;
107
149
  /**
108
- * Stream-parse already-split, metadata-stripped data lines into row
109
- * objects with a known delimiter. Shared core for parseCSVString() and
110
- * process(), both of which need this after already splitting the content
111
- * and resolving the delimiter once — calling parseCSVString() directly
112
- * from process() would re-split and re-run metadata/delimiter detection
113
- * over the same content a second time.
150
+ * String-parse variant that also reports whether the parse timed out, used by
151
+ * `process()` to surface `metadata.parseTimedOut`.
152
+ */
153
+ static parseCSVStringWithMeta(csvString: string, maxRows?: number, timeoutMs?: number): Promise<{
154
+ rows: CSVRow[];
155
+ timedOut: boolean;
156
+ }>;
157
+ /**
158
+ * Shared streaming consumer for both parse methods. Guarantees:
159
+ * - source, rawSource, and parser are all destroyed on ANY settle path
160
+ * (#371) — `rawSource` matters for the file-parse path, where `source`
161
+ * is a decode Transform piped FROM `rawSource`; `.pipe()` does not
162
+ * propagate `.destroy()` upstream, so without this the underlying
163
+ * `fs.createReadStream` fd would leak on abort/timeout/error,
164
+ * - a wall-clock timeout returns partial rows instead of hanging (#379),
165
+ * - every row is validated at the boundary (#384),
166
+ * - reject messages carry columns/last-row-shape/format context (#375),
167
+ * - the delimiter (#361, comma/tab/semicolon/pipe) detected by the caller
168
+ * is honored, instead of csv-parser's hard-coded comma default.
169
+ *
170
+ * @param rawSource - The original file stream `source` was piped from, when
171
+ * applicable (file-parse path only; `undefined` for string parsing).
114
172
  */
115
- private static parseCSVLines;
173
+ private static streamParse;
116
174
  /**
117
175
  * Format parsed CSV data for LLM consumption
118
176
  * Only used for JSON and Markdown formats (raw format handled separately)