@juspay/neurolink 12.6.0 → 12.7.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.
@@ -162,17 +162,142 @@ export declare class TTSProcessor {
162
162
  * ```
163
163
  */
164
164
  static synthesize(text: string, provider: string, options: TTSOptions): Promise<TTSResult>;
165
+ /**
166
+ * Normalize a provider failure into the public `TTSError` shape.
167
+ *
168
+ * Extracted from `synthesize()` so the native streaming path can shape its
169
+ * transport errors identically. A raw provider error reaching the public
170
+ * surface unwrapped loses `retriable` (and the provider-qualified message),
171
+ * which is exactly what a caller keying retry logic off
172
+ * `ttsMetadata.error.retriable` reads.
173
+ */
174
+ private static toSynthesisError;
175
+ /**
176
+ * Open the `tts.synthesize` span that `synthesize()` opens, so a segment
177
+ * served by a handler's native stream emits the same telemetry a buffered
178
+ * segment does.
179
+ */
180
+ private static startSynthesisSpan;
181
+ private static finishSynthesisSpan;
182
+ /**
183
+ * `TTSHandler.synthesizeStream` is declared `unknown`, so a handler may
184
+ * carry anything at all under that name — including a legacy member of an
185
+ * unrelated shape. The native capability is discovered structurally instead:
186
+ * the member must be callable, and what it returns must be async-iterable.
187
+ *
188
+ * Discovery reads consumer-controlled properties, and a property read can
189
+ * itself execute user code (a Proxy trap, a throwing getter) — the member on
190
+ * the handler and the well-known symbol on what it returns alike. Every one
191
+ * of those reads therefore happens inside a try: `resolveNativeStream`
192
+ * guards the whole preflight in one region, and `cancelStream`/
193
+ * `releaseIterator` in `streamCancellation.ts` guard the symbol read they
194
+ * each perform on the unwind path.
195
+ */
196
+ private static isCallableMember;
197
+ private static isNativeStream;
198
+ /**
199
+ * Coerce one fragment yielded by a handler's native stream into the fields
200
+ * this processor forwards, or `undefined` when the fragment is not audio.
201
+ *
202
+ * `TTSHandler.synthesizeStream` is declared `unknown` because ANY narrower
203
+ * type rejects some existing consumer that already carries a member of that
204
+ * name — a Critical Rule 5 break. The type therefore checks nothing at all,
205
+ * and every check happens at runtime instead. Here that means: a
206
+ * fragment must carry a non-empty binary payload, and a reported `format` is
207
+ * honoured only when it names a real audio format, falling back to the
208
+ * requested one. The caller skips an `undefined` result exactly as it skips
209
+ * a zero-length transport read.
210
+ */
211
+ private static normalizeNativeChunk;
212
+ /**
213
+ * Ask a handler for a native stream for one segment, or `undefined` to serve
214
+ * the segment from `synthesize()`.
215
+ *
216
+ * `undefined` is the documented "not incrementally deliverable" signal. A
217
+ * handler that throws instead, or hands back something that is not
218
+ * async-iterable, is buggy — but that is a reason to serve the segment from
219
+ * the buffered path, not to lose it.
220
+ *
221
+ * **Every failure this method anticipates answers `undefined` instead of
222
+ * throwing, and the caller's catch backstops what no code that must
223
+ * describe a hostile thrown value can rule out.** Everything it does is a question ABOUT the
224
+ * handler, asked before any of the segment's work has begun, and every one
225
+ * of those questions can run consumer code: reading `synthesizeStream` or
226
+ * `isConfigured` can hit an accessor or a `Proxy` trap, calling
227
+ * `isConfigured()` and calling the member run handler code outright, and
228
+ * reading `Symbol.asyncIterator` off whatever comes back can hit a getter or
229
+ * a trap of its own. A throw from ANY of them means the same thing — the
230
+ * capability could not be established — so they all sit inside the single
231
+ * guarded region below and every failure answers `undefined`. The segment
232
+ * then goes to `synthesize()`, which re-runs the identical preflight inside
233
+ * its own `tts.synthesize` span and normalizes whatever it raises, exactly
234
+ * as it did before native streaming existed. Reporting a preflight failure
235
+ * from here instead would open a second span for one segment, or drop a
236
+ * segment the buffered path can still serve.
237
+ *
238
+ * Two ordering rules make that equivalence exact:
239
+ *
240
+ * - The member is read FIRST and exactly ONCE per segment. An accessor-
241
+ * defined member runs consumer code on every read, so a second read can
242
+ * answer differently from the one that was tested — and reading it before
243
+ * `isConfigured()` keeps a handler that does not offer the capability at
244
+ * all on precisely the call sequence it had before this method existed.
245
+ * - `isConfigured()` runs only once a callable member has been found, which
246
+ * is the only case where this method is about to invoke the handler.
247
+ *
248
+ * The one measured departure from `origin/release` is a call count, not an
249
+ * outcome: a segment that starts down the native path and falls back calls
250
+ * the handler's `isConfigured()` twice — here, and again inside
251
+ * `synthesize()`. That is inherent to attempting native delivery at all and
252
+ * predates this method's current shape; `isConfigured()` is specified as a
253
+ * configuration predicate, and for any implementation that behaves as one —
254
+ * a pure predicate — every observable of such a segment (chunks, spans,
255
+ * error code and `retriable`) is identical either way. A handler whose
256
+ * answer CHANGES between the two calls changes the outcome with it: the
257
+ * second answer, taken on the path that actually synthesizes, decides.
258
+ */
259
+ private static resolveNativeStream;
165
260
  /**
166
261
  * Incrementally synthesize sentence-buffered text chunks.
167
262
  *
168
263
  * Text is flushed at a sentence boundary after `streamingBufferSize`
169
264
  * characters, or hard-split before the provider's maximum text length.
170
- * Each segment goes through `synthesize()`, preserving the existing handler
171
- * registry, validation, error normalization, and telemetry seam.
172
265
  *
173
- * The most recent successful audio chunk is held until another succeeds or
174
- * the input ends, so exactly one real audio chunk carries `isFinal: true`
175
- * without emitting a separate empty terminator chunk.
266
+ * A segment is served by the handler's `synthesizeStream()` when it offers
267
+ * one and returns a stream, and by `synthesize()` otherwise including
268
+ * when the native stream produces no deliverable audio at all, and including
269
+ * every way capability discovery itself can fail. The preflight reads and
270
+ * calls that decide whether a native stream exists all sit inside one
271
+ * guarded region in `resolveNativeStream`, with the call site's own catch
272
+ * backstopping it, so a handler that misbehaves while being ASKED lands on
273
+ * the buffered path rather than costing the segment. That is what makes the
274
+ * next sentence true of every segment rather than only of the ones that got
275
+ * that far.
276
+ *
277
+ * Either way the segment keeps the same handler registry, validation, error
278
+ * normalization and `tts.synthesize` telemetry seam — exactly one span per
279
+ * segment, opened by whichever path served it — cancellation included: a
280
+ * segment whose stream is still in flight when the consumer stops records
281
+ * its span from the unwind path rather than dropping it. Failures that
282
+ * originate in the native segment's own work, once a stream has been
283
+ * established, are a different case and keep the shaped failed-segment
284
+ * semantics every synthesis failure has.
285
+ *
286
+ * Provider-reported indexes, cumulative sizes and finality are discarded and
287
+ * recomputed globally. A native fragment is dropped unless it carries a
288
+ * non-empty binary payload, so no native read reaches the consumer as an
289
+ * empty chunk; the buffered path is unfiltered and forwards whatever
290
+ * `synthesize()` returns, so a handler that produces a zero-byte buffer
291
+ * still yields an empty chunk and a repeated `cumulativeSize`. The most
292
+ * recent successful audio chunk is held until another succeeds or the input
293
+ * ends, so exactly one real audio chunk carries `isFinal: true` without
294
+ * emitting a separate empty terminator chunk.
295
+ *
296
+ * Segment production and per-segment synthesis are deliberately inline
297
+ * rather than nested async generators: each additional generator layer costs
298
+ * every chunk several microtask turns, which is directly observable at
299
+ * `NeuroLink.stream()` as audio interleaving one text chunk later than it
300
+ * does without native streaming.
176
301
  */
177
302
  static synthesizeStream(textChunks: AsyncIterable<string>, provider: string, options: TTSOptions, shouldStop?: () => boolean): AsyncGenerator<TTSChunk>;
178
303
  }