@juspay/neurolink 8.16.0 → 8.18.0
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 +12 -0
- package/dist/lib/types/generateTypes.d.ts +63 -0
- package/dist/lib/types/streamTypes.d.ts +95 -0
- package/dist/lib/types/ttsTypes.d.ts +24 -0
- package/dist/lib/utils/csvProcessor.js +55 -1
- package/dist/types/generateTypes.d.ts +63 -0
- package/dist/types/streamTypes.d.ts +95 -0
- package/dist/types/ttsTypes.d.ts +24 -0
- package/dist/utils/csvProcessor.js +55 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [8.18.0](https://github.com/juspay/neurolink/compare/v8.17.0...v8.18.0) (2025-12-16)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- **(utils):** standardize logging levels in CSVProcessor ([1c348b2](https://github.com/juspay/neurolink/commit/1c348b28d1212cd8ec33eb0100acddaa5a3df2bd))
|
|
6
|
+
|
|
7
|
+
## [8.17.0](https://github.com/juspay/neurolink/compare/v8.16.0...v8.17.0) (2025-12-16)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **(tts):** Add TTS type integration to GenerateOptions, GenerateResult, and StreamChunk ([e290330](https://github.com/juspay/neurolink/commit/e290330e8fe22a4cd0427185cbddbb8856fbd5ca))
|
|
12
|
+
|
|
1
13
|
## [8.16.0](https://github.com/juspay/neurolink/compare/v8.15.0...v8.16.0) (2025-12-16)
|
|
2
14
|
|
|
3
15
|
### Features
|
|
@@ -7,6 +7,7 @@ import type { ChatMessage, ConversationMemoryConfig } from "./conversation.js";
|
|
|
7
7
|
import type { MiddlewareFactoryOptions } from "./middlewareTypes.js";
|
|
8
8
|
import type { JsonValue } from "./common.js";
|
|
9
9
|
import type { Content, ImageWithAltText } from "./content.js";
|
|
10
|
+
import type { TTSOptions, TTSResult } from "./ttsTypes.js";
|
|
10
11
|
/**
|
|
11
12
|
* Generate function options type - Primary method for content generation
|
|
12
13
|
* Supports multimodal content while maintaining backward compatibility
|
|
@@ -52,6 +53,39 @@ export type GenerateOptions = {
|
|
|
52
53
|
format?: "jpeg" | "png";
|
|
53
54
|
transcribeAudio?: boolean;
|
|
54
55
|
};
|
|
56
|
+
/**
|
|
57
|
+
* Text-to-Speech (TTS) configuration
|
|
58
|
+
*
|
|
59
|
+
* Enable audio generation from the text response. The generated audio will be
|
|
60
|
+
* returned in the result's `audio` field as a TTSResult object.
|
|
61
|
+
*
|
|
62
|
+
* @example Basic TTS
|
|
63
|
+
* ```typescript
|
|
64
|
+
* const result = await neurolink.generate({
|
|
65
|
+
* input: { text: "Tell me a story" },
|
|
66
|
+
* provider: "google-ai",
|
|
67
|
+
* tts: { enabled: true, voice: "en-US-Neural2-C" }
|
|
68
|
+
* });
|
|
69
|
+
* console.log(result.audio?.buffer); // Audio Buffer
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @example Advanced TTS with options
|
|
73
|
+
* ```typescript
|
|
74
|
+
* const result = await neurolink.generate({
|
|
75
|
+
* input: { text: "Speak slowly and clearly" },
|
|
76
|
+
* provider: "google-ai",
|
|
77
|
+
* tts: {
|
|
78
|
+
* enabled: true,
|
|
79
|
+
* voice: "en-US-Neural2-D",
|
|
80
|
+
* speed: 0.8,
|
|
81
|
+
* pitch: 2.0,
|
|
82
|
+
* format: "mp3",
|
|
83
|
+
* quality: "standard"
|
|
84
|
+
* }
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
tts?: TTSOptions;
|
|
55
89
|
provider?: AIProviderName | string;
|
|
56
90
|
model?: string;
|
|
57
91
|
region?: string;
|
|
@@ -144,6 +178,35 @@ export type GenerateResult = {
|
|
|
144
178
|
outputs?: {
|
|
145
179
|
text: string;
|
|
146
180
|
};
|
|
181
|
+
/**
|
|
182
|
+
* Text-to-Speech audio result
|
|
183
|
+
*
|
|
184
|
+
* Contains the generated audio buffer and metadata when TTS is enabled.
|
|
185
|
+
* Generated by TTSProcessor.synthesize() using the specified provider.
|
|
186
|
+
*
|
|
187
|
+
* @example Accessing TTS audio
|
|
188
|
+
* ```typescript
|
|
189
|
+
* const result = await neurolink.generate({
|
|
190
|
+
* input: { text: "Hello world" },
|
|
191
|
+
* provider: "google-ai",
|
|
192
|
+
* tts: { enabled: true, voice: "en-US-Neural2-C" }
|
|
193
|
+
* });
|
|
194
|
+
*
|
|
195
|
+
* if (result.audio) {
|
|
196
|
+
* console.log(`Audio size: ${result.audio.size} bytes`);
|
|
197
|
+
* console.log(`Format: ${result.audio.format}`);
|
|
198
|
+
* if (result.audio.duration) {
|
|
199
|
+
* console.log(`Duration: ${result.audio.duration}s`);
|
|
200
|
+
* }
|
|
201
|
+
* if (result.audio.voice) {
|
|
202
|
+
* console.log(`Voice: ${result.audio.voice}`);
|
|
203
|
+
* }
|
|
204
|
+
* // Save or play the audio buffer
|
|
205
|
+
* fs.writeFileSync('output.mp3', result.audio.buffer);
|
|
206
|
+
* }
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
audio?: TTSResult;
|
|
147
210
|
provider?: string;
|
|
148
211
|
model?: string;
|
|
149
212
|
usage?: TokenUsage;
|
|
@@ -9,6 +9,7 @@ import type { EvaluationData } from "../index.js";
|
|
|
9
9
|
import type { UnknownRecord, JsonValue } from "./common.js";
|
|
10
10
|
import type { MiddlewareFactoryOptions } from "../types/middlewareTypes.js";
|
|
11
11
|
import type { ChatMessage } from "./conversation.js";
|
|
12
|
+
import type { TTSOptions, TTSChunk } from "./ttsTypes.js";
|
|
12
13
|
/**
|
|
13
14
|
* Progress tracking and metadata for streaming operations
|
|
14
15
|
*/
|
|
@@ -121,6 +122,60 @@ export type AudioChunk = {
|
|
|
121
122
|
channels: number;
|
|
122
123
|
encoding: PCMEncoding;
|
|
123
124
|
};
|
|
125
|
+
/**
|
|
126
|
+
* Stream chunk type using discriminated union for type safety
|
|
127
|
+
*
|
|
128
|
+
* Used in streaming responses to deliver either text or TTS audio chunks.
|
|
129
|
+
* The discriminated union ensures type safety - only one variant can exist at a time.
|
|
130
|
+
*
|
|
131
|
+
* @example Processing text chunks
|
|
132
|
+
* ```typescript
|
|
133
|
+
* for await (const chunk of result.stream) {
|
|
134
|
+
* if (chunk.type === "text") {
|
|
135
|
+
* console.log(chunk.content); // TypeScript knows 'content' exists
|
|
136
|
+
* }
|
|
137
|
+
* }
|
|
138
|
+
* ```
|
|
139
|
+
*
|
|
140
|
+
* @example Processing audio chunks
|
|
141
|
+
* ```typescript
|
|
142
|
+
* const audioBuffer: Buffer[] = [];
|
|
143
|
+
* for await (const chunk of result.stream) {
|
|
144
|
+
* if (chunk.type === "audio") {
|
|
145
|
+
* audioBuffer.push(chunk.audioChunk.data); // TypeScript knows 'audioChunk' exists
|
|
146
|
+
* if (chunk.audioChunk.isFinal) {
|
|
147
|
+
* const fullAudio = Buffer.concat(audioBuffer);
|
|
148
|
+
* fs.writeFileSync('output.mp3', fullAudio);
|
|
149
|
+
* }
|
|
150
|
+
* }
|
|
151
|
+
* }
|
|
152
|
+
* ```
|
|
153
|
+
*
|
|
154
|
+
* @example Processing both text and audio
|
|
155
|
+
* ```typescript
|
|
156
|
+
* for await (const chunk of result.stream) {
|
|
157
|
+
* switch (chunk.type) {
|
|
158
|
+
* case "text":
|
|
159
|
+
* process.stdout.write(chunk.content);
|
|
160
|
+
* break;
|
|
161
|
+
* case "audio":
|
|
162
|
+
* playAudioChunk(chunk.audioChunk.data);
|
|
163
|
+
* break;
|
|
164
|
+
* }
|
|
165
|
+
* }
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
export type StreamChunk = {
|
|
169
|
+
/** Discriminator for text chunks */
|
|
170
|
+
type: "text";
|
|
171
|
+
/** Text content chunk */
|
|
172
|
+
content: string;
|
|
173
|
+
} | {
|
|
174
|
+
/** Discriminator for audio chunks */
|
|
175
|
+
type: "audio";
|
|
176
|
+
/** TTS audio chunk data */
|
|
177
|
+
audioChunk: TTSChunk;
|
|
178
|
+
};
|
|
124
179
|
export type StreamOptions = {
|
|
125
180
|
input: {
|
|
126
181
|
text: string;
|
|
@@ -168,6 +223,46 @@ export type StreamOptions = {
|
|
|
168
223
|
format?: "jpeg" | "png";
|
|
169
224
|
transcribeAudio?: boolean;
|
|
170
225
|
};
|
|
226
|
+
/**
|
|
227
|
+
* Text-to-Speech (TTS) configuration for streaming
|
|
228
|
+
*
|
|
229
|
+
* Enable audio generation from the streamed text response. Audio chunks will be
|
|
230
|
+
* delivered through the stream alongside text chunks as TTSChunk objects.
|
|
231
|
+
*
|
|
232
|
+
* @example Basic streaming TTS
|
|
233
|
+
* ```typescript
|
|
234
|
+
* const result = await neurolink.stream({
|
|
235
|
+
* input: { text: "Tell me a story" },
|
|
236
|
+
* provider: "google-ai",
|
|
237
|
+
* tts: { enabled: true, voice: "en-US-Neural2-C" }
|
|
238
|
+
* });
|
|
239
|
+
*
|
|
240
|
+
* for await (const chunk of result.stream) {
|
|
241
|
+
* if (chunk.type === "text") {
|
|
242
|
+
* process.stdout.write(chunk.content);
|
|
243
|
+
* } else if (chunk.type === "audio") {
|
|
244
|
+
* // Handle audio chunk
|
|
245
|
+
* playAudioChunk(chunk.audioChunk.data);
|
|
246
|
+
* }
|
|
247
|
+
* }
|
|
248
|
+
* ```
|
|
249
|
+
*
|
|
250
|
+
* @example Advanced streaming TTS with audio buffer
|
|
251
|
+
* ```typescript
|
|
252
|
+
* const result = await neurolink.stream({
|
|
253
|
+
* input: { text: "Speak slowly" },
|
|
254
|
+
* provider: "google-ai",
|
|
255
|
+
* tts: {
|
|
256
|
+
* enabled: true,
|
|
257
|
+
* voice: "en-US-Neural2-D",
|
|
258
|
+
* speed: 0.8,
|
|
259
|
+
* format: "mp3",
|
|
260
|
+
* quality: "hd"
|
|
261
|
+
* }
|
|
262
|
+
* });
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
tts?: TTSOptions;
|
|
171
266
|
provider?: AIProviderName | string;
|
|
172
267
|
model?: string;
|
|
173
268
|
region?: string;
|
|
@@ -114,3 +114,27 @@ export declare function isTTSResult(value: unknown): value is TTSResult;
|
|
|
114
114
|
* Type guard to check if TTSOptions are valid
|
|
115
115
|
*/
|
|
116
116
|
export declare function isValidTTSOptions(options: unknown): options is TTSOptions;
|
|
117
|
+
/**
|
|
118
|
+
* TTS audio chunk for streaming Text-to-Speech output
|
|
119
|
+
*
|
|
120
|
+
* Represents a chunk of audio data generated during streaming TTS.
|
|
121
|
+
* Used in StreamChunk type to deliver audio alongside text content.
|
|
122
|
+
*/
|
|
123
|
+
export type TTSChunk = {
|
|
124
|
+
/** Audio data chunk as Buffer */
|
|
125
|
+
data: Buffer;
|
|
126
|
+
/** Audio format of this chunk */
|
|
127
|
+
format: AudioFormat;
|
|
128
|
+
/** Chunk sequence number (0-indexed) */
|
|
129
|
+
index: number;
|
|
130
|
+
/** Whether this is the final audio chunk */
|
|
131
|
+
isFinal: boolean;
|
|
132
|
+
/** Cumulative audio size in bytes so far */
|
|
133
|
+
cumulativeSize?: number;
|
|
134
|
+
/** Estimated total duration in seconds (if available) */
|
|
135
|
+
estimatedDuration?: number;
|
|
136
|
+
/** Voice used for generation */
|
|
137
|
+
voice?: string;
|
|
138
|
+
/** Sample rate in Hz */
|
|
139
|
+
sampleRate?: number;
|
|
140
|
+
};
|
|
@@ -64,12 +64,21 @@ export class CSVProcessor {
|
|
|
64
64
|
static async process(content, options) {
|
|
65
65
|
const { maxRows: rawMaxRows = 1000, formatStyle = "raw", includeHeaders = true, sampleDataFormat = "json", } = options || {};
|
|
66
66
|
const maxRows = Math.max(1, Math.min(10000, rawMaxRows));
|
|
67
|
+
logger.debug("[CSVProcessor] Starting CSV processing", {
|
|
68
|
+
contentSize: content.length,
|
|
69
|
+
formatStyle,
|
|
70
|
+
maxRows,
|
|
71
|
+
includeHeaders,
|
|
72
|
+
});
|
|
67
73
|
const csvString = content.toString("utf-8");
|
|
68
74
|
// For raw format, return original CSV with row limit (no parsing needed)
|
|
69
75
|
// This preserves the exact original format which works best for LLMs
|
|
70
76
|
if (formatStyle === "raw") {
|
|
71
77
|
const lines = csvString.split("\n");
|
|
72
78
|
const hasMetadataLine = isMetadataLine(lines);
|
|
79
|
+
if (hasMetadataLine) {
|
|
80
|
+
logger.debug("[CSVProcessor] Detected metadata line, skipping first line");
|
|
81
|
+
}
|
|
73
82
|
// Skip metadata line if present, then take header + maxRows data rows
|
|
74
83
|
const csvLines = hasMetadataLine
|
|
75
84
|
? lines.slice(1) // Skip metadata line
|
|
@@ -78,11 +87,21 @@ export class CSVProcessor {
|
|
|
78
87
|
const limitedCSV = limitedLines.join("\n");
|
|
79
88
|
const rowCount = limitedLines.length - 1; // Subtract header
|
|
80
89
|
const originalRowCount = csvLines.length - 1; // Subtract header from original
|
|
90
|
+
const wasTruncated = rowCount < originalRowCount;
|
|
91
|
+
if (wasTruncated) {
|
|
92
|
+
logger.warn(`[CSVProcessor] CSV data truncated: showing ${rowCount} of ${originalRowCount} rows (limit: ${maxRows})`);
|
|
93
|
+
}
|
|
81
94
|
logger.debug(`[CSVProcessor] raw format: ${rowCount} rows (original: ${originalRowCount}) → ${limitedCSV.length} chars`, {
|
|
82
95
|
formatStyle: "raw",
|
|
83
96
|
originalSize: csvString.length,
|
|
84
97
|
limitedSize: limitedCSV.length,
|
|
85
98
|
});
|
|
99
|
+
logger.info("[CSVProcessor] ✅ Processed CSV file", {
|
|
100
|
+
formatStyle: "raw",
|
|
101
|
+
rowCount,
|
|
102
|
+
columnCount: (limitedLines[0] || "").split(",").length,
|
|
103
|
+
truncated: wasTruncated,
|
|
104
|
+
});
|
|
86
105
|
return {
|
|
87
106
|
type: "csv",
|
|
88
107
|
content: limitedCSV,
|
|
@@ -96,6 +115,10 @@ export class CSVProcessor {
|
|
|
96
115
|
};
|
|
97
116
|
}
|
|
98
117
|
// Parse CSV for JSON and Markdown formats only
|
|
118
|
+
logger.debug("[CSVProcessor] Parsing CSV for structured format conversion", {
|
|
119
|
+
formatStyle,
|
|
120
|
+
maxRows,
|
|
121
|
+
});
|
|
99
122
|
const rows = await this.parseCSVString(csvString, maxRows);
|
|
100
123
|
// Extract metadata from parsed results
|
|
101
124
|
const rowCount = rows.length;
|
|
@@ -104,9 +127,24 @@ export class CSVProcessor {
|
|
|
104
127
|
const hasEmptyColumns = columnNames.some((col) => !col || col.trim() === "");
|
|
105
128
|
const sampleRows = rows.slice(0, 3);
|
|
106
129
|
const sampleData = this.formatSampleData(sampleRows, sampleDataFormat, includeHeaders);
|
|
130
|
+
if (hasEmptyColumns) {
|
|
131
|
+
logger.warn("[CSVProcessor] CSV contains empty or blank column headers", {
|
|
132
|
+
columnNames,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (rowCount === 0) {
|
|
136
|
+
logger.warn("[CSVProcessor] CSV file contains no data rows");
|
|
137
|
+
}
|
|
107
138
|
// Format parsed data
|
|
139
|
+
logger.debug(`[CSVProcessor] Converting ${rowCount} rows to ${formatStyle} format`);
|
|
108
140
|
const formatted = this.formatForLLM(rows, formatStyle, includeHeaders);
|
|
109
|
-
logger.info(
|
|
141
|
+
logger.info("[CSVProcessor] ✅ Processed CSV file", {
|
|
142
|
+
formatStyle,
|
|
143
|
+
rowCount,
|
|
144
|
+
columnCount,
|
|
145
|
+
outputLength: formatted.length,
|
|
146
|
+
hasEmptyColumns,
|
|
147
|
+
});
|
|
110
148
|
return {
|
|
111
149
|
type: "csv",
|
|
112
150
|
content: formatted,
|
|
@@ -136,6 +174,10 @@ export class CSVProcessor {
|
|
|
136
174
|
static async parseCSVFile(filePath, maxRows = 1000) {
|
|
137
175
|
const clampedMaxRows = Math.max(1, Math.min(10000, maxRows));
|
|
138
176
|
const fs = await import("fs");
|
|
177
|
+
logger.debug("[CSVProcessor] Starting file parsing", {
|
|
178
|
+
filePath,
|
|
179
|
+
maxRows: clampedMaxRows,
|
|
180
|
+
});
|
|
139
181
|
// Read first 2 lines to detect metadata
|
|
140
182
|
const fileHandle = await fs.promises.open(filePath, "r");
|
|
141
183
|
const firstLines = [];
|
|
@@ -156,6 +198,9 @@ export class CSVProcessor {
|
|
|
156
198
|
await fileHandle.close();
|
|
157
199
|
const hasMetadataLine = isMetadataLine(firstLines);
|
|
158
200
|
const skipLines = hasMetadataLine ? 1 : 0;
|
|
201
|
+
if (hasMetadataLine) {
|
|
202
|
+
logger.debug("[CSVProcessor] Detected metadata line in file, will skip first line");
|
|
203
|
+
}
|
|
159
204
|
return new Promise((resolve, reject) => {
|
|
160
205
|
const rows = [];
|
|
161
206
|
let count = 0;
|
|
@@ -182,6 +227,7 @@ export class CSVProcessor {
|
|
|
182
227
|
}
|
|
183
228
|
})
|
|
184
229
|
.on("end", () => {
|
|
230
|
+
logger.debug(`[CSVProcessor] File parsing complete: ${rows.length} rows parsed`);
|
|
185
231
|
resolve(rows);
|
|
186
232
|
})
|
|
187
233
|
.on("error", (error) => {
|
|
@@ -200,10 +246,17 @@ export class CSVProcessor {
|
|
|
200
246
|
*/
|
|
201
247
|
static async parseCSVString(csvString, maxRows = 1000) {
|
|
202
248
|
const clampedMaxRows = Math.max(1, Math.min(10000, maxRows));
|
|
249
|
+
logger.debug("[CSVProcessor] Starting string parsing", {
|
|
250
|
+
inputLength: csvString.length,
|
|
251
|
+
maxRows: clampedMaxRows,
|
|
252
|
+
});
|
|
203
253
|
// Detect and skip metadata line
|
|
204
254
|
const lines = csvString.split("\n");
|
|
205
255
|
const hasMetadataLine = isMetadataLine(lines);
|
|
206
256
|
const csvData = hasMetadataLine ? lines.slice(1).join("\n") : csvString;
|
|
257
|
+
if (hasMetadataLine) {
|
|
258
|
+
logger.debug("[CSVProcessor] Detected metadata line in string, skipping");
|
|
259
|
+
}
|
|
207
260
|
return new Promise((resolve, reject) => {
|
|
208
261
|
const rows = [];
|
|
209
262
|
let count = 0;
|
|
@@ -225,6 +278,7 @@ export class CSVProcessor {
|
|
|
225
278
|
}
|
|
226
279
|
})
|
|
227
280
|
.on("end", () => {
|
|
281
|
+
logger.debug(`[CSVProcessor] String parsing complete: ${rows.length} rows parsed`);
|
|
228
282
|
resolve(rows);
|
|
229
283
|
})
|
|
230
284
|
.on("error", (error) => {
|
|
@@ -7,6 +7,7 @@ import type { ChatMessage, ConversationMemoryConfig } from "./conversation.js";
|
|
|
7
7
|
import type { MiddlewareFactoryOptions } from "./middlewareTypes.js";
|
|
8
8
|
import type { JsonValue } from "./common.js";
|
|
9
9
|
import type { Content, ImageWithAltText } from "./content.js";
|
|
10
|
+
import type { TTSOptions, TTSResult } from "./ttsTypes.js";
|
|
10
11
|
/**
|
|
11
12
|
* Generate function options type - Primary method for content generation
|
|
12
13
|
* Supports multimodal content while maintaining backward compatibility
|
|
@@ -52,6 +53,39 @@ export type GenerateOptions = {
|
|
|
52
53
|
format?: "jpeg" | "png";
|
|
53
54
|
transcribeAudio?: boolean;
|
|
54
55
|
};
|
|
56
|
+
/**
|
|
57
|
+
* Text-to-Speech (TTS) configuration
|
|
58
|
+
*
|
|
59
|
+
* Enable audio generation from the text response. The generated audio will be
|
|
60
|
+
* returned in the result's `audio` field as a TTSResult object.
|
|
61
|
+
*
|
|
62
|
+
* @example Basic TTS
|
|
63
|
+
* ```typescript
|
|
64
|
+
* const result = await neurolink.generate({
|
|
65
|
+
* input: { text: "Tell me a story" },
|
|
66
|
+
* provider: "google-ai",
|
|
67
|
+
* tts: { enabled: true, voice: "en-US-Neural2-C" }
|
|
68
|
+
* });
|
|
69
|
+
* console.log(result.audio?.buffer); // Audio Buffer
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @example Advanced TTS with options
|
|
73
|
+
* ```typescript
|
|
74
|
+
* const result = await neurolink.generate({
|
|
75
|
+
* input: { text: "Speak slowly and clearly" },
|
|
76
|
+
* provider: "google-ai",
|
|
77
|
+
* tts: {
|
|
78
|
+
* enabled: true,
|
|
79
|
+
* voice: "en-US-Neural2-D",
|
|
80
|
+
* speed: 0.8,
|
|
81
|
+
* pitch: 2.0,
|
|
82
|
+
* format: "mp3",
|
|
83
|
+
* quality: "standard"
|
|
84
|
+
* }
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
tts?: TTSOptions;
|
|
55
89
|
provider?: AIProviderName | string;
|
|
56
90
|
model?: string;
|
|
57
91
|
region?: string;
|
|
@@ -144,6 +178,35 @@ export type GenerateResult = {
|
|
|
144
178
|
outputs?: {
|
|
145
179
|
text: string;
|
|
146
180
|
};
|
|
181
|
+
/**
|
|
182
|
+
* Text-to-Speech audio result
|
|
183
|
+
*
|
|
184
|
+
* Contains the generated audio buffer and metadata when TTS is enabled.
|
|
185
|
+
* Generated by TTSProcessor.synthesize() using the specified provider.
|
|
186
|
+
*
|
|
187
|
+
* @example Accessing TTS audio
|
|
188
|
+
* ```typescript
|
|
189
|
+
* const result = await neurolink.generate({
|
|
190
|
+
* input: { text: "Hello world" },
|
|
191
|
+
* provider: "google-ai",
|
|
192
|
+
* tts: { enabled: true, voice: "en-US-Neural2-C" }
|
|
193
|
+
* });
|
|
194
|
+
*
|
|
195
|
+
* if (result.audio) {
|
|
196
|
+
* console.log(`Audio size: ${result.audio.size} bytes`);
|
|
197
|
+
* console.log(`Format: ${result.audio.format}`);
|
|
198
|
+
* if (result.audio.duration) {
|
|
199
|
+
* console.log(`Duration: ${result.audio.duration}s`);
|
|
200
|
+
* }
|
|
201
|
+
* if (result.audio.voice) {
|
|
202
|
+
* console.log(`Voice: ${result.audio.voice}`);
|
|
203
|
+
* }
|
|
204
|
+
* // Save or play the audio buffer
|
|
205
|
+
* fs.writeFileSync('output.mp3', result.audio.buffer);
|
|
206
|
+
* }
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
audio?: TTSResult;
|
|
147
210
|
provider?: string;
|
|
148
211
|
model?: string;
|
|
149
212
|
usage?: TokenUsage;
|
|
@@ -9,6 +9,7 @@ import type { EvaluationData } from "../index.js";
|
|
|
9
9
|
import type { UnknownRecord, JsonValue } from "./common.js";
|
|
10
10
|
import type { MiddlewareFactoryOptions } from "../types/middlewareTypes.js";
|
|
11
11
|
import type { ChatMessage } from "./conversation.js";
|
|
12
|
+
import type { TTSOptions, TTSChunk } from "./ttsTypes.js";
|
|
12
13
|
/**
|
|
13
14
|
* Progress tracking and metadata for streaming operations
|
|
14
15
|
*/
|
|
@@ -121,6 +122,60 @@ export type AudioChunk = {
|
|
|
121
122
|
channels: number;
|
|
122
123
|
encoding: PCMEncoding;
|
|
123
124
|
};
|
|
125
|
+
/**
|
|
126
|
+
* Stream chunk type using discriminated union for type safety
|
|
127
|
+
*
|
|
128
|
+
* Used in streaming responses to deliver either text or TTS audio chunks.
|
|
129
|
+
* The discriminated union ensures type safety - only one variant can exist at a time.
|
|
130
|
+
*
|
|
131
|
+
* @example Processing text chunks
|
|
132
|
+
* ```typescript
|
|
133
|
+
* for await (const chunk of result.stream) {
|
|
134
|
+
* if (chunk.type === "text") {
|
|
135
|
+
* console.log(chunk.content); // TypeScript knows 'content' exists
|
|
136
|
+
* }
|
|
137
|
+
* }
|
|
138
|
+
* ```
|
|
139
|
+
*
|
|
140
|
+
* @example Processing audio chunks
|
|
141
|
+
* ```typescript
|
|
142
|
+
* const audioBuffer: Buffer[] = [];
|
|
143
|
+
* for await (const chunk of result.stream) {
|
|
144
|
+
* if (chunk.type === "audio") {
|
|
145
|
+
* audioBuffer.push(chunk.audioChunk.data); // TypeScript knows 'audioChunk' exists
|
|
146
|
+
* if (chunk.audioChunk.isFinal) {
|
|
147
|
+
* const fullAudio = Buffer.concat(audioBuffer);
|
|
148
|
+
* fs.writeFileSync('output.mp3', fullAudio);
|
|
149
|
+
* }
|
|
150
|
+
* }
|
|
151
|
+
* }
|
|
152
|
+
* ```
|
|
153
|
+
*
|
|
154
|
+
* @example Processing both text and audio
|
|
155
|
+
* ```typescript
|
|
156
|
+
* for await (const chunk of result.stream) {
|
|
157
|
+
* switch (chunk.type) {
|
|
158
|
+
* case "text":
|
|
159
|
+
* process.stdout.write(chunk.content);
|
|
160
|
+
* break;
|
|
161
|
+
* case "audio":
|
|
162
|
+
* playAudioChunk(chunk.audioChunk.data);
|
|
163
|
+
* break;
|
|
164
|
+
* }
|
|
165
|
+
* }
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
export type StreamChunk = {
|
|
169
|
+
/** Discriminator for text chunks */
|
|
170
|
+
type: "text";
|
|
171
|
+
/** Text content chunk */
|
|
172
|
+
content: string;
|
|
173
|
+
} | {
|
|
174
|
+
/** Discriminator for audio chunks */
|
|
175
|
+
type: "audio";
|
|
176
|
+
/** TTS audio chunk data */
|
|
177
|
+
audioChunk: TTSChunk;
|
|
178
|
+
};
|
|
124
179
|
export type StreamOptions = {
|
|
125
180
|
input: {
|
|
126
181
|
text: string;
|
|
@@ -168,6 +223,46 @@ export type StreamOptions = {
|
|
|
168
223
|
format?: "jpeg" | "png";
|
|
169
224
|
transcribeAudio?: boolean;
|
|
170
225
|
};
|
|
226
|
+
/**
|
|
227
|
+
* Text-to-Speech (TTS) configuration for streaming
|
|
228
|
+
*
|
|
229
|
+
* Enable audio generation from the streamed text response. Audio chunks will be
|
|
230
|
+
* delivered through the stream alongside text chunks as TTSChunk objects.
|
|
231
|
+
*
|
|
232
|
+
* @example Basic streaming TTS
|
|
233
|
+
* ```typescript
|
|
234
|
+
* const result = await neurolink.stream({
|
|
235
|
+
* input: { text: "Tell me a story" },
|
|
236
|
+
* provider: "google-ai",
|
|
237
|
+
* tts: { enabled: true, voice: "en-US-Neural2-C" }
|
|
238
|
+
* });
|
|
239
|
+
*
|
|
240
|
+
* for await (const chunk of result.stream) {
|
|
241
|
+
* if (chunk.type === "text") {
|
|
242
|
+
* process.stdout.write(chunk.content);
|
|
243
|
+
* } else if (chunk.type === "audio") {
|
|
244
|
+
* // Handle audio chunk
|
|
245
|
+
* playAudioChunk(chunk.audioChunk.data);
|
|
246
|
+
* }
|
|
247
|
+
* }
|
|
248
|
+
* ```
|
|
249
|
+
*
|
|
250
|
+
* @example Advanced streaming TTS with audio buffer
|
|
251
|
+
* ```typescript
|
|
252
|
+
* const result = await neurolink.stream({
|
|
253
|
+
* input: { text: "Speak slowly" },
|
|
254
|
+
* provider: "google-ai",
|
|
255
|
+
* tts: {
|
|
256
|
+
* enabled: true,
|
|
257
|
+
* voice: "en-US-Neural2-D",
|
|
258
|
+
* speed: 0.8,
|
|
259
|
+
* format: "mp3",
|
|
260
|
+
* quality: "hd"
|
|
261
|
+
* }
|
|
262
|
+
* });
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
tts?: TTSOptions;
|
|
171
266
|
provider?: AIProviderName | string;
|
|
172
267
|
model?: string;
|
|
173
268
|
region?: string;
|
package/dist/types/ttsTypes.d.ts
CHANGED
|
@@ -114,3 +114,27 @@ export declare function isTTSResult(value: unknown): value is TTSResult;
|
|
|
114
114
|
* Type guard to check if TTSOptions are valid
|
|
115
115
|
*/
|
|
116
116
|
export declare function isValidTTSOptions(options: unknown): options is TTSOptions;
|
|
117
|
+
/**
|
|
118
|
+
* TTS audio chunk for streaming Text-to-Speech output
|
|
119
|
+
*
|
|
120
|
+
* Represents a chunk of audio data generated during streaming TTS.
|
|
121
|
+
* Used in StreamChunk type to deliver audio alongside text content.
|
|
122
|
+
*/
|
|
123
|
+
export type TTSChunk = {
|
|
124
|
+
/** Audio data chunk as Buffer */
|
|
125
|
+
data: Buffer;
|
|
126
|
+
/** Audio format of this chunk */
|
|
127
|
+
format: AudioFormat;
|
|
128
|
+
/** Chunk sequence number (0-indexed) */
|
|
129
|
+
index: number;
|
|
130
|
+
/** Whether this is the final audio chunk */
|
|
131
|
+
isFinal: boolean;
|
|
132
|
+
/** Cumulative audio size in bytes so far */
|
|
133
|
+
cumulativeSize?: number;
|
|
134
|
+
/** Estimated total duration in seconds (if available) */
|
|
135
|
+
estimatedDuration?: number;
|
|
136
|
+
/** Voice used for generation */
|
|
137
|
+
voice?: string;
|
|
138
|
+
/** Sample rate in Hz */
|
|
139
|
+
sampleRate?: number;
|
|
140
|
+
};
|
|
@@ -64,12 +64,21 @@ export class CSVProcessor {
|
|
|
64
64
|
static async process(content, options) {
|
|
65
65
|
const { maxRows: rawMaxRows = 1000, formatStyle = "raw", includeHeaders = true, sampleDataFormat = "json", } = options || {};
|
|
66
66
|
const maxRows = Math.max(1, Math.min(10000, rawMaxRows));
|
|
67
|
+
logger.debug("[CSVProcessor] Starting CSV processing", {
|
|
68
|
+
contentSize: content.length,
|
|
69
|
+
formatStyle,
|
|
70
|
+
maxRows,
|
|
71
|
+
includeHeaders,
|
|
72
|
+
});
|
|
67
73
|
const csvString = content.toString("utf-8");
|
|
68
74
|
// For raw format, return original CSV with row limit (no parsing needed)
|
|
69
75
|
// This preserves the exact original format which works best for LLMs
|
|
70
76
|
if (formatStyle === "raw") {
|
|
71
77
|
const lines = csvString.split("\n");
|
|
72
78
|
const hasMetadataLine = isMetadataLine(lines);
|
|
79
|
+
if (hasMetadataLine) {
|
|
80
|
+
logger.debug("[CSVProcessor] Detected metadata line, skipping first line");
|
|
81
|
+
}
|
|
73
82
|
// Skip metadata line if present, then take header + maxRows data rows
|
|
74
83
|
const csvLines = hasMetadataLine
|
|
75
84
|
? lines.slice(1) // Skip metadata line
|
|
@@ -78,11 +87,21 @@ export class CSVProcessor {
|
|
|
78
87
|
const limitedCSV = limitedLines.join("\n");
|
|
79
88
|
const rowCount = limitedLines.length - 1; // Subtract header
|
|
80
89
|
const originalRowCount = csvLines.length - 1; // Subtract header from original
|
|
90
|
+
const wasTruncated = rowCount < originalRowCount;
|
|
91
|
+
if (wasTruncated) {
|
|
92
|
+
logger.warn(`[CSVProcessor] CSV data truncated: showing ${rowCount} of ${originalRowCount} rows (limit: ${maxRows})`);
|
|
93
|
+
}
|
|
81
94
|
logger.debug(`[CSVProcessor] raw format: ${rowCount} rows (original: ${originalRowCount}) → ${limitedCSV.length} chars`, {
|
|
82
95
|
formatStyle: "raw",
|
|
83
96
|
originalSize: csvString.length,
|
|
84
97
|
limitedSize: limitedCSV.length,
|
|
85
98
|
});
|
|
99
|
+
logger.info("[CSVProcessor] ✅ Processed CSV file", {
|
|
100
|
+
formatStyle: "raw",
|
|
101
|
+
rowCount,
|
|
102
|
+
columnCount: (limitedLines[0] || "").split(",").length,
|
|
103
|
+
truncated: wasTruncated,
|
|
104
|
+
});
|
|
86
105
|
return {
|
|
87
106
|
type: "csv",
|
|
88
107
|
content: limitedCSV,
|
|
@@ -96,6 +115,10 @@ export class CSVProcessor {
|
|
|
96
115
|
};
|
|
97
116
|
}
|
|
98
117
|
// Parse CSV for JSON and Markdown formats only
|
|
118
|
+
logger.debug("[CSVProcessor] Parsing CSV for structured format conversion", {
|
|
119
|
+
formatStyle,
|
|
120
|
+
maxRows,
|
|
121
|
+
});
|
|
99
122
|
const rows = await this.parseCSVString(csvString, maxRows);
|
|
100
123
|
// Extract metadata from parsed results
|
|
101
124
|
const rowCount = rows.length;
|
|
@@ -104,9 +127,24 @@ export class CSVProcessor {
|
|
|
104
127
|
const hasEmptyColumns = columnNames.some((col) => !col || col.trim() === "");
|
|
105
128
|
const sampleRows = rows.slice(0, 3);
|
|
106
129
|
const sampleData = this.formatSampleData(sampleRows, sampleDataFormat, includeHeaders);
|
|
130
|
+
if (hasEmptyColumns) {
|
|
131
|
+
logger.warn("[CSVProcessor] CSV contains empty or blank column headers", {
|
|
132
|
+
columnNames,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (rowCount === 0) {
|
|
136
|
+
logger.warn("[CSVProcessor] CSV file contains no data rows");
|
|
137
|
+
}
|
|
107
138
|
// Format parsed data
|
|
139
|
+
logger.debug(`[CSVProcessor] Converting ${rowCount} rows to ${formatStyle} format`);
|
|
108
140
|
const formatted = this.formatForLLM(rows, formatStyle, includeHeaders);
|
|
109
|
-
logger.info(
|
|
141
|
+
logger.info("[CSVProcessor] ✅ Processed CSV file", {
|
|
142
|
+
formatStyle,
|
|
143
|
+
rowCount,
|
|
144
|
+
columnCount,
|
|
145
|
+
outputLength: formatted.length,
|
|
146
|
+
hasEmptyColumns,
|
|
147
|
+
});
|
|
110
148
|
return {
|
|
111
149
|
type: "csv",
|
|
112
150
|
content: formatted,
|
|
@@ -136,6 +174,10 @@ export class CSVProcessor {
|
|
|
136
174
|
static async parseCSVFile(filePath, maxRows = 1000) {
|
|
137
175
|
const clampedMaxRows = Math.max(1, Math.min(10000, maxRows));
|
|
138
176
|
const fs = await import("fs");
|
|
177
|
+
logger.debug("[CSVProcessor] Starting file parsing", {
|
|
178
|
+
filePath,
|
|
179
|
+
maxRows: clampedMaxRows,
|
|
180
|
+
});
|
|
139
181
|
// Read first 2 lines to detect metadata
|
|
140
182
|
const fileHandle = await fs.promises.open(filePath, "r");
|
|
141
183
|
const firstLines = [];
|
|
@@ -156,6 +198,9 @@ export class CSVProcessor {
|
|
|
156
198
|
await fileHandle.close();
|
|
157
199
|
const hasMetadataLine = isMetadataLine(firstLines);
|
|
158
200
|
const skipLines = hasMetadataLine ? 1 : 0;
|
|
201
|
+
if (hasMetadataLine) {
|
|
202
|
+
logger.debug("[CSVProcessor] Detected metadata line in file, will skip first line");
|
|
203
|
+
}
|
|
159
204
|
return new Promise((resolve, reject) => {
|
|
160
205
|
const rows = [];
|
|
161
206
|
let count = 0;
|
|
@@ -182,6 +227,7 @@ export class CSVProcessor {
|
|
|
182
227
|
}
|
|
183
228
|
})
|
|
184
229
|
.on("end", () => {
|
|
230
|
+
logger.debug(`[CSVProcessor] File parsing complete: ${rows.length} rows parsed`);
|
|
185
231
|
resolve(rows);
|
|
186
232
|
})
|
|
187
233
|
.on("error", (error) => {
|
|
@@ -200,10 +246,17 @@ export class CSVProcessor {
|
|
|
200
246
|
*/
|
|
201
247
|
static async parseCSVString(csvString, maxRows = 1000) {
|
|
202
248
|
const clampedMaxRows = Math.max(1, Math.min(10000, maxRows));
|
|
249
|
+
logger.debug("[CSVProcessor] Starting string parsing", {
|
|
250
|
+
inputLength: csvString.length,
|
|
251
|
+
maxRows: clampedMaxRows,
|
|
252
|
+
});
|
|
203
253
|
// Detect and skip metadata line
|
|
204
254
|
const lines = csvString.split("\n");
|
|
205
255
|
const hasMetadataLine = isMetadataLine(lines);
|
|
206
256
|
const csvData = hasMetadataLine ? lines.slice(1).join("\n") : csvString;
|
|
257
|
+
if (hasMetadataLine) {
|
|
258
|
+
logger.debug("[CSVProcessor] Detected metadata line in string, skipping");
|
|
259
|
+
}
|
|
207
260
|
return new Promise((resolve, reject) => {
|
|
208
261
|
const rows = [];
|
|
209
262
|
let count = 0;
|
|
@@ -225,6 +278,7 @@ export class CSVProcessor {
|
|
|
225
278
|
}
|
|
226
279
|
})
|
|
227
280
|
.on("end", () => {
|
|
281
|
+
logger.debug(`[CSVProcessor] String parsing complete: ${rows.length} rows parsed`);
|
|
228
282
|
resolve(rows);
|
|
229
283
|
})
|
|
230
284
|
.on("error", (error) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.18.0",
|
|
4
4
|
"description": "Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and deploy AI applications with 9 major providers: OpenAI, Anthropic, Google AI, AWS Bedrock, Azure, Hugging Face, Ollama, and Mistral AI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Juspay Technologies",
|