@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/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;
@@ -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
- /** Response shapes. Field names match the API (snake_case); timestamps are Unix milliseconds.
479
- * Media URLs (`original_audio_url`, `sample_audio_url`, `avatar_url`, `audio_url`) may be
480
- * short-lived signed URLs: download them promptly, or fetch the object again for a fresh one. */
481
- interface VoiceSettings {
482
- stability?: number;
483
- similarity?: number;
484
- style?: number;
485
- speed?: number;
486
- pitch?: number;
487
- volume?: number;
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.1.2";
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 };