@flamai/finesse 0.1.1 → 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/dist/index.d.cts 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;
@@ -332,10 +483,11 @@ interface ContextOptions {
332
483
  voice_id: string;
333
484
  context_id?: string;
334
485
  language?: string;
335
- /** `s16le` (default), `s24le`, `s32le`, `f32le`, `f64le` or `opus`. */
336
- output_format?: "s16le" | "s24le" | "s32le" | "f32le" | "f64le" | "opus";
337
- /** Output rate in Hz, 8000-48000; the native rate is 24000 and lower values still produce
338
- * 24000. */
486
+ /** `s16le` (default), `s24le`, `s32le`, `f32le`, `f64le`, `opus`, or `mulaw` (G.711 µ-law
487
+ * for telephony, with `sample_rate: 8000`). */
488
+ output_format?: "s16le" | "s24le" | "s32le" | "f32le" | "f64le" | "opus" | "mulaw";
489
+ /** Output rate in Hz, 8000-48000; the native rate is 24000. 8000 is produced as asked, other
490
+ * lower values still produce 24000. */
339
491
  sample_rate?: number;
340
492
  /** Generation steps, 1-32 (default 32); fewer is faster. Values over 32 are capped. */
341
493
  num_step?: number;
@@ -357,9 +509,23 @@ interface ConnectOptions {
357
509
  /** Returns a fresh token, e.g. from your own backend running `tokens.create`. Used for the
358
510
  * first handshake when there is no `accessToken`, and for every reconnect. */
359
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>;
360
519
  /** A WebSocket implementation (defaults to the `ws` package in Node, the global elsewhere). */
361
520
  WebSocket?: unknown;
362
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
+ }
363
529
  /**
364
530
  * One turn of speech. Create it with {@link RealtimeSession.context}. `send` text (the first
365
531
  * call opens the context), `finish` when the turn's text is complete, and iterate it for its
@@ -427,8 +593,15 @@ declare class RealtimeSession {
427
593
  private closedWaiters;
428
594
  private readonly staticToken;
429
595
  private tokenSpent;
596
+ private readonly directMode;
597
+ private staticSessionSpent;
598
+ private current;
430
599
  constructor(core: Core, options?: ConnectOptions);
600
+ /** Where the socket connects: the API's `/stream`, or a direct session's GPU URL. */
431
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;
432
605
  get isOpen(): boolean;
433
606
  /**
434
607
  * Open the socket (no-op when open). Throws `AuthenticationError`, `PaymentRequiredError` or
@@ -459,142 +632,52 @@ declare class RealtimeSession {
459
632
  /** The token for the next handshake, or `null` to use the API key. A static token is used
460
633
  * first; once a handshake has spent it, `getAccessToken` supplies one per reconnect. */
461
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;
462
638
  private dial;
463
639
  private onClose;
464
640
  private onAudio;
465
641
  private onMessage;
466
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
+ }
467
659
  /** `client.realtime`: open realtime sessions. */
468
660
  declare class Realtime {
469
661
  private readonly core;
662
+ /** Direct sessions: lower latency, straight to the GPU. */
663
+ readonly sessions: DirectSessions;
470
664
  constructor(core: Core);
471
665
  /** Open a session. */
472
666
  connect(options?: ConnectOptions): Promise<RealtimeSession>;
473
667
  /** Create a session without opening it; it opens on first use. */
474
668
  session(options?: ConnectOptions): RealtimeSession;
475
669
  }
476
-
477
- /** Response shapes. Field names match the API (snake_case); timestamps are Unix milliseconds.
478
- * Media URLs (`original_audio_url`, `sample_audio_url`, `avatar_url`, `audio_url`) may be
479
- * short-lived signed URLs: download them promptly, or fetch the object again for a fresh one. */
480
- interface VoiceSettings {
481
- stability?: number;
482
- similarity?: number;
483
- style?: number;
484
- speed?: number;
485
- pitch?: number;
486
- volume?: number;
487
- use_speaker_boost?: boolean;
488
- emotion?: string;
489
- engine_params?: Record<string, unknown>;
490
- }
491
- /** A voice. It can synthesize once `status === "ready"`. */
492
- interface Voice {
493
- id: string;
494
- org_id: string;
495
- name: string;
496
- /** `draft`, `processing`, `ready`, `failed`, ... */
497
- status: string;
498
- clone_method: string;
499
- /** `private` for your voices, `public` for platform catalog voices. */
500
- visibility: string;
501
- primary_language: string;
502
- description?: string;
503
- model?: string;
504
- /** Human-friendly handle, also usable as `voice` on the OpenAI-compatible endpoint. */
505
- alias?: string;
506
- source?: string;
507
- language?: string;
508
- gender?: string;
509
- /** Labels as a plain object (the API sends `[{key, value}]`; the SDK converts it). */
510
- labels: Record<string, string>;
511
- settings?: VoiceSettings;
512
- external_ref?: string;
513
- original_audio_url?: string;
514
- sample_audio_url?: string;
515
- avatar_url?: string;
516
- base_voice_id?: string;
517
- created_at?: number;
518
- updated_at?: number;
519
- last_used_at?: number;
520
- expires_at?: number;
521
- /** Prefix of the API key that created the voice; only on your own voices. */
522
- created_via_key_prefix?: string;
523
- [key: string]: unknown;
524
- }
525
- /** An asynchronous clone job. */
526
- interface CloneJob {
527
- id: string;
528
- org_id: string;
529
- /** `pending`, `running`, `succeeded`, `failed`, `cancelled` or `expired`. */
530
- status: string;
531
- job_type: string;
532
- voice_id?: string;
533
- /** Percent complete. Not updated while running; 100 once succeeded. */
534
- progress: number;
535
- attempts: number;
536
- /** Failure detail, `{message, status_code}`. */
537
- error?: Record<string, unknown> | null;
538
- started_at?: number;
539
- finished_at?: number;
540
- created_at?: number;
541
- [key: string]: unknown;
542
- }
543
- /** What `voices.clone` / `voices.enroll` return: the voice (`processing`) and its job id. */
544
- interface CloneResult {
545
- voice: Voice;
546
- job_id: string;
547
- }
548
- /** A synthesized clip from `speech.create`. */
549
- interface Audio {
550
- data: Uint8Array;
551
- /** `audio/wav` for WAV, `application/octet-stream` for raw PCM. */
552
- contentType: string;
553
- sampleRate: number | null;
554
- /** Id of the generation history record (when object storage is enabled). */
555
- generationId: string | null;
556
- /** The stored clip, when object storage is enabled; may be a short-lived signed URL. */
557
- audioURL: string | null;
558
- billingRemaining: number | null;
559
- }
560
- /** One chunk of raw PCM from `speech.stream`. */
561
- interface AudioChunk {
562
- pcm: Uint8Array;
563
- sampleRate: number | null;
564
- /** PCM encoding, e.g. `s16le`. */
565
- audioFormat: string | null;
566
- index: number | null;
567
- }
568
- /** The final event of `speech.stream`. */
569
- interface StreamDone {
570
- audio_duration_s?: number;
571
- generation_id?: string;
572
- audio_url?: string;
573
- }
574
- /** `wav` (one-shot default) or raw little-endian PCM. */
575
- /** A short-lived, single-use token from `tokens.create`. Hand `token` to a browser or device:
576
- * it is good for one `speech.create` / `speech.stream` call or one realtime session, carries
577
- * the `tts` scope only, and dies when the key that minted it is revoked or expires. */
578
- interface AccessToken {
579
- token: string;
580
- /** Unix epoch ms. */
581
- expires_at: number;
582
- ttl_seconds: number;
583
- }
584
- type OutputFormat = "wav" | "s16le" | "s24le" | "s32le" | "f32le" | "f64le";
585
- interface SpeechParams {
586
- /** One of your voices or a public platform voice; it must be ready. */
587
- voice_id: string;
588
- /** At most 10,000 characters. Billed per character. */
589
- text: string;
590
- language?: string;
591
- output_format?: OutputFormat;
592
- /** Output rate in Hz, 8000-48000. The native rate is 24000; lower values still return 24000. */
593
- sample_rate?: number;
594
- /** Generation steps, 1-32 (default and maximum 32; over 32 is a 400); fewer is faster. */
595
- num_step?: number;
596
- }
597
- 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>;
598
681
 
599
682
  interface WaitOptions {
600
683
  /** Give up after this many ms. Default 300 000 (the first clone after the cloner has been
@@ -823,6 +906,6 @@ declare class Finesse {
823
906
  get withRawResponse(): RawClient<FinesseResources>;
824
907
  }
825
908
 
826
- declare const VERSION = "0.1.1";
909
+ declare const VERSION = "0.2.0";
827
910
 
828
- 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 };