@juspay/neurolink 12.6.1 → 12.7.1
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 +2 -3
- package/dist/browser/neurolink.min.js +381 -381
- package/dist/core/evaluationProviders.d.ts +1 -1
- package/dist/core/evaluationProviders.js +1 -1
- package/dist/proxy/clientAttribution.d.ts +43 -0
- package/dist/proxy/clientAttribution.js +40 -14
- package/dist/types/common.d.ts +87 -0
- package/dist/types/voice.d.ts +13 -0
- package/dist/utils/ttsProcessor.d.ts +130 -5
- package/dist/utils/ttsProcessor.js +642 -97
- package/dist/utils/ttsStream.js +4 -0
- package/dist/voice/providers/OpenAITTS.d.ts +26 -8
- package/dist/voice/providers/OpenAITTS.js +327 -85
- package/package.json +1 -1
|
@@ -33,7 +33,7 @@ export declare function isProviderAvailable(providerName: string): boolean;
|
|
|
33
33
|
*/
|
|
34
34
|
export declare function getBestAvailableProvider(preferCheap?: boolean): ProviderModelConfig | null;
|
|
35
35
|
/**
|
|
36
|
-
Record actual provider performance for optimization
|
|
36
|
+
* Record actual provider performance for optimization
|
|
37
37
|
*/
|
|
38
38
|
export declare function recordProviderPerformanceFromMetrics(providerName: string, metrics: {
|
|
39
39
|
responseTime: number;
|
|
@@ -111,7 +111,7 @@ export function getBestAvailableProvider(preferCheap = true) {
|
|
|
111
111
|
return sortedProviders[0];
|
|
112
112
|
}
|
|
113
113
|
/**
|
|
114
|
-
Record actual provider performance for optimization
|
|
114
|
+
* Record actual provider performance for optimization
|
|
115
115
|
*/
|
|
116
116
|
export function recordProviderPerformanceFromMetrics(providerName, metrics) {
|
|
117
117
|
const existing = providerMetrics.get(providerName) || {
|
|
@@ -16,6 +16,49 @@
|
|
|
16
16
|
* client is then still attributable by its own User-Agent rather than
|
|
17
17
|
* collapsing into one bucket with every other unknown.
|
|
18
18
|
*/
|
|
19
|
+
/**
|
|
20
|
+
* Deliberately NOT mapped, and why each was ruled out.
|
|
21
|
+
*
|
|
22
|
+
* These two strings are unrelated to each other. They are grouped only because
|
|
23
|
+
* both were candidates for a Copilot mapping at some point, and neither can
|
|
24
|
+
* carry one.
|
|
25
|
+
*
|
|
26
|
+
* - `OpenAI/JS 5.20.1` — Copilot CLI's actual User-Agent, and the problem is
|
|
27
|
+
* that it is not Copilot's alone: it is the stock OpenAI JS SDK string, sent
|
|
28
|
+
* by every caller of that SDK. Mapping it would file unrelated OpenAI-SDK
|
|
29
|
+
* traffic under Copilot's name, which is worse than leaving it unattributed.
|
|
30
|
+
* Copilot also sends `x-initiator` and `x-interaction-type`, but neither is
|
|
31
|
+
* exclusive to it either, so it stays `unknown` and remains traceable
|
|
32
|
+
* through the stored raw header.
|
|
33
|
+
*
|
|
34
|
+
* - `Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)` — not a
|
|
35
|
+
* CLI's User-Agent at all, and in particular NOT Copilot's. It is what
|
|
36
|
+
* `curl` sends on a machine whose `~/.curlrc` sets `user-agent`, so every
|
|
37
|
+
* curl-driven caller on such a host shares it: scripts, agents, health
|
|
38
|
+
* probes. It accounted for the largest single block of `unknown` rows in the
|
|
39
|
+
* log this table was measured against, which is exactly what made it
|
|
40
|
+
* tempting.
|
|
41
|
+
*
|
|
42
|
+
* Recorded because the first pass got this wrong in a way worth naming. The
|
|
43
|
+
* string was seen arriving at two capture servers during a Copilot CLI run
|
|
44
|
+
* and a Gemini CLI run, and was written up as "sent by both CLIs". It was
|
|
45
|
+
* sent by neither: it was the aliveness `curl` fired at each capture server
|
|
46
|
+
* moments before the CLI was pointed at it, picking up that host's curlrc.
|
|
47
|
+
* The request paths gave it away on review — the Gemini-side hit was a bare
|
|
48
|
+
* `GET /v1beta/models`, which is the probe's URL and not one the CLI
|
|
49
|
+
* requests. An identical unusual string arriving from two unrelated clients
|
|
50
|
+
* is evidence of shared tooling, never a property of either client.
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* The client names this table can produce.
|
|
54
|
+
*
|
|
55
|
+
* Exported so a test can check the roster of CLIs the proxy configures against
|
|
56
|
+
* the roster it can actually name. Those two lists drifted apart silently once
|
|
57
|
+
* already: five configurators shipped while the table still knew only Claude
|
|
58
|
+
* Code, and nothing failed, because an unattributed client looks exactly like
|
|
59
|
+
* a quiet one.
|
|
60
|
+
*/
|
|
61
|
+
export declare function getMappedClientNames(): ReadonlySet<string>;
|
|
19
62
|
/**
|
|
20
63
|
* Derive a stable client name, or "unknown" when the header is absent or
|
|
21
64
|
* unrecognised. Never throws: attribution must not be able to fail a request.
|
|
@@ -41,24 +41,50 @@ const CLIENT_PREFIXES = [
|
|
|
41
41
|
["codex_exec/", "codex"],
|
|
42
42
|
];
|
|
43
43
|
/**
|
|
44
|
-
* Deliberately NOT mapped,
|
|
44
|
+
* Deliberately NOT mapped, and why each was ruled out.
|
|
45
45
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
46
|
+
* These two strings are unrelated to each other. They are grouped only because
|
|
47
|
+
* both were candidates for a Copilot mapping at some point, and neither can
|
|
48
|
+
* carry one.
|
|
48
49
|
*
|
|
49
|
-
* - `OpenAI/JS 5.20.1` —
|
|
50
|
-
* that
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* `unknown` rows in this machine's log, which is what made it tempting.)
|
|
50
|
+
* - `OpenAI/JS 5.20.1` — Copilot CLI's actual User-Agent, and the problem is
|
|
51
|
+
* that it is not Copilot's alone: it is the stock OpenAI JS SDK string, sent
|
|
52
|
+
* by every caller of that SDK. Mapping it would file unrelated OpenAI-SDK
|
|
53
|
+
* traffic under Copilot's name, which is worse than leaving it unattributed.
|
|
54
|
+
* Copilot also sends `x-initiator` and `x-interaction-type`, but neither is
|
|
55
|
+
* exclusive to it either, so it stays `unknown` and remains traceable
|
|
56
|
+
* through the stored raw header.
|
|
57
57
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
58
|
+
* - `Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)` — not a
|
|
59
|
+
* CLI's User-Agent at all, and in particular NOT Copilot's. It is what
|
|
60
|
+
* `curl` sends on a machine whose `~/.curlrc` sets `user-agent`, so every
|
|
61
|
+
* curl-driven caller on such a host shares it: scripts, agents, health
|
|
62
|
+
* probes. It accounted for the largest single block of `unknown` rows in the
|
|
63
|
+
* log this table was measured against, which is exactly what made it
|
|
64
|
+
* tempting.
|
|
65
|
+
*
|
|
66
|
+
* Recorded because the first pass got this wrong in a way worth naming. The
|
|
67
|
+
* string was seen arriving at two capture servers during a Copilot CLI run
|
|
68
|
+
* and a Gemini CLI run, and was written up as "sent by both CLIs". It was
|
|
69
|
+
* sent by neither: it was the aliveness `curl` fired at each capture server
|
|
70
|
+
* moments before the CLI was pointed at it, picking up that host's curlrc.
|
|
71
|
+
* The request paths gave it away on review — the Gemini-side hit was a bare
|
|
72
|
+
* `GET /v1beta/models`, which is the probe's URL and not one the CLI
|
|
73
|
+
* requests. An identical unusual string arriving from two unrelated clients
|
|
74
|
+
* is evidence of shared tooling, never a property of either client.
|
|
61
75
|
*/
|
|
76
|
+
/**
|
|
77
|
+
* The client names this table can produce.
|
|
78
|
+
*
|
|
79
|
+
* Exported so a test can check the roster of CLIs the proxy configures against
|
|
80
|
+
* the roster it can actually name. Those two lists drifted apart silently once
|
|
81
|
+
* already: five configurators shipped while the table still knew only Claude
|
|
82
|
+
* Code, and nothing failed, because an unattributed client looks exactly like
|
|
83
|
+
* a quiet one.
|
|
84
|
+
*/
|
|
85
|
+
export function getMappedClientNames() {
|
|
86
|
+
return new Set(CLIENT_PREFIXES.map(([, name]) => name));
|
|
87
|
+
}
|
|
62
88
|
/** Cap stored User-Agents. They are attacker-influenced and unbounded. */
|
|
63
89
|
const MAX_USER_AGENT_CHARS = 200;
|
|
64
90
|
/**
|
package/dist/types/common.d.ts
CHANGED
|
@@ -352,6 +352,93 @@ export type TTSHandler = {
|
|
|
352
352
|
* @throws {TTSError} On synthesis failure, timeout, or configuration issues
|
|
353
353
|
*/
|
|
354
354
|
synthesize(text: string, options: TTSOptions): Promise<TTSResult>;
|
|
355
|
+
/**
|
|
356
|
+
* Stream provider-native audio for one pre-validated text segment.
|
|
357
|
+
*
|
|
358
|
+
* Return `undefined` when the requested options cannot be delivered
|
|
359
|
+
* incrementally. The processor then uses `synthesize()` and preserves the
|
|
360
|
+
* buffered fallback for handlers and formats without native support.
|
|
361
|
+
* Provider-local indexes, cumulative sizes, and finality are normalized by
|
|
362
|
+
* the processor before chunks reach the public stream — an implementation
|
|
363
|
+
* may leave `isFinal` false on every chunk rather than hold a fragment back
|
|
364
|
+
* to label the last one, and the processor discards reported finality
|
|
365
|
+
* either way.
|
|
366
|
+
*
|
|
367
|
+
* Yield `TTSChunk` fragments. The member is declared `unknown` — not a
|
|
368
|
+
* method signature — on purpose, and that is a deliberate trade.
|
|
369
|
+
*
|
|
370
|
+
* This is an OPTIONAL member added to a public structural type that
|
|
371
|
+
* consumers already implement. Any type narrower than `unknown` rejects some
|
|
372
|
+
* existing handler that already carries a member of this name, which is a
|
|
373
|
+
* source break under Critical Rule 5 whatever that other shape happens to
|
|
374
|
+
* be. That is not hypothetical: a member returning a sync `Generator`, an
|
|
375
|
+
* `async` method returning a `Promise` of an async iterable, a
|
|
376
|
+
* callback-style member returning `void` or `Promise<void>`, and a plain
|
|
377
|
+
* boolean capability flag all compile against `origin/release` today, and
|
|
378
|
+
* every one of them is rejected by a declared method signature — including
|
|
379
|
+
* an intentionally wide one such as `(...args: never[]) => unknown`, which
|
|
380
|
+
* still cannot accept the boolean. Only `unknown` accepts them all.
|
|
381
|
+
*
|
|
382
|
+
* The cost is that this member cannot contextually type an implementation's
|
|
383
|
+
* parameters. Authors annotate their own signature instead — OpenAI TTS
|
|
384
|
+
* declares `synthesizeStream(text: string, options: TTSOptions):
|
|
385
|
+
* AsyncIterable<TTSChunk> | undefined` on the class — which keeps full
|
|
386
|
+
* compiler checking of what that implementation yields. Nothing is checked
|
|
387
|
+
* at this member; usability is decided at runtime by the processor.
|
|
388
|
+
*
|
|
389
|
+
* The processor validates each fragment at runtime instead. A fragment is
|
|
390
|
+
* audio only when it carries a non-empty `data` `Buffer` (or `Uint8Array`);
|
|
391
|
+
* its `format` is honoured only when it names a real `TTSAudioFormat`, and
|
|
392
|
+
* the requested format is used otherwise. A fragment that fails that test —
|
|
393
|
+
* including an empty (`data.length === 0`) read — is skipped and never
|
|
394
|
+
* reaches the consumer. A native stream that completes without yielding a
|
|
395
|
+
* single deliverable fragment is treated as "no incremental delivery after
|
|
396
|
+
* all" and falls back to `synthesize()` for that segment.
|
|
397
|
+
*
|
|
398
|
+
* That filtering is specific to this native path. The buffered path
|
|
399
|
+
* forwards whatever `synthesize()` returns, a zero-byte buffer included, so
|
|
400
|
+
* a consumer of the public stream can still observe an empty chunk when a
|
|
401
|
+
* handler produces one.
|
|
402
|
+
*
|
|
403
|
+
* `undefined` is the only capability signal. Everything the processor does
|
|
404
|
+
* to decide whether this capability exists is asked BEFORE the segment's
|
|
405
|
+
* work starts, and every way that question can fail is a handler bug that
|
|
406
|
+
* the processor answers the same way: it serves that segment from
|
|
407
|
+
* `synthesize()` rather than losing it (the throwing modes also log a
|
|
408
|
+
* warning; a member that is merely not callable falls back silently). None
|
|
409
|
+
* of them should be used as a deliberate fallback mechanism. The modes it
|
|
410
|
+
* anticipates cover each read as well as each call, because reading a
|
|
411
|
+
* property can run a getter or a `Proxy` trap that throws just as a call
|
|
412
|
+
* can:
|
|
413
|
+
*
|
|
414
|
+
* - reading `synthesizeStream` off the handler throws;
|
|
415
|
+
* - the member is present but not callable;
|
|
416
|
+
* - reading `isConfigured` off the handler throws, or calling it throws —
|
|
417
|
+
* the segment falls back to the buffered path, where `synthesize()` asks
|
|
418
|
+
* again and a throw there fails the segment shaped, exactly as it would
|
|
419
|
+
* for a handler with no native member (a handler that merely reports
|
|
420
|
+
* itself unconfigured is not a bug: that segment fails with
|
|
421
|
+
* `TTS_PROVIDER_NOT_CONFIGURED`, as it always has);
|
|
422
|
+
* - calling the member throws;
|
|
423
|
+
* - the returned value's async-iterability cannot be established, including
|
|
424
|
+
* a value whose `Symbol.asyncIterator` property cannot even be read.
|
|
425
|
+
*
|
|
426
|
+
* An error raised once the segment's own work has started, by contrast,
|
|
427
|
+
* fails that segment like any other synthesis failure — it is not re-served
|
|
428
|
+
* from the buffered path. Iteration begins at the `[Symbol.asyncIterator]()`
|
|
429
|
+
* call, so that call throwing, that call handing back something that is not
|
|
430
|
+
* an iterator, and a first read that rejects are all segment failures rather
|
|
431
|
+
* than fallbacks.
|
|
432
|
+
*
|
|
433
|
+
* Implementations MUST enforce their own timeout and cancel any active
|
|
434
|
+
* transport when the returned iterable is closed.
|
|
435
|
+
*
|
|
436
|
+
* The value the processor looks for is a callable of the shape
|
|
437
|
+
* `(text: string, options: TTSOptions) => AsyncIterable<TTSChunk> | undefined`,
|
|
438
|
+
* invoked with the handler as `this`. `text` is one buffered text segment
|
|
439
|
+
* within the provider's length limit.
|
|
440
|
+
*/
|
|
441
|
+
synthesizeStream?: unknown;
|
|
355
442
|
/**
|
|
356
443
|
* Get available voices for the provider
|
|
357
444
|
*
|
package/dist/types/voice.d.ts
CHANGED
|
@@ -134,6 +134,14 @@ export type VoiceTurn = {
|
|
|
134
134
|
};
|
|
135
135
|
/**
|
|
136
136
|
* TTS-capable voice provider type
|
|
137
|
+
*
|
|
138
|
+
* @deprecated Use the canonical `TTSHandler` contract instead. Nothing in
|
|
139
|
+
* this package consumes `TTSProvider`; it is kept at its original shape so
|
|
140
|
+
* existing external callers keep compiling. `TTSHandler` is not a drop-in
|
|
141
|
+
* replacement — it requires `isConfigured()`, makes `getVoices` and
|
|
142
|
+
* `maxTextLength` optional, and its `synthesizeStream` may return `undefined`
|
|
143
|
+
* to select the buffered path — so this is a distinct legacy shape, not an
|
|
144
|
+
* alias.
|
|
137
145
|
*/
|
|
138
146
|
export type TTSProvider = {
|
|
139
147
|
/**
|
|
@@ -155,6 +163,11 @@ export type TTSProvider = {
|
|
|
155
163
|
};
|
|
156
164
|
/**
|
|
157
165
|
* TTS stream chunk for streaming synthesis
|
|
166
|
+
*
|
|
167
|
+
* @deprecated Use the canonical `TTSChunk` type instead. Kept at its
|
|
168
|
+
* original shape so existing external callers keep compiling: `TTSChunk`
|
|
169
|
+
* narrows `format` to `TTSAudioFormat` and has no `timestampMs`, so it
|
|
170
|
+
* is not a drop-in replacement.
|
|
158
171
|
*/
|
|
159
172
|
export type TTSStreamChunk = {
|
|
160
173
|
/** Audio data chunk */
|
|
@@ -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
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
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
|
}
|