@flamai/finesse 0.1.2 → 0.2.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 +11 -0
- package/README.md +47 -0
- package/dist/index.cjs +107 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +207 -127
- package/dist/index.d.ts +207 -127
- package/dist/index.js +106 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -234,6 +234,151 @@ declare class CloneTimeoutError extends FinesseError {
|
|
|
234
234
|
constructor(jobId: string, timeoutMs: number);
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
/** Response shapes. Field names match the API (snake_case); timestamps are Unix milliseconds.
|
|
238
|
+
* Media URLs (`original_audio_url`, `sample_audio_url`, `avatar_url`, `audio_url`) may be
|
|
239
|
+
* short-lived signed URLs: download them promptly, or fetch the object again for a fresh one. */
|
|
240
|
+
interface VoiceSettings {
|
|
241
|
+
stability?: number;
|
|
242
|
+
similarity?: number;
|
|
243
|
+
style?: number;
|
|
244
|
+
speed?: number;
|
|
245
|
+
pitch?: number;
|
|
246
|
+
volume?: number;
|
|
247
|
+
use_speaker_boost?: boolean;
|
|
248
|
+
emotion?: string;
|
|
249
|
+
engine_params?: Record<string, unknown>;
|
|
250
|
+
}
|
|
251
|
+
/** A voice. It can synthesize once `status === "ready"`. */
|
|
252
|
+
interface Voice {
|
|
253
|
+
id: string;
|
|
254
|
+
org_id: string;
|
|
255
|
+
name: string;
|
|
256
|
+
/** `draft`, `processing`, `ready`, `failed`, ... */
|
|
257
|
+
status: string;
|
|
258
|
+
clone_method: string;
|
|
259
|
+
/** `private` for your voices, `public` for platform catalog voices. */
|
|
260
|
+
visibility: string;
|
|
261
|
+
primary_language: string;
|
|
262
|
+
description?: string;
|
|
263
|
+
model?: string;
|
|
264
|
+
/** Human-friendly handle, also usable as `voice` on the OpenAI-compatible endpoint. */
|
|
265
|
+
alias?: string;
|
|
266
|
+
source?: string;
|
|
267
|
+
language?: string;
|
|
268
|
+
gender?: string;
|
|
269
|
+
/** Labels as a plain object (the API sends `[{key, value}]`; the SDK converts it). */
|
|
270
|
+
labels: Record<string, string>;
|
|
271
|
+
settings?: VoiceSettings;
|
|
272
|
+
external_ref?: string;
|
|
273
|
+
original_audio_url?: string;
|
|
274
|
+
sample_audio_url?: string;
|
|
275
|
+
avatar_url?: string;
|
|
276
|
+
base_voice_id?: string;
|
|
277
|
+
created_at?: number;
|
|
278
|
+
updated_at?: number;
|
|
279
|
+
last_used_at?: number;
|
|
280
|
+
expires_at?: number;
|
|
281
|
+
/** Prefix of the API key that created the voice; only on your own voices. */
|
|
282
|
+
created_via_key_prefix?: string;
|
|
283
|
+
[key: string]: unknown;
|
|
284
|
+
}
|
|
285
|
+
/** An asynchronous clone job. */
|
|
286
|
+
interface CloneJob {
|
|
287
|
+
id: string;
|
|
288
|
+
org_id: string;
|
|
289
|
+
/** `pending`, `running`, `succeeded`, `failed`, `cancelled` or `expired`. */
|
|
290
|
+
status: string;
|
|
291
|
+
job_type: string;
|
|
292
|
+
voice_id?: string;
|
|
293
|
+
/** Percent complete. Not updated while running; 100 once succeeded. */
|
|
294
|
+
progress: number;
|
|
295
|
+
attempts: number;
|
|
296
|
+
/** Failure detail, `{message, status_code}`. */
|
|
297
|
+
error?: Record<string, unknown> | null;
|
|
298
|
+
started_at?: number;
|
|
299
|
+
finished_at?: number;
|
|
300
|
+
created_at?: number;
|
|
301
|
+
[key: string]: unknown;
|
|
302
|
+
}
|
|
303
|
+
/** What `voices.clone` / `voices.enroll` return: the voice (`processing`) and its job id. */
|
|
304
|
+
interface CloneResult {
|
|
305
|
+
voice: Voice;
|
|
306
|
+
job_id: string;
|
|
307
|
+
}
|
|
308
|
+
/** A synthesized clip from `speech.create`. */
|
|
309
|
+
interface Audio {
|
|
310
|
+
data: Uint8Array;
|
|
311
|
+
/** `audio/wav` for WAV, `application/octet-stream` for raw PCM. */
|
|
312
|
+
contentType: string;
|
|
313
|
+
sampleRate: number | null;
|
|
314
|
+
/** Id of the generation history record (when object storage is enabled). */
|
|
315
|
+
generationId: string | null;
|
|
316
|
+
/** The stored clip, when object storage is enabled; may be a short-lived signed URL. */
|
|
317
|
+
audioURL: string | null;
|
|
318
|
+
billingRemaining: number | null;
|
|
319
|
+
}
|
|
320
|
+
/** One chunk of raw PCM from `speech.stream`. */
|
|
321
|
+
interface AudioChunk {
|
|
322
|
+
pcm: Uint8Array;
|
|
323
|
+
sampleRate: number | null;
|
|
324
|
+
/** PCM encoding, e.g. `s16le`. */
|
|
325
|
+
audioFormat: string | null;
|
|
326
|
+
index: number | null;
|
|
327
|
+
}
|
|
328
|
+
/** The final event of `speech.stream`. */
|
|
329
|
+
interface StreamDone {
|
|
330
|
+
audio_duration_s?: number;
|
|
331
|
+
generation_id?: string;
|
|
332
|
+
audio_url?: string;
|
|
333
|
+
}
|
|
334
|
+
/** `wav` (one-shot default) or raw little-endian PCM. */
|
|
335
|
+
/** A short-lived, single-use token from `tokens.create`. Hand `token` to a browser or device:
|
|
336
|
+
* it is good for one `speech.create` / `speech.stream` call or one realtime session, carries
|
|
337
|
+
* the `tts` scope only, and dies when the key that minted it is revoked or expires. */
|
|
338
|
+
interface AccessToken {
|
|
339
|
+
token: string;
|
|
340
|
+
/** Unix epoch ms. */
|
|
341
|
+
expires_at: number;
|
|
342
|
+
ttl_seconds: number;
|
|
343
|
+
}
|
|
344
|
+
/** A direct realtime session from `realtime.sessions.create`: a socket straight to the
|
|
345
|
+
* synthesis GPU in your account's server region. Pass it to `realtime.connect({ session })`. */
|
|
346
|
+
interface DirectSession {
|
|
347
|
+
session_id: string;
|
|
348
|
+
/** The GPU's WebSocket URL. Connect to it as returned. */
|
|
349
|
+
url: string;
|
|
350
|
+
/** The subprotocol to offer first (`finesse.v1`). */
|
|
351
|
+
protocol: string;
|
|
352
|
+
/** Single-use `ft1.` token, offered as the second subprotocol. */
|
|
353
|
+
token: string;
|
|
354
|
+
/** RFC 3339 time by which the socket must open (60 s after creation). */
|
|
355
|
+
connect_by: string;
|
|
356
|
+
/** The longest the session can run once connected. */
|
|
357
|
+
ttl_seconds: number;
|
|
358
|
+
/** Characters reserved for the session; it cannot synthesize more. */
|
|
359
|
+
budget_chars: number;
|
|
360
|
+
/** GPU region the session runs in: `ap-south` or `us-east`. */
|
|
361
|
+
region: string;
|
|
362
|
+
/** The only voices the session can use. */
|
|
363
|
+
voices: string[];
|
|
364
|
+
}
|
|
365
|
+
/** `mulaw` is raw G.711 µ-law for telephony and needs `sample_rate: 8000`. */
|
|
366
|
+
type OutputFormat = "wav" | "s16le" | "s24le" | "s32le" | "f32le" | "f64le" | "mulaw";
|
|
367
|
+
interface SpeechParams {
|
|
368
|
+
/** One of your voices or a public platform voice; it must be ready. */
|
|
369
|
+
voice_id: string;
|
|
370
|
+
/** At most 10,000 characters. Billed per character. */
|
|
371
|
+
text: string;
|
|
372
|
+
language?: string;
|
|
373
|
+
output_format?: OutputFormat;
|
|
374
|
+
/** Output rate in Hz, 8000-48000. The native rate is 24000; 8000 is produced as asked, other
|
|
375
|
+
* lower values still return 24000. */
|
|
376
|
+
sample_rate?: number;
|
|
377
|
+
/** Generation steps, 1-32 (default and maximum 32; over 32 is a 400); fewer is faster. */
|
|
378
|
+
num_step?: number;
|
|
379
|
+
}
|
|
380
|
+
declare const isReady: (voice: Voice) => boolean;
|
|
381
|
+
|
|
237
382
|
/**
|
|
238
383
|
* Realtime speech over one WebSocket. One session is one socket; each turn of speech runs in a
|
|
239
384
|
* *context*, and a socket can run several contexts at once (up to 4). Audio arrives as raw PCM,
|
|
@@ -255,6 +400,11 @@ declare class CloneTimeoutError extends FinesseError {
|
|
|
255
400
|
* in the WebSocket subprotocol list (`["finesse.v1", token]`), the one place a browser can put
|
|
256
401
|
* a credential on a handshake. (With a key, browsers and Deno before 2.5 fall back to a
|
|
257
402
|
* deprecated URL parameter, and the SDK logs a warning once.)
|
|
403
|
+
*
|
|
404
|
+
* For the lowest latency, run a *direct session*: the socket goes straight to the synthesis GPU
|
|
405
|
+
* in your account's server region instead of through the API. Create one on your server with
|
|
406
|
+
* `realtime.sessions.create` and connect with `{ session }` or `{ getSession }`. The messages
|
|
407
|
+
* and events are the same.
|
|
258
408
|
*/
|
|
259
409
|
|
|
260
410
|
/** A realtime context or session failed. */
|
|
@@ -263,7 +413,8 @@ declare class RealtimeError extends FinesseError {
|
|
|
263
413
|
* voices), `invalid_request` (a parameter out of range), a `task_failed` code. Whole session:
|
|
264
414
|
* `insufficient_credits`, `upstream_unavailable` (the engine could not be reached),
|
|
265
415
|
* `upstream_busy` (it is at capacity), `upstream_error`, `upstream_closed`,
|
|
266
|
-
* `connection_lost`, `token_spent`,
|
|
416
|
+
* `connection_lost`, `token_spent`, `session_spent`, `session_expired`, `unauthorized` (a
|
|
417
|
+
* direct session's token was refused), ... */
|
|
267
418
|
readonly code: string;
|
|
268
419
|
/** The context it applies to, or `null` for the whole session. */
|
|
269
420
|
readonly contextId: string | null;
|
|
@@ -358,9 +509,23 @@ interface ConnectOptions {
|
|
|
358
509
|
/** Returns a fresh token, e.g. from your own backend running `tokens.create`. Used for the
|
|
359
510
|
* first handshake when there is no `accessToken`, and for every reconnect. */
|
|
360
511
|
getAccessToken?: () => string | Promise<string>;
|
|
512
|
+
/** A direct session from `realtime.sessions.create`, usually created by your server. Its token
|
|
513
|
+
* is single use, so pass `getSession` too if the session should reconnect. */
|
|
514
|
+
session?: DirectSession;
|
|
515
|
+
/** Returns a new direct session, e.g. `() => client.realtime.sessions.create({ voice_ids })`
|
|
516
|
+
* on a server, or a call to your own backend in a browser. Used for the first handshake when
|
|
517
|
+
* there is no `session`, and for every reconnect. */
|
|
518
|
+
getSession?: () => DirectSession | Promise<DirectSession>;
|
|
361
519
|
/** A WebSocket implementation (defaults to the `ws` package in Node, the global elsewhere). */
|
|
362
520
|
WebSocket?: unknown;
|
|
363
521
|
}
|
|
522
|
+
interface CreateDirectSessionParams {
|
|
523
|
+
/** The voices the session may use: 1 to 16 of your voices or public platform voices. */
|
|
524
|
+
voice_ids: string[];
|
|
525
|
+
/** Characters to reserve, 1-200000 (default 20000). Unused characters are returned when the
|
|
526
|
+
* session ends. If the balance can't cover it, 1000 are reserved instead. */
|
|
527
|
+
budget_chars?: number;
|
|
528
|
+
}
|
|
364
529
|
/**
|
|
365
530
|
* One turn of speech. Create it with {@link RealtimeSession.context}. `send` text (the first
|
|
366
531
|
* call opens the context), `finish` when the turn's text is complete, and iterate it for its
|
|
@@ -428,8 +593,15 @@ declare class RealtimeSession {
|
|
|
428
593
|
private closedWaiters;
|
|
429
594
|
private readonly staticToken;
|
|
430
595
|
private tokenSpent;
|
|
596
|
+
private readonly directMode;
|
|
597
|
+
private staticSessionSpent;
|
|
598
|
+
private current;
|
|
431
599
|
constructor(core: Core, options?: ConnectOptions);
|
|
600
|
+
/** Where the socket connects: the API's `/stream`, or a direct session's GPU URL. */
|
|
432
601
|
get url(): string;
|
|
602
|
+
/** The direct session the socket runs on (its region and budget), or `null` when the session
|
|
603
|
+
* goes through the API. */
|
|
604
|
+
get directSession(): DirectSession | null;
|
|
433
605
|
get isOpen(): boolean;
|
|
434
606
|
/**
|
|
435
607
|
* Open the socket (no-op when open). Throws `AuthenticationError`, `PaymentRequiredError` or
|
|
@@ -460,144 +632,52 @@ declare class RealtimeSession {
|
|
|
460
632
|
/** The token for the next handshake, or `null` to use the API key. A static token is used
|
|
461
633
|
* first; once a handshake has spent it, `getAccessToken` supplies one per reconnect. */
|
|
462
634
|
private nextToken;
|
|
635
|
+
/** The direct session for the next handshake. Like a token, a static session is used first;
|
|
636
|
+
* once a handshake has spent it, `getSession` supplies one per reconnect. */
|
|
637
|
+
private nextSession;
|
|
463
638
|
private dial;
|
|
464
639
|
private onClose;
|
|
465
640
|
private onAudio;
|
|
466
641
|
private onMessage;
|
|
467
642
|
}
|
|
643
|
+
/** `client.realtime.sessions`: direct sessions, which run straight against the synthesis GPU. */
|
|
644
|
+
declare class DirectSessions {
|
|
645
|
+
private readonly core;
|
|
646
|
+
constructor(core: Core);
|
|
647
|
+
/** Create a direct session. Call this on your server with an API key that has the `tts`
|
|
648
|
+
* scope; it reserves `budget_chars` from your balance now. Connect within 60 s
|
|
649
|
+
* (`connect_by`), with `realtime.connect({ session })`, or hand `url`, `protocol` and `token`
|
|
650
|
+
* to a browser:
|
|
651
|
+
*
|
|
652
|
+
* ```ts
|
|
653
|
+
* const session = await client.realtime.sessions.create({ voice_ids: [voiceId] });
|
|
654
|
+
* const rt = await client.realtime.connect({ session });
|
|
655
|
+
* ```
|
|
656
|
+
*/
|
|
657
|
+
create(params: CreateDirectSessionParams, options?: RequestOptions): Promise<DirectSession>;
|
|
658
|
+
}
|
|
468
659
|
/** `client.realtime`: open realtime sessions. */
|
|
469
660
|
declare class Realtime {
|
|
470
661
|
private readonly core;
|
|
662
|
+
/** Direct sessions: lower latency, straight to the GPU. */
|
|
663
|
+
readonly sessions: DirectSessions;
|
|
471
664
|
constructor(core: Core);
|
|
472
665
|
/** Open a session. */
|
|
473
666
|
connect(options?: ConnectOptions): Promise<RealtimeSession>;
|
|
474
667
|
/** Create a session without opening it; it opens on first use. */
|
|
475
668
|
session(options?: ConnectOptions): RealtimeSession;
|
|
476
669
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
use_speaker_boost?: boolean;
|
|
489
|
-
emotion?: string;
|
|
490
|
-
engine_params?: Record<string, unknown>;
|
|
491
|
-
}
|
|
492
|
-
/** A voice. It can synthesize once `status === "ready"`. */
|
|
493
|
-
interface Voice {
|
|
494
|
-
id: string;
|
|
495
|
-
org_id: string;
|
|
496
|
-
name: string;
|
|
497
|
-
/** `draft`, `processing`, `ready`, `failed`, ... */
|
|
498
|
-
status: string;
|
|
499
|
-
clone_method: string;
|
|
500
|
-
/** `private` for your voices, `public` for platform catalog voices. */
|
|
501
|
-
visibility: string;
|
|
502
|
-
primary_language: string;
|
|
503
|
-
description?: string;
|
|
504
|
-
model?: string;
|
|
505
|
-
/** Human-friendly handle, also usable as `voice` on the OpenAI-compatible endpoint. */
|
|
506
|
-
alias?: string;
|
|
507
|
-
source?: string;
|
|
508
|
-
language?: string;
|
|
509
|
-
gender?: string;
|
|
510
|
-
/** Labels as a plain object (the API sends `[{key, value}]`; the SDK converts it). */
|
|
511
|
-
labels: Record<string, string>;
|
|
512
|
-
settings?: VoiceSettings;
|
|
513
|
-
external_ref?: string;
|
|
514
|
-
original_audio_url?: string;
|
|
515
|
-
sample_audio_url?: string;
|
|
516
|
-
avatar_url?: string;
|
|
517
|
-
base_voice_id?: string;
|
|
518
|
-
created_at?: number;
|
|
519
|
-
updated_at?: number;
|
|
520
|
-
last_used_at?: number;
|
|
521
|
-
expires_at?: number;
|
|
522
|
-
/** Prefix of the API key that created the voice; only on your own voices. */
|
|
523
|
-
created_via_key_prefix?: string;
|
|
524
|
-
[key: string]: unknown;
|
|
525
|
-
}
|
|
526
|
-
/** An asynchronous clone job. */
|
|
527
|
-
interface CloneJob {
|
|
528
|
-
id: string;
|
|
529
|
-
org_id: string;
|
|
530
|
-
/** `pending`, `running`, `succeeded`, `failed`, `cancelled` or `expired`. */
|
|
531
|
-
status: string;
|
|
532
|
-
job_type: string;
|
|
533
|
-
voice_id?: string;
|
|
534
|
-
/** Percent complete. Not updated while running; 100 once succeeded. */
|
|
535
|
-
progress: number;
|
|
536
|
-
attempts: number;
|
|
537
|
-
/** Failure detail, `{message, status_code}`. */
|
|
538
|
-
error?: Record<string, unknown> | null;
|
|
539
|
-
started_at?: number;
|
|
540
|
-
finished_at?: number;
|
|
541
|
-
created_at?: number;
|
|
542
|
-
[key: string]: unknown;
|
|
543
|
-
}
|
|
544
|
-
/** What `voices.clone` / `voices.enroll` return: the voice (`processing`) and its job id. */
|
|
545
|
-
interface CloneResult {
|
|
546
|
-
voice: Voice;
|
|
547
|
-
job_id: string;
|
|
548
|
-
}
|
|
549
|
-
/** A synthesized clip from `speech.create`. */
|
|
550
|
-
interface Audio {
|
|
551
|
-
data: Uint8Array;
|
|
552
|
-
/** `audio/wav` for WAV, `application/octet-stream` for raw PCM. */
|
|
553
|
-
contentType: string;
|
|
554
|
-
sampleRate: number | null;
|
|
555
|
-
/** Id of the generation history record (when object storage is enabled). */
|
|
556
|
-
generationId: string | null;
|
|
557
|
-
/** The stored clip, when object storage is enabled; may be a short-lived signed URL. */
|
|
558
|
-
audioURL: string | null;
|
|
559
|
-
billingRemaining: number | null;
|
|
560
|
-
}
|
|
561
|
-
/** One chunk of raw PCM from `speech.stream`. */
|
|
562
|
-
interface AudioChunk {
|
|
563
|
-
pcm: Uint8Array;
|
|
564
|
-
sampleRate: number | null;
|
|
565
|
-
/** PCM encoding, e.g. `s16le`. */
|
|
566
|
-
audioFormat: string | null;
|
|
567
|
-
index: number | null;
|
|
568
|
-
}
|
|
569
|
-
/** The final event of `speech.stream`. */
|
|
570
|
-
interface StreamDone {
|
|
571
|
-
audio_duration_s?: number;
|
|
572
|
-
generation_id?: string;
|
|
573
|
-
audio_url?: string;
|
|
574
|
-
}
|
|
575
|
-
/** `wav` (one-shot default) or raw little-endian PCM. */
|
|
576
|
-
/** A short-lived, single-use token from `tokens.create`. Hand `token` to a browser or device:
|
|
577
|
-
* it is good for one `speech.create` / `speech.stream` call or one realtime session, carries
|
|
578
|
-
* the `tts` scope only, and dies when the key that minted it is revoked or expires. */
|
|
579
|
-
interface AccessToken {
|
|
580
|
-
token: string;
|
|
581
|
-
/** Unix epoch ms. */
|
|
582
|
-
expires_at: number;
|
|
583
|
-
ttl_seconds: number;
|
|
584
|
-
}
|
|
585
|
-
/** `mulaw` is raw G.711 µ-law for telephony and needs `sample_rate: 8000`. */
|
|
586
|
-
type OutputFormat = "wav" | "s16le" | "s24le" | "s32le" | "f32le" | "f64le" | "mulaw";
|
|
587
|
-
interface SpeechParams {
|
|
588
|
-
/** One of your voices or a public platform voice; it must be ready. */
|
|
589
|
-
voice_id: string;
|
|
590
|
-
/** At most 10,000 characters. Billed per character. */
|
|
591
|
-
text: string;
|
|
592
|
-
language?: string;
|
|
593
|
-
output_format?: OutputFormat;
|
|
594
|
-
/** Output rate in Hz, 8000-48000. The native rate is 24000; 8000 is produced as asked, other
|
|
595
|
-
* lower values still return 24000. */
|
|
596
|
-
sample_rate?: number;
|
|
597
|
-
/** Generation steps, 1-32 (default and maximum 32; over 32 is a 400); fewer is faster. */
|
|
598
|
-
num_step?: number;
|
|
599
|
-
}
|
|
600
|
-
declare const isReady: (voice: Voice) => boolean;
|
|
670
|
+
/**
|
|
671
|
+
* Open a direct session without a client, for a browser or device that holds no API key. Your
|
|
672
|
+
* server creates the session (`realtime.sessions.create`) and returns it; `getSession` fetches a
|
|
673
|
+
* new one for each reconnect.
|
|
674
|
+
*
|
|
675
|
+
* ```ts
|
|
676
|
+
* const getSession = async () => (await fetch("/api/finesse-session", { method: "POST" })).json();
|
|
677
|
+
* const rt = await connectDirect({ getSession });
|
|
678
|
+
* ```
|
|
679
|
+
*/
|
|
680
|
+
declare function connectDirect(options: Omit<ConnectOptions, "accessToken" | "getAccessToken">): Promise<RealtimeSession>;
|
|
601
681
|
|
|
602
682
|
interface WaitOptions {
|
|
603
683
|
/** Give up after this many ms. Default 300 000 (the first clone after the cloner has been
|
|
@@ -826,6 +906,6 @@ declare class Finesse {
|
|
|
826
906
|
get withRawResponse(): RawClient<FinesseResources>;
|
|
827
907
|
}
|
|
828
908
|
|
|
829
|
-
declare const VERSION = "0.
|
|
909
|
+
declare const VERSION = "0.2.0";
|
|
830
910
|
|
|
831
|
-
export { APIConnectionError, APIStatusError, APITimeoutError, type AccessToken, type Audio, type AudioChunk, type AudioEvent, AuthenticationError, BadRequestError, type ClientOptions, CloneFailedError, type CloneJob, type CloneResult, CloneTimeoutError, type CloneVoiceParams, ConflictError, type ConnectOptions, type ConsentParams, type ContextOptions, type CreateTokenParams, type CreateVoiceParams, type EnrollVoiceParams, type FileInput, Finesse, FinesseError, InternalServerError, type ListVoicesParams, type LogLevel, type Logger, NotFoundError, type OutputFormat, PayloadTooLargeError, PaymentRequiredError, PermissionDeniedError, RateLimitError, type RawClient, type RawResponse, RealtimeContext, RealtimeError, type RealtimeEvent, RealtimeSession, type RequestOptions, type SentenceDone, type SpeechParams, SpeechStream, type StreamDone, StreamError, type TaskCancelled, type TaskDone, type TaskStarted, type UnknownEvent, UnprocessableEntityError, type UpdateVoiceParams, VERSION, type Voice, VoicePage, VoicePagePromise, type VoiceSettings, type WaitOptions, fileFromPath, isReady };
|
|
911
|
+
export { APIConnectionError, APIStatusError, APITimeoutError, type AccessToken, type Audio, type AudioChunk, type AudioEvent, AuthenticationError, BadRequestError, type ClientOptions, CloneFailedError, type CloneJob, type CloneResult, CloneTimeoutError, type CloneVoiceParams, ConflictError, type ConnectOptions, type ConsentParams, type ContextOptions, type CreateDirectSessionParams, type CreateTokenParams, type CreateVoiceParams, type DirectSession, type EnrollVoiceParams, type FileInput, Finesse, FinesseError, InternalServerError, type ListVoicesParams, type LogLevel, type Logger, NotFoundError, type OutputFormat, PayloadTooLargeError, PaymentRequiredError, PermissionDeniedError, RateLimitError, type RawClient, type RawResponse, RealtimeContext, RealtimeError, type RealtimeEvent, RealtimeSession, type RequestOptions, type SentenceDone, type SpeechParams, SpeechStream, type StreamDone, StreamError, type TaskCancelled, type TaskDone, type TaskStarted, type UnknownEvent, UnprocessableEntityError, type UpdateVoiceParams, VERSION, type Voice, VoicePage, VoicePagePromise, type VoiceSettings, type WaitOptions, connectDirect, fileFromPath, isReady };
|
package/dist/index.js
CHANGED
|
@@ -163,7 +163,7 @@ function numberField(body2, key) {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
// src/version.ts
|
|
166
|
-
var VERSION = "0.
|
|
166
|
+
var VERSION = "0.2.0";
|
|
167
167
|
|
|
168
168
|
// src/core.ts
|
|
169
169
|
var DEFAULT_BASE_URL = "https://fi.production.flamapis.com";
|
|
@@ -526,7 +526,8 @@ var RealtimeError = class extends FinesseError {
|
|
|
526
526
|
* voices), `invalid_request` (a parameter out of range), a `task_failed` code. Whole session:
|
|
527
527
|
* `insufficient_credits`, `upstream_unavailable` (the engine could not be reached),
|
|
528
528
|
* `upstream_busy` (it is at capacity), `upstream_error`, `upstream_closed`,
|
|
529
|
-
* `connection_lost`, `token_spent`,
|
|
529
|
+
* `connection_lost`, `token_spent`, `session_spent`, `session_expired`, `unauthorized` (a
|
|
530
|
+
* direct session's token was refused), ... */
|
|
530
531
|
code;
|
|
531
532
|
/** The context it applies to, or `null` for the whole session. */
|
|
532
533
|
contextId;
|
|
@@ -543,8 +544,15 @@ var RealtimeError = class extends FinesseError {
|
|
|
543
544
|
return RETRYABLE_CODES.has(this.code) || this.closeCode !== null && RETRYABLE_CLOSES.has(this.closeCode);
|
|
544
545
|
}
|
|
545
546
|
};
|
|
546
|
-
function closeError(code, reason) {
|
|
547
|
+
function closeError(code, reason, direct = false) {
|
|
547
548
|
const c = { closeCode: code };
|
|
549
|
+
if (direct && code === 4401) {
|
|
550
|
+
return new RealtimeError(
|
|
551
|
+
"unauthorized",
|
|
552
|
+
"The session token was refused (invalid, already used, or past connect_by). Create a new session.",
|
|
553
|
+
c
|
|
554
|
+
);
|
|
555
|
+
}
|
|
548
556
|
if (code === 1e3) return null;
|
|
549
557
|
if (code === 1008) return new RealtimeError(reason || "policy_violation", reason, c);
|
|
550
558
|
if (code === 1009) return new RealtimeError("message_too_big", "A message was too large.", c);
|
|
@@ -760,7 +768,13 @@ var RealtimeSession = class {
|
|
|
760
768
|
constructor(core, options = {}) {
|
|
761
769
|
this.core = core;
|
|
762
770
|
this.options = options;
|
|
763
|
-
this.
|
|
771
|
+
this.directMode = Boolean(options.session || options.getSession);
|
|
772
|
+
if (this.directMode && (options.accessToken || options.getAccessToken)) {
|
|
773
|
+
throw new FinesseError(
|
|
774
|
+
"Pass { session } / { getSession } or { accessToken } / { getAccessToken }, not both."
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
this.staticToken = this.directMode ? null : options.accessToken ?? core.accessToken;
|
|
764
778
|
}
|
|
765
779
|
core;
|
|
766
780
|
options;
|
|
@@ -775,9 +789,19 @@ var RealtimeSession = class {
|
|
|
775
789
|
closedWaiters = [];
|
|
776
790
|
staticToken;
|
|
777
791
|
tokenSpent = false;
|
|
792
|
+
directMode;
|
|
793
|
+
staticSessionSpent = false;
|
|
794
|
+
current = null;
|
|
795
|
+
/** Where the socket connects: the API's `/stream`, or a direct session's GPU URL. */
|
|
778
796
|
get url() {
|
|
797
|
+
if (this.directMode) return this.current?.url ?? this.options.session?.url ?? "";
|
|
779
798
|
return `${this.core.wsBaseURL}${API_PREFIX}/stream`;
|
|
780
799
|
}
|
|
800
|
+
/** The direct session the socket runs on (its region and budget), or `null` when the session
|
|
801
|
+
* goes through the API. */
|
|
802
|
+
get directSession() {
|
|
803
|
+
return this.current;
|
|
804
|
+
}
|
|
781
805
|
get isOpen() {
|
|
782
806
|
return this.ws !== null && this.ws.readyState === OPEN;
|
|
783
807
|
}
|
|
@@ -911,6 +935,31 @@ var RealtimeSession = class {
|
|
|
911
935
|
}
|
|
912
936
|
return null;
|
|
913
937
|
}
|
|
938
|
+
/** The direct session for the next handshake. Like a token, a static session is used first;
|
|
939
|
+
* once a handshake has spent it, `getSession` supplies one per reconnect. */
|
|
940
|
+
async nextSession() {
|
|
941
|
+
const fixed = this.options.session;
|
|
942
|
+
if (fixed && !this.staticSessionSpent) {
|
|
943
|
+
if (Date.parse(fixed.connect_by) <= Date.now()) {
|
|
944
|
+
throw new RealtimeError(
|
|
945
|
+
"session_expired",
|
|
946
|
+
"This direct session's connect_by has passed. Create a new session."
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
return fixed;
|
|
950
|
+
}
|
|
951
|
+
if (this.options.getSession) {
|
|
952
|
+
const s = await this.options.getSession();
|
|
953
|
+
if (!s || typeof s.url !== "string" || !s.url || typeof s.token !== "string" || !s.token) {
|
|
954
|
+
throw new FinesseError("getSession must return a session with a url and a token.");
|
|
955
|
+
}
|
|
956
|
+
return s;
|
|
957
|
+
}
|
|
958
|
+
throw new RealtimeError(
|
|
959
|
+
"session_spent",
|
|
960
|
+
"This direct session was used by an earlier connection. Pass { getSession } so a reconnect can create a new one."
|
|
961
|
+
);
|
|
962
|
+
}
|
|
914
963
|
async dial() {
|
|
915
964
|
const timeout = this.options.openTimeout ?? DEFAULT_OPEN_TIMEOUT_MS;
|
|
916
965
|
const custom = this.options.WebSocket;
|
|
@@ -918,11 +967,21 @@ var RealtimeSession = class {
|
|
|
918
967
|
headers: !isBrowser(),
|
|
919
968
|
open: (url2, h, t, protocols2) => h ? new custom(url2, protocols2, { headers: h, handshakeTimeout: t }) : new custom(url2, protocols2)
|
|
920
969
|
} : await defaultDialer();
|
|
921
|
-
const
|
|
922
|
-
const
|
|
970
|
+
const direct = this.directMode ? await this.nextSession() : null;
|
|
971
|
+
const token = direct ? null : await this.nextToken();
|
|
972
|
+
let protocols = token ? [SUBPROTOCOL, token] : void 0;
|
|
923
973
|
let url = this.url;
|
|
924
974
|
let headers = null;
|
|
925
|
-
if (
|
|
975
|
+
if (direct) {
|
|
976
|
+
protocols = [direct.protocol || SUBPROTOCOL, direct.token];
|
|
977
|
+
url = direct.url;
|
|
978
|
+
if (dialer.headers) {
|
|
979
|
+
headers = { ...this.core.headers };
|
|
980
|
+
delete headers.Accept;
|
|
981
|
+
delete headers["X-API-Key"];
|
|
982
|
+
delete headers.Authorization;
|
|
983
|
+
}
|
|
984
|
+
} else if (token) {
|
|
926
985
|
if (dialer.headers) {
|
|
927
986
|
headers = { ...this.core.headers };
|
|
928
987
|
delete headers.Accept;
|
|
@@ -981,6 +1040,10 @@ var RealtimeSession = class {
|
|
|
981
1040
|
throw new RealtimeError("session_closed", "This session was closed.");
|
|
982
1041
|
}
|
|
983
1042
|
if (token && token === this.staticToken) this.tokenSpent = true;
|
|
1043
|
+
if (direct) {
|
|
1044
|
+
if (direct === this.options.session) this.staticSessionSpent = true;
|
|
1045
|
+
this.current = direct;
|
|
1046
|
+
}
|
|
984
1047
|
this.ws = ws;
|
|
985
1048
|
this.pendingAudio = null;
|
|
986
1049
|
this.fatal = null;
|
|
@@ -999,14 +1062,14 @@ var RealtimeSession = class {
|
|
|
999
1062
|
}
|
|
1000
1063
|
onClose(ws, code, reason) {
|
|
1001
1064
|
if (this.ws !== null && this.ws !== ws) {
|
|
1002
|
-
const err = closeError(code, reason ?? "") ?? new RealtimeError("connection_closed");
|
|
1065
|
+
const err = closeError(code, reason ?? "", this.directMode) ?? new RealtimeError("connection_closed");
|
|
1003
1066
|
for (const ctx of [...this.contexts.values()]) if (ctx.socket === ws) ctx._fail(err);
|
|
1004
1067
|
return;
|
|
1005
1068
|
}
|
|
1006
1069
|
this.ws = null;
|
|
1007
1070
|
let error = this.fatal;
|
|
1008
1071
|
if (error) error.closeCode ??= code;
|
|
1009
|
-
else error = closeError(code, reason ?? "");
|
|
1072
|
+
else error = closeError(code, reason ?? "", this.directMode);
|
|
1010
1073
|
if (!error && this.contexts.size) {
|
|
1011
1074
|
error = new RealtimeError("connection_closed", "The session ended.", { closeCode: code });
|
|
1012
1075
|
}
|
|
@@ -1073,11 +1136,35 @@ var RealtimeSession = class {
|
|
|
1073
1136
|
);
|
|
1074
1137
|
}
|
|
1075
1138
|
};
|
|
1139
|
+
var DirectSessions = class {
|
|
1140
|
+
constructor(core) {
|
|
1141
|
+
this.core = core;
|
|
1142
|
+
}
|
|
1143
|
+
core;
|
|
1144
|
+
/** Create a direct session. Call this on your server with an API key that has the `tts`
|
|
1145
|
+
* scope; it reserves `budget_chars` from your balance now. Connect within 60 s
|
|
1146
|
+
* (`connect_by`), with `realtime.connect({ session })`, or hand `url`, `protocol` and `token`
|
|
1147
|
+
* to a browser:
|
|
1148
|
+
*
|
|
1149
|
+
* ```ts
|
|
1150
|
+
* const session = await client.realtime.sessions.create({ voice_ids: [voiceId] });
|
|
1151
|
+
* const rt = await client.realtime.connect({ session });
|
|
1152
|
+
* ```
|
|
1153
|
+
*/
|
|
1154
|
+
async create(params, options = {}) {
|
|
1155
|
+
const json = { voice_ids: params.voice_ids };
|
|
1156
|
+
if (params.budget_chars !== void 0) json.budget_chars = params.budget_chars;
|
|
1157
|
+
return this.core.json("POST", "/realtime/sessions", { ...options, json });
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1076
1160
|
var Realtime = class {
|
|
1077
1161
|
constructor(core) {
|
|
1078
1162
|
this.core = core;
|
|
1163
|
+
this.sessions = new DirectSessions(core);
|
|
1079
1164
|
}
|
|
1080
1165
|
core;
|
|
1166
|
+
/** Direct sessions: lower latency, straight to the GPU. */
|
|
1167
|
+
sessions;
|
|
1081
1168
|
/** Open a session. */
|
|
1082
1169
|
async connect(options = {}) {
|
|
1083
1170
|
const session = new RealtimeSession(this.core, options);
|
|
@@ -1089,6 +1176,15 @@ var Realtime = class {
|
|
|
1089
1176
|
return new RealtimeSession(this.core, options);
|
|
1090
1177
|
}
|
|
1091
1178
|
};
|
|
1179
|
+
async function connectDirect(options) {
|
|
1180
|
+
if (!options.session && !options.getSession) {
|
|
1181
|
+
throw new FinesseError("connectDirect needs { session } or { getSession }.");
|
|
1182
|
+
}
|
|
1183
|
+
const core = new Core({ accessToken: "direct-session-only" });
|
|
1184
|
+
const session = new RealtimeSession(core, options);
|
|
1185
|
+
await session.open();
|
|
1186
|
+
return session;
|
|
1187
|
+
}
|
|
1092
1188
|
var warnedLoggers = /* @__PURE__ */ new WeakSet();
|
|
1093
1189
|
function warnQueryKey(logger) {
|
|
1094
1190
|
if (warnedLoggers.has(logger)) return;
|
|
@@ -1716,6 +1812,7 @@ export {
|
|
|
1716
1812
|
VERSION,
|
|
1717
1813
|
VoicePage,
|
|
1718
1814
|
VoicePagePromise,
|
|
1815
|
+
connectDirect,
|
|
1719
1816
|
fileFromPath,
|
|
1720
1817
|
isReady
|
|
1721
1818
|
};
|