@juspay/neurolink 11.25.3 → 11.26.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 +3 -3
- package/dist/browser/neurolink.min.js +395 -395
- package/dist/core/baseProvider.d.ts +55 -4
- package/dist/core/baseProvider.js +216 -59
- package/dist/localUsage/claudeCodeReader.js +2 -6
- package/dist/localUsage/codexReader.js +2 -6
- package/dist/localUsage/openCodeReader.js +4 -6
- package/dist/localUsage/scanWindow.d.ts +29 -0
- package/dist/localUsage/scanWindow.js +42 -0
- package/dist/neurolink.d.ts +7 -20
- package/dist/neurolink.js +99 -85
- package/dist/providers/anthropic/client.js +22 -1
- package/dist/types/stream.d.ts +44 -6
- package/dist/types/tts.d.ts +9 -0
- package/dist/types/tts.js +6 -0
- package/dist/utils/ttsProcessor.d.ts +20 -1
- package/dist/utils/ttsProcessor.js +167 -0
- package/dist/utils/ttsStream.d.ts +15 -0
- package/dist/utils/ttsStream.js +225 -0
- package/package.json +1 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { NeuroLinkError } from "./errorHandling.js";
|
|
2
|
+
import { sanitizeErrorCause } from "./logSanitize.js";
|
|
3
|
+
import { logger } from "./logger.js";
|
|
4
|
+
import { IncrementalTTSSynthesisError, TTS_ERROR_CODES, TTSProcessor, } from "./ttsProcessor.js";
|
|
5
|
+
import { TimeoutError as AsyncTimeoutError } from "./async/withTimeout.js";
|
|
6
|
+
function getStreamingTTSErrorDetails(error) {
|
|
7
|
+
const incrementalFailure = error instanceof IncrementalTTSSynthesisError ? error : undefined;
|
|
8
|
+
const cause = incrementalFailure?.firstError ?? error;
|
|
9
|
+
const safeMessage = sanitizeErrorCause(cause).message;
|
|
10
|
+
let detail;
|
|
11
|
+
if (cause instanceof AsyncTimeoutError) {
|
|
12
|
+
detail = {
|
|
13
|
+
code: TTS_ERROR_CODES.SYNTHESIS_FAILED,
|
|
14
|
+
message: safeMessage,
|
|
15
|
+
retriable: true,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
else if (cause instanceof NeuroLinkError) {
|
|
19
|
+
detail = {
|
|
20
|
+
code: cause.code,
|
|
21
|
+
message: safeMessage,
|
|
22
|
+
retriable: cause.retriable,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
detail = {
|
|
27
|
+
code: TTS_ERROR_CODES.SYNTHESIS_FAILED,
|
|
28
|
+
message: safeMessage,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const failedSegments = incrementalFailure?.failedSegments;
|
|
32
|
+
const count = failedSegments?.length ?? 1;
|
|
33
|
+
const positions = failedSegments?.join(", ") ?? "unknown";
|
|
34
|
+
return {
|
|
35
|
+
...detail,
|
|
36
|
+
message: `Incremental TTS failed for ${count} segment${count === 1 ? "" : "s"} (segment${count === 1 ? "" : "s"} ${positions}): ${detail.message}`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
class AsyncTextQueue {
|
|
40
|
+
values = [];
|
|
41
|
+
waiters = [];
|
|
42
|
+
ended = false;
|
|
43
|
+
failure;
|
|
44
|
+
[Symbol.asyncIterator]() {
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
next() {
|
|
48
|
+
if (this.values.length > 0) {
|
|
49
|
+
const value = this.values.shift();
|
|
50
|
+
if (value !== undefined) {
|
|
51
|
+
return Promise.resolve({ value, done: false });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (this.failure !== undefined) {
|
|
55
|
+
return Promise.reject(this.failure);
|
|
56
|
+
}
|
|
57
|
+
if (this.ended) {
|
|
58
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
59
|
+
}
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
this.waiters.push({ resolve, reject });
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
push(value) {
|
|
65
|
+
if (this.ended || this.failure !== undefined) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const waiter = this.waiters.shift();
|
|
69
|
+
if (waiter) {
|
|
70
|
+
waiter.resolve({ value, done: false });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this.values.push(value);
|
|
74
|
+
}
|
|
75
|
+
end() {
|
|
76
|
+
if (this.ended || this.failure !== undefined) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
this.ended = true;
|
|
80
|
+
for (const waiter of this.waiters.splice(0)) {
|
|
81
|
+
waiter.resolve({ value: undefined, done: true });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
fail(error) {
|
|
85
|
+
if (this.ended || this.failure !== undefined) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.failure = error;
|
|
89
|
+
for (const waiter of this.waiters.splice(0)) {
|
|
90
|
+
waiter.reject(error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function sourceEvent(iterator) {
|
|
95
|
+
return iterator.next().then((result) => ({ kind: "source", result }), (error) => ({ kind: "source-error", error }));
|
|
96
|
+
}
|
|
97
|
+
function audioEvent(iterator) {
|
|
98
|
+
return iterator.next().then((result) => ({ kind: "audio", result }), (error) => ({ kind: "audio-error", error }));
|
|
99
|
+
}
|
|
100
|
+
function textFromChunk(chunk) {
|
|
101
|
+
if (chunk &&
|
|
102
|
+
typeof chunk === "object" &&
|
|
103
|
+
"content" in chunk &&
|
|
104
|
+
typeof chunk.content === "string" &&
|
|
105
|
+
chunk.content.length > 0) {
|
|
106
|
+
return chunk.content;
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
function aggregateTTSChunks(chunks) {
|
|
111
|
+
const last = chunks.at(-1);
|
|
112
|
+
if (!last) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
const buffer = Buffer.concat(chunks.map((chunk) => chunk.data));
|
|
116
|
+
return {
|
|
117
|
+
buffer,
|
|
118
|
+
format: last.format,
|
|
119
|
+
size: buffer.length,
|
|
120
|
+
duration: last.estimatedDuration,
|
|
121
|
+
voice: last.voice,
|
|
122
|
+
sampleRate: last.sampleRate,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Preserve source-stream backpressure while interleaving incremental TTS audio.
|
|
127
|
+
* Source chunks are yielded before their derived audio, and TTS failures degrade
|
|
128
|
+
* to the unchanged source stream.
|
|
129
|
+
*/
|
|
130
|
+
export async function* interleaveTTSStream(params) {
|
|
131
|
+
const { stream, provider, options, onComplete } = params;
|
|
132
|
+
if (!TTSProcessor.supports(provider)) {
|
|
133
|
+
try {
|
|
134
|
+
for await (const chunk of stream) {
|
|
135
|
+
yield chunk;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
onComplete?.(undefined);
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const textQueue = new AsyncTextQueue();
|
|
144
|
+
const sourceIterator = stream[Symbol.asyncIterator]();
|
|
145
|
+
let cancelled = false;
|
|
146
|
+
const audioIterator = TTSProcessor.synthesizeStream(textQueue, provider, options, () => cancelled)[Symbol.asyncIterator]();
|
|
147
|
+
const audioChunks = [];
|
|
148
|
+
let nextSource = sourceEvent(sourceIterator);
|
|
149
|
+
let nextAudio = audioEvent(audioIterator);
|
|
150
|
+
let completed = false;
|
|
151
|
+
let preferAudio = false;
|
|
152
|
+
try {
|
|
153
|
+
while (nextSource || nextAudio) {
|
|
154
|
+
const event = nextSource && nextAudio
|
|
155
|
+
? await Promise.race(preferAudio ? [nextAudio, nextSource] : [nextSource, nextAudio])
|
|
156
|
+
: nextSource
|
|
157
|
+
? await nextSource
|
|
158
|
+
: nextAudio
|
|
159
|
+
? await nextAudio
|
|
160
|
+
: undefined;
|
|
161
|
+
if (!event) {
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
if (event.kind === "source-error") {
|
|
165
|
+
textQueue.fail(event.error);
|
|
166
|
+
throw event.error;
|
|
167
|
+
}
|
|
168
|
+
if (event.kind === "audio-error") {
|
|
169
|
+
textQueue.end();
|
|
170
|
+
const error = getStreamingTTSErrorDetails(event.error);
|
|
171
|
+
logger.warn(`[TTSProcessor] Incremental stream disabled after an audio error: ${event.error instanceof Error
|
|
172
|
+
? event.error.message
|
|
173
|
+
: String(event.error)}`);
|
|
174
|
+
nextAudio = undefined;
|
|
175
|
+
preferAudio = false;
|
|
176
|
+
completed = true;
|
|
177
|
+
onComplete?.(aggregateTTSChunks(audioChunks), error);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (event.kind === "source") {
|
|
181
|
+
if (event.result.done) {
|
|
182
|
+
nextSource = undefined;
|
|
183
|
+
textQueue.end();
|
|
184
|
+
preferAudio = true;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const text = textFromChunk(event.result.value);
|
|
188
|
+
if (text) {
|
|
189
|
+
textQueue.push(text);
|
|
190
|
+
}
|
|
191
|
+
nextSource = sourceEvent(sourceIterator);
|
|
192
|
+
preferAudio = true;
|
|
193
|
+
yield event.result.value;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (event.result.done) {
|
|
197
|
+
nextAudio = undefined;
|
|
198
|
+
completed = true;
|
|
199
|
+
onComplete?.(aggregateTTSChunks(audioChunks));
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
audioChunks.push(event.result.value);
|
|
203
|
+
nextAudio = audioEvent(audioIterator);
|
|
204
|
+
preferAudio = false;
|
|
205
|
+
yield { type: "tts_audio", audio: event.result.value };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
if (!completed) {
|
|
210
|
+
cancelled = true;
|
|
211
|
+
}
|
|
212
|
+
textQueue.end();
|
|
213
|
+
const releases = [];
|
|
214
|
+
if (nextSource && sourceIterator.return) {
|
|
215
|
+
releases.push(Promise.resolve().then(() => sourceIterator.return?.(undefined)));
|
|
216
|
+
}
|
|
217
|
+
if (nextAudio && audioIterator.return) {
|
|
218
|
+
releases.push(Promise.resolve().then(() => audioIterator.return?.(undefined)));
|
|
219
|
+
}
|
|
220
|
+
await Promise.allSettled(releases);
|
|
221
|
+
if (!completed) {
|
|
222
|
+
onComplete?.(undefined);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.26.0",
|
|
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": {
|