@lokutor/sdk 1.5.0 → 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 +29 -2
- package/dist/index.d.mts +70 -25
- package/dist/index.d.ts +70 -25
- package/dist/index.js +267 -106
- package/dist/index.mjs +265 -106
- package/package.json +1 -1
- package/src/browser-audio.ts +82 -13
- package/src/client.ts +185 -99
- package/src/conversational-panel.ts +61 -15
- package/src/index.ts +1 -1
- package/src/types.ts +38 -17
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**:
|
|
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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
-
|
|
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' | '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
|
|
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
|
|
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
|
/**
|
|
@@ -699,8 +712,26 @@ interface BrowserAudioConfig {
|
|
|
699
712
|
* the call goes silent in both directions. Set false if your app manages this itself.
|
|
700
713
|
*/
|
|
701
714
|
keepAwake?: boolean;
|
|
715
|
+
/**
|
|
716
|
+
* A microphone stream the page already asked for, ideally with requestMicrophone() inside the tap that
|
|
717
|
+
* started the call. startMicrophone uses it instead of calling getUserMedia again later, after the
|
|
718
|
+
* connection opens -- which iOS Safari can refuse without showing its prompt.
|
|
719
|
+
*/
|
|
720
|
+
microphone?: MediaStream | Promise<MediaStream>;
|
|
702
721
|
onInputError?: (error: Error) => void;
|
|
703
722
|
}
|
|
723
|
+
/**
|
|
724
|
+
* Ask for the microphone now, from inside the tap or click that starts a call, and return the pending stream.
|
|
725
|
+
*
|
|
726
|
+
* Call it synchronously in the gesture handler, before any await, and pass the result to BrowserAudioManager
|
|
727
|
+
* as `microphone` (ConversationalPanel does this itself). Two iOS Safari behaviours made a later request fail
|
|
728
|
+
* with no prompt at all -- the call connected, the agent spoke, and nothing the caller said was heard:
|
|
729
|
+
* - a request outside a user gesture may be refused instead of prompting;
|
|
730
|
+
* - while the page's audio session is in 'playback' mode (set by pages so audio plays through the mute
|
|
731
|
+
* switch), capture is refused outright. It is put back to 'auto', under which Safari switches to
|
|
732
|
+
* play-and-record itself when capture starts and keeps the loudspeaker.
|
|
733
|
+
*/
|
|
734
|
+
declare function requestMicrophone(constraints?: Pick<BrowserAudioConfig, 'autoGainControl' | 'echoCancellation' | 'noiseSuppression'>): Promise<MediaStream>;
|
|
704
735
|
/**
|
|
705
736
|
* Analyser configuration for audio visualization
|
|
706
737
|
*/
|
|
@@ -729,6 +760,7 @@ declare class BrowserAudioManager {
|
|
|
729
760
|
private echoCancellation;
|
|
730
761
|
private noiseSuppression;
|
|
731
762
|
private keepAwake;
|
|
763
|
+
private providedMicrophone;
|
|
732
764
|
private wakeLock;
|
|
733
765
|
private visibilityHandler;
|
|
734
766
|
private onAudioInput?;
|
|
@@ -848,7 +880,11 @@ interface ConversationalPanelConfig {
|
|
|
848
880
|
backgroundColor?: string;
|
|
849
881
|
/** API key for connecting to the voice agent */
|
|
850
882
|
apiKey: string;
|
|
851
|
-
/**
|
|
883
|
+
/**
|
|
884
|
+
* Reference to the ConvoAgent class. Its constructor options include `microphone`, a
|
|
885
|
+
* Promise<MediaStream> requested inside the Start tap (see requestMicrophone); pass it on to
|
|
886
|
+
* BrowserAudioManager as `microphone` so the call uses it instead of asking again later.
|
|
887
|
+
*/
|
|
852
888
|
ConvoAgent: any;
|
|
853
889
|
/** Reference to the SphereVisualizer class */
|
|
854
890
|
SphereVisualizer: any;
|
|
@@ -863,6 +899,9 @@ interface ConversationalPanelConfig {
|
|
|
863
899
|
/** Seconds of silence before auto-close (default 60) */
|
|
864
900
|
silenceTimeout?: number;
|
|
865
901
|
}
|
|
902
|
+
/** What to tell the caller when the microphone did not start: a DOMException from getUserMedia, or the
|
|
903
|
+
* SDK's 'audio.microphone_unavailable' error wrapping one. */
|
|
904
|
+
declare function microphoneMessage(err: any): string;
|
|
866
905
|
declare class ConversationalPanel {
|
|
867
906
|
private cfg;
|
|
868
907
|
private container;
|
|
@@ -892,6 +931,8 @@ declare class ConversationalPanel {
|
|
|
892
931
|
private countdownEl;
|
|
893
932
|
private noteEl;
|
|
894
933
|
private countdownTicker;
|
|
934
|
+
private microphone;
|
|
935
|
+
private micError;
|
|
895
936
|
onTranscription?: (text: string) => void;
|
|
896
937
|
onResponse?: (text: string) => void;
|
|
897
938
|
onStart?: () => void;
|
|
@@ -907,8 +948,12 @@ declare class ConversationalPanel {
|
|
|
907
948
|
constructor(cfg: ConversationalPanelConfig);
|
|
908
949
|
private buildDOM;
|
|
909
950
|
private esc;
|
|
910
|
-
/**
|
|
911
|
-
|
|
951
|
+
/**
|
|
952
|
+
* Start the conversation (called when user clicks "Start Conversation" or externally). Call it from
|
|
953
|
+
* inside the click or tap, before any await: it asks for the microphone there. Resolves true once the
|
|
954
|
+
* call is live, false when it could not start (the reason is shown, and passed to onError).
|
|
955
|
+
*/
|
|
956
|
+
start(): Promise<boolean>;
|
|
912
957
|
/** Stop / disconnect the conversation */
|
|
913
958
|
stop(): void;
|
|
914
959
|
/** Update accent color dynamically */
|
|
@@ -941,4 +986,4 @@ declare class ConversationalPanel {
|
|
|
941
986
|
private playErrorTone;
|
|
942
987
|
}
|
|
943
988
|
|
|
944
|
-
export { AUDIO_CONFIG, type AnalyserConfig, type AudioManager, type BrowserAudioConfig, BrowserAudioManager, type BrowserAudioOptions, ConversationalPanel, type ConversationalPanelConfig, DEFAULT_URLS, type ErrorCode, type HealthStatus, Language, type LanguageInfo, type LokutorConfig, LokutorError, type ModelInfo, NodeAudioManager, STTClient, type ServerConfig, type ServerStatus, type SessionLimit, SpeechToTextClient, type SpeechToTextOptions, StreamResampler, type SynthesizeOptions, TTSClient, type ToolCall, type ToolDefinition, type TranscribeOptions, type TranscribeResult, type TranscribeSegment, type Viseme, VoiceAgentClient, type VoiceAgentOptions, type VoiceId, type VoiceInfo, VoiceStyle, applyLowPassFilter, bytesToPcm16, calculateRMS, float32ToPcm16, isRetryable, normalizeAudio, pcm16ToBytes, pcm16ToFloat32, resample, resampleWithAntiAliasing, simpleConversation, simpleTTS, simpleTranscribe };
|
|
989
|
+
export { AUDIO_CONFIG, type AnalyserConfig, type AudioManager, type BrowserAudioConfig, BrowserAudioManager, type BrowserAudioOptions, ConversationalPanel, type ConversationalPanelConfig, DEFAULT_URLS, type ErrorCode, type HealthStatus, Language, type LanguageInfo, type LokutorConfig, LokutorError, type ModelInfo, NodeAudioManager, STTClient, type ServerConfig, type ServerStatus, type SessionLimit, SpeechToTextClient, type SpeechToTextOptions, StreamResampler, type SynthesizeOptions, TTSClient, type ToolCall, type ToolDefinition, type TranscribeOptions, type TranscribeResult, type TranscribeSegment, type Viseme, VoiceAgentClient, type VoiceAgentOptions, type VoiceId, type VoiceInfo, VoiceStyle, applyLowPassFilter, bytesToPcm16, calculateRMS, float32ToPcm16, isRetryable, microphoneMessage, normalizeAudio, pcm16ToBytes, pcm16ToFloat32, requestMicrophone, resample, resampleWithAntiAliasing, simpleConversation, simpleTTS, simpleTranscribe };
|
package/dist/index.d.ts
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* Built-in voices.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
-
|
|
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' | '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
|
|
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
|
|
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
|
/**
|
|
@@ -699,8 +712,26 @@ interface BrowserAudioConfig {
|
|
|
699
712
|
* the call goes silent in both directions. Set false if your app manages this itself.
|
|
700
713
|
*/
|
|
701
714
|
keepAwake?: boolean;
|
|
715
|
+
/**
|
|
716
|
+
* A microphone stream the page already asked for, ideally with requestMicrophone() inside the tap that
|
|
717
|
+
* started the call. startMicrophone uses it instead of calling getUserMedia again later, after the
|
|
718
|
+
* connection opens -- which iOS Safari can refuse without showing its prompt.
|
|
719
|
+
*/
|
|
720
|
+
microphone?: MediaStream | Promise<MediaStream>;
|
|
702
721
|
onInputError?: (error: Error) => void;
|
|
703
722
|
}
|
|
723
|
+
/**
|
|
724
|
+
* Ask for the microphone now, from inside the tap or click that starts a call, and return the pending stream.
|
|
725
|
+
*
|
|
726
|
+
* Call it synchronously in the gesture handler, before any await, and pass the result to BrowserAudioManager
|
|
727
|
+
* as `microphone` (ConversationalPanel does this itself). Two iOS Safari behaviours made a later request fail
|
|
728
|
+
* with no prompt at all -- the call connected, the agent spoke, and nothing the caller said was heard:
|
|
729
|
+
* - a request outside a user gesture may be refused instead of prompting;
|
|
730
|
+
* - while the page's audio session is in 'playback' mode (set by pages so audio plays through the mute
|
|
731
|
+
* switch), capture is refused outright. It is put back to 'auto', under which Safari switches to
|
|
732
|
+
* play-and-record itself when capture starts and keeps the loudspeaker.
|
|
733
|
+
*/
|
|
734
|
+
declare function requestMicrophone(constraints?: Pick<BrowserAudioConfig, 'autoGainControl' | 'echoCancellation' | 'noiseSuppression'>): Promise<MediaStream>;
|
|
704
735
|
/**
|
|
705
736
|
* Analyser configuration for audio visualization
|
|
706
737
|
*/
|
|
@@ -729,6 +760,7 @@ declare class BrowserAudioManager {
|
|
|
729
760
|
private echoCancellation;
|
|
730
761
|
private noiseSuppression;
|
|
731
762
|
private keepAwake;
|
|
763
|
+
private providedMicrophone;
|
|
732
764
|
private wakeLock;
|
|
733
765
|
private visibilityHandler;
|
|
734
766
|
private onAudioInput?;
|
|
@@ -848,7 +880,11 @@ interface ConversationalPanelConfig {
|
|
|
848
880
|
backgroundColor?: string;
|
|
849
881
|
/** API key for connecting to the voice agent */
|
|
850
882
|
apiKey: string;
|
|
851
|
-
/**
|
|
883
|
+
/**
|
|
884
|
+
* Reference to the ConvoAgent class. Its constructor options include `microphone`, a
|
|
885
|
+
* Promise<MediaStream> requested inside the Start tap (see requestMicrophone); pass it on to
|
|
886
|
+
* BrowserAudioManager as `microphone` so the call uses it instead of asking again later.
|
|
887
|
+
*/
|
|
852
888
|
ConvoAgent: any;
|
|
853
889
|
/** Reference to the SphereVisualizer class */
|
|
854
890
|
SphereVisualizer: any;
|
|
@@ -863,6 +899,9 @@ interface ConversationalPanelConfig {
|
|
|
863
899
|
/** Seconds of silence before auto-close (default 60) */
|
|
864
900
|
silenceTimeout?: number;
|
|
865
901
|
}
|
|
902
|
+
/** What to tell the caller when the microphone did not start: a DOMException from getUserMedia, or the
|
|
903
|
+
* SDK's 'audio.microphone_unavailable' error wrapping one. */
|
|
904
|
+
declare function microphoneMessage(err: any): string;
|
|
866
905
|
declare class ConversationalPanel {
|
|
867
906
|
private cfg;
|
|
868
907
|
private container;
|
|
@@ -892,6 +931,8 @@ declare class ConversationalPanel {
|
|
|
892
931
|
private countdownEl;
|
|
893
932
|
private noteEl;
|
|
894
933
|
private countdownTicker;
|
|
934
|
+
private microphone;
|
|
935
|
+
private micError;
|
|
895
936
|
onTranscription?: (text: string) => void;
|
|
896
937
|
onResponse?: (text: string) => void;
|
|
897
938
|
onStart?: () => void;
|
|
@@ -907,8 +948,12 @@ declare class ConversationalPanel {
|
|
|
907
948
|
constructor(cfg: ConversationalPanelConfig);
|
|
908
949
|
private buildDOM;
|
|
909
950
|
private esc;
|
|
910
|
-
/**
|
|
911
|
-
|
|
951
|
+
/**
|
|
952
|
+
* Start the conversation (called when user clicks "Start Conversation" or externally). Call it from
|
|
953
|
+
* inside the click or tap, before any await: it asks for the microphone there. Resolves true once the
|
|
954
|
+
* call is live, false when it could not start (the reason is shown, and passed to onError).
|
|
955
|
+
*/
|
|
956
|
+
start(): Promise<boolean>;
|
|
912
957
|
/** Stop / disconnect the conversation */
|
|
913
958
|
stop(): void;
|
|
914
959
|
/** Update accent color dynamically */
|
|
@@ -941,4 +986,4 @@ declare class ConversationalPanel {
|
|
|
941
986
|
private playErrorTone;
|
|
942
987
|
}
|
|
943
988
|
|
|
944
|
-
export { AUDIO_CONFIG, type AnalyserConfig, type AudioManager, type BrowserAudioConfig, BrowserAudioManager, type BrowserAudioOptions, ConversationalPanel, type ConversationalPanelConfig, DEFAULT_URLS, type ErrorCode, type HealthStatus, Language, type LanguageInfo, type LokutorConfig, LokutorError, type ModelInfo, NodeAudioManager, STTClient, type ServerConfig, type ServerStatus, type SessionLimit, SpeechToTextClient, type SpeechToTextOptions, StreamResampler, type SynthesizeOptions, TTSClient, type ToolCall, type ToolDefinition, type TranscribeOptions, type TranscribeResult, type TranscribeSegment, type Viseme, VoiceAgentClient, type VoiceAgentOptions, type VoiceId, type VoiceInfo, VoiceStyle, applyLowPassFilter, bytesToPcm16, calculateRMS, float32ToPcm16, isRetryable, normalizeAudio, pcm16ToBytes, pcm16ToFloat32, resample, resampleWithAntiAliasing, simpleConversation, simpleTTS, simpleTranscribe };
|
|
989
|
+
export { AUDIO_CONFIG, type AnalyserConfig, type AudioManager, type BrowserAudioConfig, BrowserAudioManager, type BrowserAudioOptions, ConversationalPanel, type ConversationalPanelConfig, DEFAULT_URLS, type ErrorCode, type HealthStatus, Language, type LanguageInfo, type LokutorConfig, LokutorError, type ModelInfo, NodeAudioManager, STTClient, type ServerConfig, type ServerStatus, type SessionLimit, SpeechToTextClient, type SpeechToTextOptions, StreamResampler, type SynthesizeOptions, TTSClient, type ToolCall, type ToolDefinition, type TranscribeOptions, type TranscribeResult, type TranscribeSegment, type Viseme, VoiceAgentClient, type VoiceAgentOptions, type VoiceId, type VoiceInfo, VoiceStyle, applyLowPassFilter, bytesToPcm16, calculateRMS, float32ToPcm16, isRetryable, microphoneMessage, normalizeAudio, pcm16ToBytes, pcm16ToFloat32, requestMicrophone, resample, resampleWithAntiAliasing, simpleConversation, simpleTTS, simpleTranscribe };
|