@lokutor/sdk 1.5.1 → 1.6.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/README.md CHANGED
@@ -11,7 +11,7 @@ Official JavaScript/TypeScript SDK for [Lokutor AI](https://lokutor.com), provid
11
11
 
12
12
  - ⚡ **Ultra-low latency**: Optimized WebSocket streaming for real-time interactions.
13
13
  - 🗣️ **Conversational Intelligence**: Integrated STT, LLM, and TTS with VAD and barge-in support.
14
- - 🌍 **Multi-lingual**: Support for English, Spanish, French, Portuguese, and Korean.
14
+ - 🌍 **Multi-lingual**: English, Spanish, Catalan, Galician, Basque, Portuguese, French, Italian and German (Catalan, Galician and Basque are speak-only: no speech recognition yet).
15
15
  - 🎨 **Natural Voices**: Multiple high-quality male and female voice styles.
16
16
  - 💻 **TypeScript First**: Full type definitions included.
17
17
 
@@ -63,9 +63,36 @@ await client.synthesize({
63
63
  });
64
64
  ```
65
65
 
66
+ ## Using the SDK in a browser without exposing your key
67
+
68
+ Never ship an API key in browser code. Mint a short-lived ticket on your server and let the SDK fetch a
69
+ fresh one before every connect (tickets last 10 minutes, so reconnects need a new one):
70
+
71
+ ```js
72
+ // Your server (Node 22+, key stays here)
73
+ app.post('/lokutor-ticket', async (_req, res) => {
74
+ const r = await fetch('https://api.lokutor.com/ws-ticket', {
75
+ method: 'POST',
76
+ headers: { Authorization: `Bearer ${process.env.LOKUTOR_API_KEY}` },
77
+ });
78
+ res.json(await r.json()); // { ticket: "wst_...", expires_in: 600 }
79
+ });
80
+
81
+ // The page
82
+ const agent = new VoiceAgentClient({
83
+ auth: async () => (await (await fetch('/lokutor-ticket', { method: 'POST' })).json()).ticket,
84
+ prompt: 'You are a helpful assistant.',
85
+ enableAudio: true,
86
+ });
87
+ await agent.connect();
88
+ ```
89
+
90
+ To run an agent you configured in the dashboard, pass `agentId` and leave out `prompt`, `voice` and
91
+ `language`: only what you set explicitly overrides the agent.
92
+
66
93
  ## Hardware Compatibility
67
94
 
68
- This SDK is platform-agnostic and works in both **Node.js** and **Browser** environments.
95
+ This SDK is platform-agnostic and works in both **Node.js** (22 or newer, for the global `WebSocket`) and **Browser** environments.
69
96
 
70
97
  ### For Node.js
71
98
  We recommend using `node-record-lpcm16` for recording and `speaker` for playback. See the [examples](./examples) folder for a complete Node.js CLI implementation.
package/dist/index.d.mts CHANGED
@@ -4,13 +4,13 @@
4
4
  /**
5
5
  * Built-in voices.
6
6
  *
7
- * Versa 2.0 names each voice for the language its reference speaker was recorded in: `EN_*` are
8
- * English speakers, `ESCA_*` Spanish/Catalan. That is a guide to how a voice sounds, not a
9
- * restriction — the model is zero-shot, so any voice can speak any supported language.
7
+ * Use `F1`–`F5` / `M1`–`M5` (what GET /voices lists). Each is a slot: the server resolves it to the
8
+ * native speaker of the call's language in that slot, so `F1` in Spanish is a Spanish speaker and `F1`
9
+ * in English an English one.
10
10
  *
11
- * The bare `F1`–`M5` values are the Versa 1.x names. They still work: the server maps them onto
12
- * the matching Versa 2.0 voice, keeping the language family of the call. Prefer the explicit names
13
- * in new code.
11
+ * `EN_*` pin the English speakers regardless of language. `ESCA_*` is the retired Spanish/Catalan pack
12
+ * (its recordings measure as noisy in the top octave); do not use it in new code — `F1`–`M5` with
13
+ * `language: 'es'` gives the vetted Spanish voices.
14
14
  *
15
15
  * This enum is a convenience, not the full set — a cloned voice's id (`clone_…`) is also a valid
16
16
  * `voice`, which is why the config fields accept any string.
@@ -26,35 +26,35 @@ declare enum VoiceStyle {
26
26
  EN_M3 = "en_m3",
27
27
  EN_M4 = "en_m4",
28
28
  EN_M5 = "en_m5",
29
+ /** @deprecated Retired pack; use F1 with language "es". */
29
30
  ESCA_F1 = "esca_f1",
31
+ /** @deprecated Retired pack; use F2 with language "es". */
30
32
  ESCA_F2 = "esca_f2",
33
+ /** @deprecated Retired pack; use F3 with language "es". */
31
34
  ESCA_F3 = "esca_f3",
35
+ /** @deprecated Retired pack; use F4 with language "es". */
32
36
  ESCA_F4 = "esca_f4",
37
+ /** @deprecated Retired pack; use F5 with language "es". */
33
38
  ESCA_F5 = "esca_f5",
39
+ /** @deprecated Retired pack; use M1 with language "es". */
34
40
  ESCA_M1 = "esca_m1",
41
+ /** @deprecated Retired pack; use M2 with language "es". */
35
42
  ESCA_M2 = "esca_m2",
43
+ /** @deprecated Retired pack; use M3 with language "es". */
36
44
  ESCA_M3 = "esca_m3",
45
+ /** @deprecated Retired pack; use M4 with language "es". */
37
46
  ESCA_M4 = "esca_m4",
47
+ /** @deprecated Retired pack; use M5 with language "es". */
38
48
  ESCA_M5 = "esca_m5",
39
- /** @deprecated Versa 1.x name, mapped server-side. Use EN_F1 / ESCA_F1. */
40
49
  F1 = "F1",
41
- /** @deprecated Versa 1.x name, mapped server-side. */
42
50
  F2 = "F2",
43
- /** @deprecated Versa 1.x name, mapped server-side. */
44
51
  F3 = "F3",
45
- /** @deprecated Versa 1.x name, mapped server-side. */
46
52
  F4 = "F4",
47
- /** @deprecated Versa 1.x name, mapped server-side. */
48
53
  F5 = "F5",
49
- /** @deprecated Versa 1.x name, mapped server-side. */
50
54
  M1 = "M1",
51
- /** @deprecated Versa 1.x name, mapped server-side. */
52
55
  M2 = "M2",
53
- /** @deprecated Versa 1.x name, mapped server-side. */
54
56
  M3 = "M3",
55
- /** @deprecated Versa 1.x name, mapped server-side. */
56
57
  M4 = "M4",
57
- /** @deprecated Versa 1.x name, mapped server-side. */
58
58
  M5 = "M5"
59
59
  }
60
60
  /**
@@ -107,7 +107,14 @@ declare const DEFAULT_URLS: {
107
107
  * SDK Configuration interface
108
108
  */
109
109
  interface LokutorConfig {
110
- apiKey: string;
110
+ /** An API key, or a short-lived wst_ ticket from POST /ws-ticket. Optional when `auth` is given. */
111
+ apiKey?: string;
112
+ /**
113
+ * Called before every connect (and reconnect) for the credential to use. In a browser, have it ask
114
+ * your backend for a fresh ticket (POST https://api.lokutor.com/ws-ticket with your API key) so the
115
+ * key itself never reaches the page; a ticket lasts 10 minutes, so reconnects need a new one.
116
+ */
117
+ auth?: () => string | Promise<string>;
111
118
  agentId?: string;
112
119
  onTranscription?: (text: string) => void;
113
120
  onResponse?: (text: string) => void;
@@ -285,11 +292,13 @@ interface ToolDefinition {
285
292
  interface ToolCall {
286
293
  name: string;
287
294
  arguments: string;
295
+ /** Pass to submitToolResult(call_id, result). */
296
+ call_id: string;
288
297
  }
289
298
  /**
290
299
  * Error code enum matching the backend API error catalog
291
300
  */
292
- type ErrorCode = 'auth.missing_key' | 'auth.invalid_key' | 'auth.rate_limited' | 'auth.time_limited' | 'validation.invalid_voice' | 'validation.invalid_language' | 'validation.text_too_long' | 'validation.speed_out_of_range' | 'validation.steps_out_of_range' | 'validation.invalid_request_format' | 'tts.synthesis_failed' | 'tts.voice_unavailable' | 'tts.model_not_found' | 'tts.session_limit_reached' | 'stt.not_configured' | 'stt.stream_create_failed' | 'stt.language_not_supported' | 'agent.session_failed' | 'agent.provider_error' | 'session.ended' | 'audio.microphone_unavailable' | 'internal.error' | 'internal.timeout' | 'internal.cancelled' | 'ws.close';
301
+ type ErrorCode = 'auth.missing_key' | 'auth.invalid_key' | 'auth.rate_limited' | 'auth.time_limited' | 'validation.invalid_voice' | 'validation.invalid_language' | 'validation.text_too_long' | 'validation.speed_out_of_range' | 'validation.steps_out_of_range' | 'validation.invalid_request_format' | 'tts.synthesis_failed' | 'tts.voice_unavailable' | 'tts.model_not_found' | 'tts.session_limit_reached' | 'stt.not_configured' | 'stt.stream_create_failed' | 'stt.language_not_supported' | 'agent.session_failed' | 'agent.provider_error' | 'agent.not_found' | 'billing.insufficient_balance' | 'billing.topup_required' | 'billing.limit_reached' | 'billing.account_disputed' | 'session.ended' | 'audio.microphone_unavailable' | 'internal.error' | 'internal.timeout' | 'internal.cancelled' | 'ws.close';
293
302
  /**
294
303
  * Typed error class for all Lokutor SDK errors.
295
304
  * Includes the backend error code, human-readable message,
@@ -369,8 +378,12 @@ declare class VoiceAgentClient {
369
378
  private serverPlaybackRate;
370
379
  private serverInputRate;
371
380
  private serverUrl;
381
+ private authProvider?;
382
+ private voiceExplicit;
383
+ private languageExplicit;
372
384
  constructor(config: LokutorConfig & {
373
- prompt: string;
385
+ /** Required unless agentId is set (a dashboard agent brings its own prompt). */
386
+ prompt?: string;
374
387
  voice?: VoiceId;
375
388
  language?: Language;
376
389
  visemes?: boolean;
@@ -466,8 +479,8 @@ declare class VoiceAgentClient {
466
479
  */
467
480
  static fetchStatus(baseUrl?: string): Promise<ServerStatus>;
468
481
  /**
469
- * Fetch health/liveness status from the server.
470
- * No authentication required.
482
+ * Fetch readiness from the server (GET /ready: 200 once it can serve).
483
+ * No authentication required. (/health is the operator dashboard's and needs its password.)
471
484
  */
472
485
  static fetchHealth(baseUrl?: string): Promise<HealthStatus>;
473
486
  /**
package/dist/index.d.ts CHANGED
@@ -4,13 +4,13 @@
4
4
  /**
5
5
  * Built-in voices.
6
6
  *
7
- * Versa 2.0 names each voice for the language its reference speaker was recorded in: `EN_*` are
8
- * English speakers, `ESCA_*` Spanish/Catalan. That is a guide to how a voice sounds, not a
9
- * restriction — the model is zero-shot, so any voice can speak any supported language.
7
+ * Use `F1`–`F5` / `M1`–`M5` (what GET /voices lists). Each is a slot: the server resolves it to the
8
+ * native speaker of the call's language in that slot, so `F1` in Spanish is a Spanish speaker and `F1`
9
+ * in English an English one.
10
10
  *
11
- * The bare `F1`–`M5` values are the Versa 1.x names. They still work: the server maps them onto
12
- * the matching Versa 2.0 voice, keeping the language family of the call. Prefer the explicit names
13
- * in new code.
11
+ * `EN_*` pin the English speakers regardless of language. `ESCA_*` is the retired Spanish/Catalan pack
12
+ * (its recordings measure as noisy in the top octave); do not use it in new code — `F1`–`M5` with
13
+ * `language: 'es'` gives the vetted Spanish voices.
14
14
  *
15
15
  * This enum is a convenience, not the full set — a cloned voice's id (`clone_…`) is also a valid
16
16
  * `voice`, which is why the config fields accept any string.
@@ -26,35 +26,35 @@ declare enum VoiceStyle {
26
26
  EN_M3 = "en_m3",
27
27
  EN_M4 = "en_m4",
28
28
  EN_M5 = "en_m5",
29
+ /** @deprecated Retired pack; use F1 with language "es". */
29
30
  ESCA_F1 = "esca_f1",
31
+ /** @deprecated Retired pack; use F2 with language "es". */
30
32
  ESCA_F2 = "esca_f2",
33
+ /** @deprecated Retired pack; use F3 with language "es". */
31
34
  ESCA_F3 = "esca_f3",
35
+ /** @deprecated Retired pack; use F4 with language "es". */
32
36
  ESCA_F4 = "esca_f4",
37
+ /** @deprecated Retired pack; use F5 with language "es". */
33
38
  ESCA_F5 = "esca_f5",
39
+ /** @deprecated Retired pack; use M1 with language "es". */
34
40
  ESCA_M1 = "esca_m1",
41
+ /** @deprecated Retired pack; use M2 with language "es". */
35
42
  ESCA_M2 = "esca_m2",
43
+ /** @deprecated Retired pack; use M3 with language "es". */
36
44
  ESCA_M3 = "esca_m3",
45
+ /** @deprecated Retired pack; use M4 with language "es". */
37
46
  ESCA_M4 = "esca_m4",
47
+ /** @deprecated Retired pack; use M5 with language "es". */
38
48
  ESCA_M5 = "esca_m5",
39
- /** @deprecated Versa 1.x name, mapped server-side. Use EN_F1 / ESCA_F1. */
40
49
  F1 = "F1",
41
- /** @deprecated Versa 1.x name, mapped server-side. */
42
50
  F2 = "F2",
43
- /** @deprecated Versa 1.x name, mapped server-side. */
44
51
  F3 = "F3",
45
- /** @deprecated Versa 1.x name, mapped server-side. */
46
52
  F4 = "F4",
47
- /** @deprecated Versa 1.x name, mapped server-side. */
48
53
  F5 = "F5",
49
- /** @deprecated Versa 1.x name, mapped server-side. */
50
54
  M1 = "M1",
51
- /** @deprecated Versa 1.x name, mapped server-side. */
52
55
  M2 = "M2",
53
- /** @deprecated Versa 1.x name, mapped server-side. */
54
56
  M3 = "M3",
55
- /** @deprecated Versa 1.x name, mapped server-side. */
56
57
  M4 = "M4",
57
- /** @deprecated Versa 1.x name, mapped server-side. */
58
58
  M5 = "M5"
59
59
  }
60
60
  /**
@@ -107,7 +107,14 @@ declare const DEFAULT_URLS: {
107
107
  * SDK Configuration interface
108
108
  */
109
109
  interface LokutorConfig {
110
- apiKey: string;
110
+ /** An API key, or a short-lived wst_ ticket from POST /ws-ticket. Optional when `auth` is given. */
111
+ apiKey?: string;
112
+ /**
113
+ * Called before every connect (and reconnect) for the credential to use. In a browser, have it ask
114
+ * your backend for a fresh ticket (POST https://api.lokutor.com/ws-ticket with your API key) so the
115
+ * key itself never reaches the page; a ticket lasts 10 minutes, so reconnects need a new one.
116
+ */
117
+ auth?: () => string | Promise<string>;
111
118
  agentId?: string;
112
119
  onTranscription?: (text: string) => void;
113
120
  onResponse?: (text: string) => void;
@@ -285,11 +292,13 @@ interface ToolDefinition {
285
292
  interface ToolCall {
286
293
  name: string;
287
294
  arguments: string;
295
+ /** Pass to submitToolResult(call_id, result). */
296
+ call_id: string;
288
297
  }
289
298
  /**
290
299
  * Error code enum matching the backend API error catalog
291
300
  */
292
- type ErrorCode = 'auth.missing_key' | 'auth.invalid_key' | 'auth.rate_limited' | 'auth.time_limited' | 'validation.invalid_voice' | 'validation.invalid_language' | 'validation.text_too_long' | 'validation.speed_out_of_range' | 'validation.steps_out_of_range' | 'validation.invalid_request_format' | 'tts.synthesis_failed' | 'tts.voice_unavailable' | 'tts.model_not_found' | 'tts.session_limit_reached' | 'stt.not_configured' | 'stt.stream_create_failed' | 'stt.language_not_supported' | 'agent.session_failed' | 'agent.provider_error' | 'session.ended' | 'audio.microphone_unavailable' | 'internal.error' | 'internal.timeout' | 'internal.cancelled' | 'ws.close';
301
+ type ErrorCode = 'auth.missing_key' | 'auth.invalid_key' | 'auth.rate_limited' | 'auth.time_limited' | 'validation.invalid_voice' | 'validation.invalid_language' | 'validation.text_too_long' | 'validation.speed_out_of_range' | 'validation.steps_out_of_range' | 'validation.invalid_request_format' | 'tts.synthesis_failed' | 'tts.voice_unavailable' | 'tts.model_not_found' | 'tts.session_limit_reached' | 'stt.not_configured' | 'stt.stream_create_failed' | 'stt.language_not_supported' | 'agent.session_failed' | 'agent.provider_error' | 'agent.not_found' | 'billing.insufficient_balance' | 'billing.topup_required' | 'billing.limit_reached' | 'billing.account_disputed' | 'session.ended' | 'audio.microphone_unavailable' | 'internal.error' | 'internal.timeout' | 'internal.cancelled' | 'ws.close';
293
302
  /**
294
303
  * Typed error class for all Lokutor SDK errors.
295
304
  * Includes the backend error code, human-readable message,
@@ -369,8 +378,12 @@ declare class VoiceAgentClient {
369
378
  private serverPlaybackRate;
370
379
  private serverInputRate;
371
380
  private serverUrl;
381
+ private authProvider?;
382
+ private voiceExplicit;
383
+ private languageExplicit;
372
384
  constructor(config: LokutorConfig & {
373
- prompt: string;
385
+ /** Required unless agentId is set (a dashboard agent brings its own prompt). */
386
+ prompt?: string;
374
387
  voice?: VoiceId;
375
388
  language?: Language;
376
389
  visemes?: boolean;
@@ -466,8 +479,8 @@ declare class VoiceAgentClient {
466
479
  */
467
480
  static fetchStatus(baseUrl?: string): Promise<ServerStatus>;
468
481
  /**
469
- * Fetch health/liveness status from the server.
470
- * No authentication required.
482
+ * Fetch readiness from the server (GET /ready: 200 once it can serve).
483
+ * No authentication required. (/health is the operator dashboard's and needs its password.)
471
484
  */
472
485
  static fetchHealth(baseUrl?: string): Promise<HealthStatus>;
473
486
  /**