@liforma/client 0.3.0 → 0.4.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 +17 -0
- package/README.md +14 -1
- package/dist/byo/audioCapture.d.ts +24 -0
- package/dist/byo/audioCapture.d.ts.map +1 -0
- package/dist/byo/index.d.ts +8 -0
- package/dist/byo/index.d.ts.map +1 -0
- package/dist/byo/index.js +307 -0
- package/dist/byo/index.js.map +1 -0
- package/dist/byo/mic.d.ts +27 -0
- package/dist/byo/mic.d.ts.map +1 -0
- package/dist/byo/pcm.d.ts +33 -0
- package/dist/byo/pcm.d.ts.map +1 -0
- package/dist/byo/pcmCarry.d.ts +17 -0
- package/dist/byo/pcmCarry.d.ts.map +1 -0
- package/dist/byo/resample.d.ts +18 -0
- package/dist/byo/resample.d.ts.map +1 -0
- package/dist/byo/turn.d.ts +39 -0
- package/dist/byo/turn.d.ts.map +1 -0
- package/dist/byo/types.d.ts +74 -0
- package/dist/byo/types.d.ts.map +1 -0
- package/dist/deepgram/connectDeepgramAgent.d.ts +51 -0
- package/dist/deepgram/connectDeepgramAgent.d.ts.map +1 -0
- package/dist/deepgram/deepgramAgentMessages.d.ts +31 -0
- package/dist/deepgram/deepgramAgentMessages.d.ts.map +1 -0
- package/dist/deepgram/index.d.ts +4 -0
- package/dist/deepgram/index.d.ts.map +1 -0
- package/dist/deepgram/index.js +142 -0
- package/dist/deepgram/index.js.map +1 -0
- package/dist/elevenlabs/connectElevenLabsAgent.d.ts +23 -0
- package/dist/elevenlabs/connectElevenLabsAgent.d.ts.map +1 -0
- package/dist/elevenlabs/index.d.ts +4 -0
- package/dist/elevenlabs/index.d.ts.map +1 -0
- package/dist/elevenlabs/index.js +89 -0
- package/dist/elevenlabs/index.js.map +1 -0
- package/dist/google/connectGeminiLive.d.ts +34 -0
- package/dist/google/connectGeminiLive.d.ts.map +1 -0
- package/dist/google/index.d.ts +4 -0
- package/dist/google/index.d.ts.map +1 -0
- package/dist/google/index.js +98 -0
- package/dist/google/index.js.map +1 -0
- package/dist/livekit/connectLiveKitAgent.d.ts +46 -0
- package/dist/livekit/connectLiveKitAgent.d.ts.map +1 -0
- package/dist/livekit/index.d.ts +3 -0
- package/dist/livekit/index.d.ts.map +1 -0
- package/dist/livekit/index.js +113 -0
- package/dist/livekit/index.js.map +1 -0
- package/dist/next/byo/audioCapture.d.ts +24 -0
- package/dist/next/byo/audioCapture.d.ts.map +1 -0
- package/dist/next/byo/pcm.d.ts +33 -0
- package/dist/next/byo/pcm.d.ts.map +1 -0
- package/dist/next/byo/resample.d.ts +18 -0
- package/dist/next/byo/resample.d.ts.map +1 -0
- package/dist/next/index.js +675 -474
- package/dist/next/index.js.map +1 -1
- package/dist/next/v2/embed/iframeHost.d.ts +16 -1
- package/dist/next/v2/embed/iframeHost.d.ts.map +1 -1
- package/dist/next/v2/embed/protocol.d.ts +41 -1
- package/dist/next/v2/embed/protocol.d.ts.map +1 -1
- package/dist/next/v2/experience.d.ts +14 -2
- package/dist/next/v2/experience.d.ts.map +1 -1
- package/dist/next/v2/speech/captureMediaStreamTrack.d.ts +1 -1
- package/dist/next/v2/speech/captureMediaStreamTrack.d.ts.map +1 -1
- package/dist/next/v2/speech/speechController.d.ts.map +1 -1
- package/dist/next/v2/speech/types.d.ts +13 -0
- package/dist/next/v2/speech/types.d.ts.map +1 -1
- package/dist/next/widget/mountExperienceWidget.d.ts.map +1 -1
- package/dist/next/widget/styles.d.ts +1 -1
- package/dist/next/widget/styles.d.ts.map +1 -1
- package/dist/npm/byo/audioCapture.d.ts +24 -0
- package/dist/npm/byo/audioCapture.d.ts.map +1 -0
- package/dist/npm/byo/pcm.d.ts +33 -0
- package/dist/npm/byo/pcm.d.ts.map +1 -0
- package/dist/npm/byo/resample.d.ts +18 -0
- package/dist/npm/byo/resample.d.ts.map +1 -0
- package/dist/npm/index.js +700 -499
- package/dist/npm/index.js.map +1 -1
- package/dist/npm/v2/embed/iframeHost.d.ts +16 -1
- package/dist/npm/v2/embed/iframeHost.d.ts.map +1 -1
- package/dist/npm/v2/embed/protocol.d.ts +41 -1
- package/dist/npm/v2/embed/protocol.d.ts.map +1 -1
- package/dist/npm/v2/experience.d.ts +14 -2
- package/dist/npm/v2/experience.d.ts.map +1 -1
- package/dist/npm/v2/speech/captureMediaStreamTrack.d.ts +1 -1
- package/dist/npm/v2/speech/captureMediaStreamTrack.d.ts.map +1 -1
- package/dist/npm/v2/speech/speechController.d.ts.map +1 -1
- package/dist/npm/v2/speech/types.d.ts +13 -0
- package/dist/npm/v2/speech/types.d.ts.map +1 -1
- package/dist/npm/widget/mountExperienceWidget.d.ts.map +1 -1
- package/dist/npm/widget/styles.d.ts +1 -1
- package/dist/npm/widget/styles.d.ts.map +1 -1
- package/dist/openai/connectOpenAiRealtime.d.ts +41 -0
- package/dist/openai/connectOpenAiRealtime.d.ts.map +1 -0
- package/dist/openai/connectOpenAiRealtimeWebRtc.d.ts +37 -0
- package/dist/openai/connectOpenAiRealtimeWebRtc.d.ts.map +1 -0
- package/dist/openai/index.d.ts +5 -0
- package/dist/openai/index.d.ts.map +1 -0
- package/dist/openai/index.js +282 -0
- package/dist/openai/index.js.map +1 -0
- package/dist/react/byo/audioCapture.d.ts +24 -0
- package/dist/react/byo/audioCapture.d.ts.map +1 -0
- package/dist/react/byo/pcm.d.ts +33 -0
- package/dist/react/byo/pcm.d.ts.map +1 -0
- package/dist/react/byo/resample.d.ts +18 -0
- package/dist/react/byo/resample.d.ts.map +1 -0
- package/dist/react/index.js +696 -495
- package/dist/react/index.js.map +1 -1
- package/dist/react/v2/embed/iframeHost.d.ts +16 -1
- package/dist/react/v2/embed/iframeHost.d.ts.map +1 -1
- package/dist/react/v2/embed/protocol.d.ts +41 -1
- package/dist/react/v2/embed/protocol.d.ts.map +1 -1
- package/dist/react/v2/experience.d.ts +14 -2
- package/dist/react/v2/experience.d.ts.map +1 -1
- package/dist/react/v2/speech/captureMediaStreamTrack.d.ts +1 -1
- package/dist/react/v2/speech/captureMediaStreamTrack.d.ts.map +1 -1
- package/dist/react/v2/speech/speechController.d.ts.map +1 -1
- package/dist/react/v2/speech/types.d.ts +13 -0
- package/dist/react/v2/speech/types.d.ts.map +1 -1
- package/dist/react/widget/mountExperienceWidget.d.ts.map +1 -1
- package/dist/react/widget/styles.d.ts +1 -1
- package/dist/react/widget/styles.d.ts.map +1 -1
- package/dist/sdk/v2/client.js +37 -10
- package/dist/sdk/v2/client.js.map +1 -1
- package/package.json +55 -7
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared duck-types for BYO (bring-your-own-voice) vendor bridges.
|
|
3
|
+
* Compatible with `ExperienceHandle['speech']` from `@liforma/client/react` / `/svelte`.
|
|
4
|
+
*/
|
|
5
|
+
export type ByoSpeechUtterance = {
|
|
6
|
+
write(chunk: ArrayBuffer | ArrayBufferView): Promise<unknown>;
|
|
7
|
+
setTranscript(text: string): Promise<void>;
|
|
8
|
+
close(options?: {
|
|
9
|
+
transcript?: string;
|
|
10
|
+
history?: 'none';
|
|
11
|
+
}): Promise<unknown>;
|
|
12
|
+
cancel(): Promise<unknown>;
|
|
13
|
+
/** Settles when the utterance completes, cancels, or errors (track bridges await this). */
|
|
14
|
+
readonly result?: Promise<unknown>;
|
|
15
|
+
};
|
|
16
|
+
export type ByoPcmSpeechController = {
|
|
17
|
+
createUtterance(options: {
|
|
18
|
+
/** Required for PCM bridges; omit when `track` is set (defaults apply). */
|
|
19
|
+
format?: {
|
|
20
|
+
encoding: 'pcm_s16le';
|
|
21
|
+
sampleRate: number;
|
|
22
|
+
channels: 1;
|
|
23
|
+
};
|
|
24
|
+
/** Live remote audio (WebRTC / similar) — host pipes until the track ends. */
|
|
25
|
+
track?: MediaStreamTrack;
|
|
26
|
+
/** Used with `track` when `format` is omitted (default 24000). */
|
|
27
|
+
sampleRate?: number;
|
|
28
|
+
queue: 'replace-active' | 'replace-all';
|
|
29
|
+
transcript?: string;
|
|
30
|
+
}): ByoSpeechUtterance;
|
|
31
|
+
interrupt(options?: {
|
|
32
|
+
readonly scope?: 'active' | 'all';
|
|
33
|
+
}): Promise<unknown>;
|
|
34
|
+
};
|
|
35
|
+
/** Experience surface for PCM turn helpers (ElevenLabs, OpenAI, Deepgram, Gemini). */
|
|
36
|
+
export type ByoPcmExperience = {
|
|
37
|
+
readonly speech: ByoPcmSpeechController;
|
|
38
|
+
};
|
|
39
|
+
export type ByoPlaySpeechController = {
|
|
40
|
+
play(options: {
|
|
41
|
+
audio: {
|
|
42
|
+
track: MediaStreamTrack;
|
|
43
|
+
sampleRate?: number;
|
|
44
|
+
};
|
|
45
|
+
queue?: 'replace-active' | 'replace-all' | 'append';
|
|
46
|
+
}): Promise<unknown>;
|
|
47
|
+
interrupt(options?: {
|
|
48
|
+
readonly scope?: 'active' | 'all';
|
|
49
|
+
}): Promise<unknown>;
|
|
50
|
+
};
|
|
51
|
+
/** Experience surface for MediaStreamTrack helpers (LiveKit, WebRTC). */
|
|
52
|
+
export type ByoPlayExperience = {
|
|
53
|
+
readonly speech: ByoPlaySpeechController;
|
|
54
|
+
};
|
|
55
|
+
export type ByoConnectCallbacks = {
|
|
56
|
+
readonly onLog?: (line: string, kind?: 'info' | 'warn') => void;
|
|
57
|
+
readonly onDisconnect?: () => void;
|
|
58
|
+
readonly onError?: (message: string) => void;
|
|
59
|
+
};
|
|
60
|
+
/** Mic capture options shared by OpenAI / Deepgram / Gemini helpers. */
|
|
61
|
+
export type ByoMicCaptureOptions = {
|
|
62
|
+
/**
|
|
63
|
+
* When true (default), capture the host mic and stream PCM to the vendor.
|
|
64
|
+
* Ignored when `mediaStream` is provided.
|
|
65
|
+
*/
|
|
66
|
+
readonly captureMic?: boolean;
|
|
67
|
+
/** Use an existing MediaStream instead of calling getUserMedia. */
|
|
68
|
+
readonly mediaStream?: MediaStream;
|
|
69
|
+
};
|
|
70
|
+
export type ByoBridge = {
|
|
71
|
+
isConnected(): boolean;
|
|
72
|
+
end(): Promise<void>;
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/byo/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,kBAAkB,GAAG;IAChC,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,KAAK,CAAC,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7E,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3B,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACpC,eAAe,CAAC,OAAO,EAAE;QACxB,2EAA2E;QAC3E,MAAM,CAAC,EAAE;YAAE,QAAQ,EAAE,WAAW,CAAC;YAAC,UAAU,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,CAAC,CAAA;SAAE,CAAC;QACpE,8EAA8E;QAC9E,KAAK,CAAC,EAAE,gBAAgB,CAAC;QACzB,kEAAkE;QAClE,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,gBAAgB,GAAG,aAAa,CAAC;QACxC,UAAU,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,kBAAkB,CAAC;IACvB,SAAS,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7E,CAAC;AAEF,sFAAsF;AACtF,MAAM,MAAM,gBAAgB,GAAG;IAC9B,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACrC,IAAI,CAAC,OAAO,EAAE;QACb,KAAK,EAAE;YAAE,KAAK,EAAE,gBAAgB,CAAC;YAAC,UAAU,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACxD,KAAK,CAAC,EAAE,gBAAgB,GAAG,aAAa,GAAG,QAAQ,CAAC;KACpD,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrB,SAAS,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7E,CAAC;AAEF,yEAAyE;AACzE,MAAM,MAAM,iBAAiB,GAAG;IAC/B,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C,CAAC;AAEF,wEAAwE;AACxE,MAAM,MAAM,oBAAoB,GAAG;IAClC;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,mEAAmE;IACnE,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACvB,WAAW,IAAI,OAAO,CAAC;IACvB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACrB,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deepgram Voice Agent → Liforma BYO voice helper.
|
|
3
|
+
*
|
|
4
|
+
* Mount a Liforma Experience (typically `mode="presenter"`, `speechInputMode="off"`),
|
|
5
|
+
* wait until the player has started (audio unlocked), then call this helper with a
|
|
6
|
+
* same-origin WebSocket proxy URL (browsers cannot set Authorization on WebSocket).
|
|
7
|
+
*
|
|
8
|
+
* Docs: https://docs.liforma.ai/avatar-experiences/bring-your-own-voice/deepgram
|
|
9
|
+
*/
|
|
10
|
+
import { type ByoBridge, type ByoConnectCallbacks, type ByoMicCaptureOptions, type ByoPcmExperience } from '@liforma/client/byo';
|
|
11
|
+
export type DeepgramAgentExperience = ByoPcmExperience;
|
|
12
|
+
/** Default Deepgram Voice Agent output rate for this helper. */
|
|
13
|
+
export declare const DEEPGRAM_AGENT_OUTPUT_RATE = 24000;
|
|
14
|
+
/** Default mic PCM rate streamed to Deepgram input. */
|
|
15
|
+
export declare const DEEPGRAM_AGENT_INPUT_RATE = 16000;
|
|
16
|
+
export type DeepgramAgentSettings = {
|
|
17
|
+
readonly type: 'Settings';
|
|
18
|
+
readonly audio: {
|
|
19
|
+
readonly input: {
|
|
20
|
+
readonly encoding: 'linear16';
|
|
21
|
+
readonly sample_rate: number;
|
|
22
|
+
};
|
|
23
|
+
readonly output: {
|
|
24
|
+
readonly encoding: 'linear16';
|
|
25
|
+
readonly sample_rate: number;
|
|
26
|
+
readonly container: 'none';
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
readonly agent: Record<string, unknown>;
|
|
30
|
+
};
|
|
31
|
+
export type ConnectDeepgramAgentOptions = ByoConnectCallbacks & ByoMicCaptureOptions & {
|
|
32
|
+
/**
|
|
33
|
+
* Same-origin (or CORS-friendly) WebSocket URL that proxies to
|
|
34
|
+
* `wss://agent.deepgram.com/v1/agent/converse` with your Deepgram API key.
|
|
35
|
+
*/
|
|
36
|
+
readonly proxyUrl: string;
|
|
37
|
+
/**
|
|
38
|
+
* Agent listen / think / speak config. Audio I/O formats are enforced by the helper
|
|
39
|
+
* (`container: 'none'`, linear16) so lipsync gets headerless PCM.
|
|
40
|
+
*/
|
|
41
|
+
readonly agent?: Record<string, unknown>;
|
|
42
|
+
readonly inputSampleRate?: number;
|
|
43
|
+
readonly outputSampleRate?: number;
|
|
44
|
+
};
|
|
45
|
+
export type DeepgramAgentBridge = ByoBridge;
|
|
46
|
+
/**
|
|
47
|
+
* Start a Deepgram Voice Agent session (via your proxy) and pipe agent PCM +
|
|
48
|
+
* transcript into `experience.speech.createUtterance` so the Liforma avatar talks.
|
|
49
|
+
*/
|
|
50
|
+
export declare function connectDeepgramAgent(experience: DeepgramAgentExperience, options: ConnectDeepgramAgentOptions): Promise<DeepgramAgentBridge>;
|
|
51
|
+
//# sourceMappingURL=connectDeepgramAgent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connectDeepgramAgent.d.ts","sourceRoot":"","sources":["../../src/deepgram/connectDeepgramAgent.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAIN,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EAErB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC;AAEvD,gEAAgE;AAChE,eAAO,MAAM,0BAA0B,QAAS,CAAC;AAEjD,uDAAuD;AACvD,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD,MAAM,MAAM,qBAAqB,GAAG;IACnC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE;QACf,QAAQ,CAAC,KAAK,EAAE;YAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;YAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;SAAE,CAAC;QAChF,QAAQ,CAAC,MAAM,EAAE;YAChB,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;YAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;YAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;SAC3B,CAAC;KACF,CAAC;IACF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG,mBAAmB,GAC5D,oBAAoB,GAAG;IACtB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACnC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,SAAS,CAAC;AAe5C;;;GAGG;AACH,wBAAsB,oBAAoB,CACzC,UAAU,EAAE,uBAAuB,EACnC,OAAO,EAAE,2BAA2B,GAClC,OAAO,CAAC,mBAAmB,CAAC,CA2K9B"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure Deepgram Voice Agent JSON → turn-session mapping (unit-tested).
|
|
3
|
+
*/
|
|
4
|
+
export type DeepgramTurnSink = {
|
|
5
|
+
setTranscript(text: string, mode?: 'replace' | 'append'): void;
|
|
6
|
+
bargeIn(): void;
|
|
7
|
+
closeActiveTurn(): Promise<void> | void;
|
|
8
|
+
};
|
|
9
|
+
export type DeepgramAgentJsonMessage = {
|
|
10
|
+
readonly type?: string;
|
|
11
|
+
readonly role?: string;
|
|
12
|
+
readonly content?: string;
|
|
13
|
+
readonly description?: string;
|
|
14
|
+
readonly message?: string;
|
|
15
|
+
};
|
|
16
|
+
export type HandleDeepgramAgentJsonResult = {
|
|
17
|
+
readonly kind: 'ignore';
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: 'welcome';
|
|
20
|
+
} | {
|
|
21
|
+
readonly kind: 'settings-applied';
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: 'error';
|
|
24
|
+
readonly message: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Apply a Deepgram Voice Agent JSON server message to a turn sink.
|
|
28
|
+
* Returns side-effect hints for the WebSocket layer (Settings / errors).
|
|
29
|
+
*/
|
|
30
|
+
export declare function handleDeepgramAgentJson(msg: DeepgramAgentJsonMessage, turns: DeepgramTurnSink): HandleDeepgramAgentJsonResult;
|
|
31
|
+
//# sourceMappingURL=deepgramAgentMessages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deepgramAgentMessages.d.ts","sourceRoot":"","sources":["../../src/deepgram/deepgramAgentMessages.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,gBAAgB,GAAG;IAC9B,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC;IAC/D,OAAO,IAAI,IAAI,CAAC;IAChB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACtC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,6BAA6B,GACtC;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAA;CAAE,GACrC;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD;;;GAGG;AACH,wBAAgB,uBAAuB,CACtC,GAAG,EAAE,wBAAwB,EAC7B,KAAK,EAAE,gBAAgB,GACrB,6BAA6B,CAgC/B"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { connectDeepgramAgent, DEEPGRAM_AGENT_INPUT_RATE, DEEPGRAM_AGENT_OUTPUT_RATE, type ConnectDeepgramAgentOptions, type DeepgramAgentBridge, type DeepgramAgentExperience, type DeepgramAgentSettings } from './connectDeepgramAgent';
|
|
2
|
+
export type { ByoPcmSpeechController as DeepgramSpeechController, ByoSpeechUtterance as DeepgramSpeechUtterance } from '@liforma/client/byo';
|
|
3
|
+
export { MAX_PCM_CHUNK_BYTES, floatToPcm16Le, resampleFloat32, writePcmInChunks } from '@liforma/client/byo';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/deepgram/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,oBAAoB,EACpB,yBAAyB,EACzB,0BAA0B,EAC1B,KAAK,2BAA2B,EAChC,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,MAAM,wBAAwB,CAAC;AAEhC,YAAY,EACX,sBAAsB,IAAI,wBAAwB,EAClD,kBAAkB,IAAI,uBAAuB,EAC7C,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACN,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { MAX_PCM_CHUNK_BYTES as e, createTurnSession as t, floatToPcm16Le as n, resampleFloat32 as r, shouldCaptureMic as i, startPcmMic as a, writePcmInChunks as o } from "@liforma/client/byo";
|
|
2
|
+
//#region src/deepgram/deepgramAgentMessages.ts
|
|
3
|
+
function s(e, t) {
|
|
4
|
+
switch (e.type) {
|
|
5
|
+
case "Welcome": return { kind: "welcome" };
|
|
6
|
+
case "SettingsApplied": return { kind: "settings-applied" };
|
|
7
|
+
case "ConversationText": return e.role === "assistant" && typeof e.content == "string" && e.content && t.setTranscript(e.content, "replace"), { kind: "ignore" };
|
|
8
|
+
case "UserStartedSpeaking": return t.bargeIn(), { kind: "ignore" };
|
|
9
|
+
case "AgentAudioDone": return t.closeActiveTurn(), { kind: "ignore" };
|
|
10
|
+
case "Error": return {
|
|
11
|
+
kind: "error",
|
|
12
|
+
message: String(e.description ?? e.message ?? "Deepgram Voice Agent error")
|
|
13
|
+
};
|
|
14
|
+
default: return { kind: "ignore" };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/deepgram/connectDeepgramAgent.ts
|
|
19
|
+
var c = 24e3, l = 16e3, u = {
|
|
20
|
+
language: "en",
|
|
21
|
+
listen: { provider: {
|
|
22
|
+
type: "deepgram",
|
|
23
|
+
model: "nova-3"
|
|
24
|
+
} },
|
|
25
|
+
think: { provider: {
|
|
26
|
+
type: "open_ai",
|
|
27
|
+
model: "gpt-4o-mini"
|
|
28
|
+
} },
|
|
29
|
+
speak: { provider: {
|
|
30
|
+
type: "deepgram",
|
|
31
|
+
model: "aura-2-thalia-en"
|
|
32
|
+
} }
|
|
33
|
+
};
|
|
34
|
+
async function d(e, n) {
|
|
35
|
+
let { proxyUrl: r, agent: o = u, inputSampleRate: d = l, outputSampleRate: f = c, captureMic: p = !0, mediaStream: m, onLog: h, onDisconnect: g, onError: _ } = n, v = r.trim();
|
|
36
|
+
if (!v) throw Error("Pass proxyUrl from your Deepgram Voice Agent proxy");
|
|
37
|
+
let y = (e, t = "info") => h?.(e, t), b = null, x = !1, S = !1, C = null, w = t(e, {
|
|
38
|
+
sampleRate: f,
|
|
39
|
+
isEnded: () => S,
|
|
40
|
+
canWrite: () => x,
|
|
41
|
+
onWriteError: (e) => _?.(e instanceof Error ? e.message : String(e))
|
|
42
|
+
});
|
|
43
|
+
function T() {
|
|
44
|
+
return {
|
|
45
|
+
type: "Settings",
|
|
46
|
+
audio: {
|
|
47
|
+
input: {
|
|
48
|
+
encoding: "linear16",
|
|
49
|
+
sample_rate: d
|
|
50
|
+
},
|
|
51
|
+
output: {
|
|
52
|
+
encoding: "linear16",
|
|
53
|
+
sample_rate: f,
|
|
54
|
+
container: "none"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
agent: o
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function E(e) {
|
|
61
|
+
let t;
|
|
62
|
+
try {
|
|
63
|
+
t = JSON.parse(e);
|
|
64
|
+
} catch {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
let n = s(t, w);
|
|
68
|
+
switch (n.kind) {
|
|
69
|
+
case "welcome":
|
|
70
|
+
b?.send(JSON.stringify(T())), y("Welcome received — sent Settings");
|
|
71
|
+
break;
|
|
72
|
+
case "settings-applied":
|
|
73
|
+
x = !0, y("SettingsApplied — agent audio ready");
|
|
74
|
+
break;
|
|
75
|
+
case "error":
|
|
76
|
+
_?.(n.message), y(n.message, "warn");
|
|
77
|
+
break;
|
|
78
|
+
default: break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (b = new WebSocket(v), b.binaryType = "arraybuffer", await new Promise((e, t) => {
|
|
82
|
+
if (!b) {
|
|
83
|
+
t(/* @__PURE__ */ Error("WebSocket failed to construct"));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
let n = b, r = () => {
|
|
87
|
+
a(), e();
|
|
88
|
+
}, i = () => {
|
|
89
|
+
a(), t(/* @__PURE__ */ Error("Deepgram Voice Agent WebSocket failed to open"));
|
|
90
|
+
}, a = () => {
|
|
91
|
+
n.removeEventListener("open", r), n.removeEventListener("error", i);
|
|
92
|
+
};
|
|
93
|
+
n.addEventListener("open", r), n.addEventListener("error", i);
|
|
94
|
+
}), b.addEventListener("message", (e) => {
|
|
95
|
+
if (e.data instanceof ArrayBuffer) {
|
|
96
|
+
w.writePcm(new Uint8Array(e.data));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
typeof e.data == "string" && E(e.data);
|
|
100
|
+
}), b.addEventListener("close", () => {
|
|
101
|
+
C?.stop(), C = null, x = !1, b = null, !S && (w.abandonActiveTurn(), y("Deepgram Voice Agent disconnected"), g?.());
|
|
102
|
+
}), i({
|
|
103
|
+
captureMic: p,
|
|
104
|
+
mediaStream: m
|
|
105
|
+
})) try {
|
|
106
|
+
C = await a({
|
|
107
|
+
targetSampleRate: d,
|
|
108
|
+
mediaStream: m,
|
|
109
|
+
onLog: y,
|
|
110
|
+
shouldSend: () => !S && x && b != null && b.readyState === WebSocket.OPEN,
|
|
111
|
+
onPcm: (e) => {
|
|
112
|
+
if (!b || b.readyState !== WebSocket.OPEN) return;
|
|
113
|
+
let t = e.buffer.slice(e.byteOffset, e.byteOffset + e.byteLength);
|
|
114
|
+
b.send(t);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
} catch (e) {
|
|
118
|
+
b.close();
|
|
119
|
+
let t = e instanceof Error ? e.message : String(e);
|
|
120
|
+
throw Error(`Microphone access failed: ${t}`);
|
|
121
|
+
}
|
|
122
|
+
return y("Deepgram Voice Agent connected. Speak into your mic — the avatar should lip-sync."), {
|
|
123
|
+
isConnected: () => {
|
|
124
|
+
let e = b;
|
|
125
|
+
return e != null && e.readyState === WebSocket.OPEN && !S;
|
|
126
|
+
},
|
|
127
|
+
async end() {
|
|
128
|
+
if (S) return;
|
|
129
|
+
S = !0, await w.closeActiveTurn(), C?.stop(), C = null;
|
|
130
|
+
let e = b;
|
|
131
|
+
if (b = null, e && e.readyState === WebSocket.OPEN) try {
|
|
132
|
+
e.close();
|
|
133
|
+
} catch (e) {
|
|
134
|
+
console.error(e);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
export { l as DEEPGRAM_AGENT_INPUT_RATE, c as DEEPGRAM_AGENT_OUTPUT_RATE, e as MAX_PCM_CHUNK_BYTES, d as connectDeepgramAgent, n as floatToPcm16Le, r as resampleFloat32, o as writePcmInChunks };
|
|
141
|
+
|
|
142
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/deepgram/deepgramAgentMessages.ts","../../src/deepgram/connectDeepgramAgent.ts"],"sourcesContent":["/**\n * Pure Deepgram Voice Agent JSON → turn-session mapping (unit-tested).\n */\n\nexport type DeepgramTurnSink = {\n\tsetTranscript(text: string, mode?: 'replace' | 'append'): void;\n\tbargeIn(): void;\n\tcloseActiveTurn(): Promise<void> | void;\n};\n\nexport type DeepgramAgentJsonMessage = {\n\treadonly type?: string;\n\treadonly role?: string;\n\treadonly content?: string;\n\treadonly description?: string;\n\treadonly message?: string;\n};\n\nexport type HandleDeepgramAgentJsonResult =\n\t| { readonly kind: 'ignore' }\n\t| { readonly kind: 'welcome' }\n\t| { readonly kind: 'settings-applied' }\n\t| { readonly kind: 'error'; readonly message: string };\n\n/**\n * Apply a Deepgram Voice Agent JSON server message to a turn sink.\n * Returns side-effect hints for the WebSocket layer (Settings / errors).\n */\nexport function handleDeepgramAgentJson(\n\tmsg: DeepgramAgentJsonMessage,\n\tturns: DeepgramTurnSink\n): HandleDeepgramAgentJsonResult {\n\tswitch (msg.type) {\n\t\tcase 'Welcome':\n\t\t\treturn { kind: 'welcome' };\n\n\t\tcase 'SettingsApplied':\n\t\t\treturn { kind: 'settings-applied' };\n\n\t\tcase 'ConversationText':\n\t\t\t// Full statements per message — replace so force-align is not duplicated.\n\t\t\tif (msg.role === 'assistant' && typeof msg.content === 'string' && msg.content) {\n\t\t\t\tturns.setTranscript(msg.content, 'replace');\n\t\t\t}\n\t\t\treturn { kind: 'ignore' };\n\n\t\tcase 'UserStartedSpeaking':\n\t\t\tturns.bargeIn();\n\t\t\treturn { kind: 'ignore' };\n\n\t\tcase 'AgentAudioDone':\n\t\t\tvoid turns.closeActiveTurn();\n\t\t\treturn { kind: 'ignore' };\n\n\t\tcase 'Error':\n\t\t\treturn {\n\t\t\t\tkind: 'error',\n\t\t\t\tmessage: String(msg.description ?? msg.message ?? 'Deepgram Voice Agent error')\n\t\t\t};\n\n\t\tdefault:\n\t\t\treturn { kind: 'ignore' };\n\t}\n}\n","/**\n * Deepgram Voice Agent → Liforma BYO voice helper.\n *\n * Mount a Liforma Experience (typically `mode=\"presenter\"`, `speechInputMode=\"off\"`),\n * wait until the player has started (audio unlocked), then call this helper with a\n * same-origin WebSocket proxy URL (browsers cannot set Authorization on WebSocket).\n *\n * Docs: https://docs.liforma.ai/avatar-experiences/bring-your-own-voice/deepgram\n */\n\nimport {\n\tcreateTurnSession,\n\tshouldCaptureMic,\n\tstartPcmMic,\n\ttype ByoBridge,\n\ttype ByoConnectCallbacks,\n\ttype ByoMicCaptureOptions,\n\ttype ByoPcmExperience,\n\ttype PcmMicSession\n} from '@liforma/client/byo';\nimport { handleDeepgramAgentJson } from './deepgramAgentMessages';\n\nexport type DeepgramAgentExperience = ByoPcmExperience;\n\n/** Default Deepgram Voice Agent output rate for this helper. */\nexport const DEEPGRAM_AGENT_OUTPUT_RATE = 24_000;\n\n/** Default mic PCM rate streamed to Deepgram input. */\nexport const DEEPGRAM_AGENT_INPUT_RATE = 16_000;\n\nexport type DeepgramAgentSettings = {\n\treadonly type: 'Settings';\n\treadonly audio: {\n\t\treadonly input: { readonly encoding: 'linear16'; readonly sample_rate: number };\n\t\treadonly output: {\n\t\t\treadonly encoding: 'linear16';\n\t\t\treadonly sample_rate: number;\n\t\t\treadonly container: 'none';\n\t\t};\n\t};\n\treadonly agent: Record<string, unknown>;\n};\n\nexport type ConnectDeepgramAgentOptions = ByoConnectCallbacks &\n\tByoMicCaptureOptions & {\n\t\t/**\n\t\t * Same-origin (or CORS-friendly) WebSocket URL that proxies to\n\t\t * `wss://agent.deepgram.com/v1/agent/converse` with your Deepgram API key.\n\t\t */\n\t\treadonly proxyUrl: string;\n\t\t/**\n\t\t * Agent listen / think / speak config. Audio I/O formats are enforced by the helper\n\t\t * (`container: 'none'`, linear16) so lipsync gets headerless PCM.\n\t\t */\n\t\treadonly agent?: Record<string, unknown>;\n\t\treadonly inputSampleRate?: number;\n\t\treadonly outputSampleRate?: number;\n\t};\n\nexport type DeepgramAgentBridge = ByoBridge;\n\nconst DEFAULT_AGENT: Record<string, unknown> = {\n\tlanguage: 'en',\n\tlisten: {\n\t\tprovider: { type: 'deepgram', model: 'nova-3' }\n\t},\n\tthink: {\n\t\tprovider: { type: 'open_ai', model: 'gpt-4o-mini' }\n\t},\n\tspeak: {\n\t\tprovider: { type: 'deepgram', model: 'aura-2-thalia-en' }\n\t}\n};\n\n/**\n * Start a Deepgram Voice Agent session (via your proxy) and pipe agent PCM +\n * transcript into `experience.speech.createUtterance` so the Liforma avatar talks.\n */\nexport async function connectDeepgramAgent(\n\texperience: DeepgramAgentExperience,\n\toptions: ConnectDeepgramAgentOptions\n): Promise<DeepgramAgentBridge> {\n\tconst {\n\t\tproxyUrl,\n\t\tagent = DEFAULT_AGENT,\n\t\tinputSampleRate = DEEPGRAM_AGENT_INPUT_RATE,\n\t\toutputSampleRate = DEEPGRAM_AGENT_OUTPUT_RATE,\n\t\tcaptureMic = true,\n\t\tmediaStream,\n\t\tonLog,\n\t\tonDisconnect,\n\t\tonError\n\t} = options;\n\n\tconst url = proxyUrl.trim();\n\tif (!url) throw new Error('Pass proxyUrl from your Deepgram Voice Agent proxy');\n\n\tconst log = (line: string, kind: 'info' | 'warn' = 'info') => onLog?.(line, kind);\n\n\tlet ws: WebSocket | null = null;\n\tlet settingsApplied = false;\n\tlet ended = false;\n\tlet mic: PcmMicSession | null = null;\n\n\tconst turns = createTurnSession(experience, {\n\t\tsampleRate: outputSampleRate,\n\t\tisEnded: () => ended,\n\t\tcanWrite: () => settingsApplied,\n\t\tonWriteError: (err) => onError?.(err instanceof Error ? err.message : String(err))\n\t});\n\n\tfunction buildSettings(): DeepgramAgentSettings {\n\t\treturn {\n\t\t\ttype: 'Settings',\n\t\t\taudio: {\n\t\t\t\tinput: { encoding: 'linear16', sample_rate: inputSampleRate },\n\t\t\t\toutput: {\n\t\t\t\t\tencoding: 'linear16',\n\t\t\t\t\tsample_rate: outputSampleRate,\n\t\t\t\t\tcontainer: 'none'\n\t\t\t\t}\n\t\t\t},\n\t\t\tagent\n\t\t};\n\t}\n\n\tfunction handleJsonMessage(raw: string): void {\n\t\tlet msg: {\n\t\t\ttype?: string;\n\t\t\trole?: string;\n\t\t\tcontent?: string;\n\t\t\tdescription?: string;\n\t\t\tmessage?: string;\n\t\t};\n\t\ttry {\n\t\t\tmsg = JSON.parse(raw) as typeof msg;\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = handleDeepgramAgentJson(msg, turns);\n\t\tswitch (result.kind) {\n\t\t\tcase 'welcome':\n\t\t\t\tws?.send(JSON.stringify(buildSettings()));\n\t\t\t\tlog('Welcome received — sent Settings');\n\t\t\t\tbreak;\n\t\t\tcase 'settings-applied':\n\t\t\t\tsettingsApplied = true;\n\t\t\t\tlog('SettingsApplied — agent audio ready');\n\t\t\t\tbreak;\n\t\t\tcase 'error':\n\t\t\t\tonError?.(result.message);\n\t\t\t\tlog(result.message, 'warn');\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tws = new WebSocket(url);\n\tws.binaryType = 'arraybuffer';\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tif (!ws) {\n\t\t\treject(new Error('WebSocket failed to construct'));\n\t\t\treturn;\n\t\t}\n\t\tconst socket = ws;\n\t\tconst onOpen = () => {\n\t\t\tcleanup();\n\t\t\tresolve();\n\t\t};\n\t\tconst onErrorEvt = () => {\n\t\t\tcleanup();\n\t\t\treject(new Error('Deepgram Voice Agent WebSocket failed to open'));\n\t\t};\n\t\tconst cleanup = () => {\n\t\t\tsocket.removeEventListener('open', onOpen);\n\t\t\tsocket.removeEventListener('error', onErrorEvt);\n\t\t};\n\t\tsocket.addEventListener('open', onOpen);\n\t\tsocket.addEventListener('error', onErrorEvt);\n\t});\n\n\tws.addEventListener('message', (ev) => {\n\t\tif (ev.data instanceof ArrayBuffer) {\n\t\t\tturns.writePcm(new Uint8Array(ev.data));\n\t\t\treturn;\n\t\t}\n\t\tif (typeof ev.data === 'string') handleJsonMessage(ev.data);\n\t});\n\n\tws.addEventListener('close', () => {\n\t\tmic?.stop();\n\t\tmic = null;\n\t\tsettingsApplied = false;\n\t\tws = null;\n\t\t// Skip after intentional end() — turn was already closed cleanly.\n\t\tif (ended) return;\n\t\tturns.abandonActiveTurn();\n\t\tlog('Deepgram Voice Agent disconnected');\n\t\tonDisconnect?.();\n\t});\n\n\tif (shouldCaptureMic({ captureMic, mediaStream })) {\n\t\ttry {\n\t\t\tmic = await startPcmMic({\n\t\t\t\ttargetSampleRate: inputSampleRate,\n\t\t\t\tmediaStream,\n\t\t\t\tonLog: log,\n\t\t\t\tshouldSend: () =>\n\t\t\t\t\t!ended && settingsApplied && ws != null && ws.readyState === WebSocket.OPEN,\n\t\t\t\tonPcm: (pcm) => {\n\t\t\t\t\tif (!ws || ws.readyState !== WebSocket.OPEN) return;\n\t\t\t\t\tconst copy = pcm.buffer.slice(\n\t\t\t\t\t\tpcm.byteOffset,\n\t\t\t\t\t\tpcm.byteOffset + pcm.byteLength\n\t\t\t\t\t) as ArrayBuffer;\n\t\t\t\t\tws.send(copy);\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tws.close();\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tthrow new Error(`Microphone access failed: ${message}`);\n\t\t}\n\t}\n\n\tlog('Deepgram Voice Agent connected. Speak into your mic — the avatar should lip-sync.');\n\n\treturn {\n\t\tisConnected: () => {\n\t\t\tconst socket = ws;\n\t\t\treturn socket != null && socket.readyState === WebSocket.OPEN && !ended;\n\t\t},\n\t\tasync end() {\n\t\t\tif (ended) return;\n\t\t\tended = true;\n\t\t\tawait turns.closeActiveTurn();\n\t\t\tmic?.stop();\n\t\t\tmic = null;\n\t\t\tconst active = ws;\n\t\t\tws = null;\n\t\t\tif (active && active.readyState === WebSocket.OPEN) {\n\t\t\t\ttry {\n\t\t\t\t\tactive.close();\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconsole.error(err);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ElevenLabs Agents → Liforma BYO voice helper.
|
|
3
|
+
*
|
|
4
|
+
* Mount a Liforma Experience (typically `mode="presenter"`, `speechInputMode="off"`),
|
|
5
|
+
* wait until the player has started (audio unlocked), then call this helper.
|
|
6
|
+
*
|
|
7
|
+
* Docs: https://docs.liforma.ai/avatar-experiences/bring-your-own-voice/elevenlabs
|
|
8
|
+
*/
|
|
9
|
+
import { type ByoBridge, type ByoConnectCallbacks, type ByoPcmExperience } from '@liforma/client/byo';
|
|
10
|
+
export type ElevenLabsAgentExperience = ByoPcmExperience;
|
|
11
|
+
export type ConnectElevenLabsAgentOptions = ByoConnectCallbacks & {
|
|
12
|
+
/** Public agents: pass agentId. Private agents: prefer signedUrl instead. */
|
|
13
|
+
readonly agentId?: string;
|
|
14
|
+
/** From your backend signed-URL mint (never ship ElevenLabs API keys to browsers). */
|
|
15
|
+
readonly signedUrl?: string;
|
|
16
|
+
};
|
|
17
|
+
export type ElevenLabsAgentBridge = ByoBridge;
|
|
18
|
+
/**
|
|
19
|
+
* Start an ElevenLabs Agents session and pipe agent PCM + transcript into
|
|
20
|
+
* `experience.speech.createUtterance` so the Liforma avatar talks.
|
|
21
|
+
*/
|
|
22
|
+
export declare function connectElevenLabsAgent(experience: ElevenLabsAgentExperience, options: ConnectElevenLabsAgentOptions): Promise<ElevenLabsAgentBridge>;
|
|
23
|
+
//# sourceMappingURL=connectElevenLabsAgent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connectElevenLabsAgent.d.ts","sourceRoot":"","sources":["../../src/elevenlabs/connectElevenLabsAgent.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAGN,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,yBAAyB,GAAG,gBAAgB,CAAC;AAEzD,MAAM,MAAM,6BAA6B,GAAG,mBAAmB,GAAG;IACjE,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,sFAAsF;IACtF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAAC;AAO9C;;;GAGG;AACH,wBAAsB,sBAAsB,CAC3C,UAAU,EAAE,yBAAyB,EACrC,OAAO,EAAE,6BAA6B,GACpC,OAAO,CAAC,qBAAqB,CAAC,CAoKhC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { connectElevenLabsAgent, type ConnectElevenLabsAgentOptions, type ElevenLabsAgentBridge, type ElevenLabsAgentExperience } from './connectElevenLabsAgent';
|
|
2
|
+
export type { ByoPcmSpeechController as ElevenLabsSpeechController, ByoSpeechUtterance as ElevenLabsSpeechUtterance } from '@liforma/client/byo';
|
|
3
|
+
export { MAX_PCM_CHUNK_BYTES, base64ToPcmBytes, parsePcmSampleRate, writePcmInChunks } from '@liforma/client/byo';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/elevenlabs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,sBAAsB,EACtB,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,EAC9B,MAAM,0BAA0B,CAAC;AAElC,YAAY,EAAE,sBAAsB,IAAI,0BAA0B,EAAE,kBAAkB,IAAI,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAEjJ,OAAO,EACN,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Conversation as e } from "@elevenlabs/client";
|
|
2
|
+
import { MAX_PCM_CHUNK_BYTES as t, base64ToPcmBytes as n, createTurnSession as r, parsePcmSampleRate as i, parsePcmSampleRate as a, writePcmInChunks as o } from "@liforma/client/byo";
|
|
3
|
+
//#region src/elevenlabs/connectElevenLabsAgent.ts
|
|
4
|
+
var s = 2e3, c = 64;
|
|
5
|
+
async function l(t, n) {
|
|
6
|
+
let { agentId: i, signedUrl: o, onLog: l, onDisconnect: u, onError: d } = n;
|
|
7
|
+
if (!o && !i) throw Error("Pass signedUrl or agentId");
|
|
8
|
+
let f = (e, t = "info") => l?.(e, t), p = null, m = null, h = !1, g = !1, _ = [], v = 0, y = !1, b = r(t, {
|
|
9
|
+
sampleRate: () => h ? m : null,
|
|
10
|
+
isEnded: () => y,
|
|
11
|
+
onWriteError: (e) => d?.(e instanceof Error ? e.message : String(e))
|
|
12
|
+
});
|
|
13
|
+
function x() {
|
|
14
|
+
v &&= (window.clearTimeout(v), 0);
|
|
15
|
+
}
|
|
16
|
+
function S(e) {
|
|
17
|
+
h || g || (g = !0, x(), _ = [], d?.(e), f(e, "warn"));
|
|
18
|
+
}
|
|
19
|
+
function C() {
|
|
20
|
+
if (!_.length) return;
|
|
21
|
+
let e = _;
|
|
22
|
+
_ = [];
|
|
23
|
+
for (let t of e) b.writePcmBase64(t);
|
|
24
|
+
}
|
|
25
|
+
function w(e) {
|
|
26
|
+
h || !Number.isFinite(e) || e <= 0 || (m = e, h = !0, g = !1, x(), f(`Agent output format: pcm_${m}`), C());
|
|
27
|
+
}
|
|
28
|
+
function T() {
|
|
29
|
+
h || g || v || (v = window.setTimeout(() => {
|
|
30
|
+
v = 0, S("ElevenLabs agent_output_audio_format missing — buffered audio dropped (will not guess sample rate)");
|
|
31
|
+
}, s));
|
|
32
|
+
}
|
|
33
|
+
function E(e) {
|
|
34
|
+
if (!g) {
|
|
35
|
+
if (!h) {
|
|
36
|
+
if (_.length >= c) {
|
|
37
|
+
S("ElevenLabs agent_output_audio_format missing — pending audio buffer full (will not guess sample rate)");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
_.push(e), T();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
b.writePcmBase64(e);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return p = await e.startSession({
|
|
47
|
+
connectionType: "websocket",
|
|
48
|
+
...o ? { signedUrl: o } : { agentId: i },
|
|
49
|
+
onConversationMetadata: (e) => {
|
|
50
|
+
let t = a(e?.agent_output_audio_format);
|
|
51
|
+
t != null && w(t);
|
|
52
|
+
},
|
|
53
|
+
onAudio: (e) => {
|
|
54
|
+
E(e);
|
|
55
|
+
},
|
|
56
|
+
onMessage: ({ message: e, role: t }) => {
|
|
57
|
+
t === "agent" && b.setTranscript(e, "replace");
|
|
58
|
+
},
|
|
59
|
+
onModeChange: ({ mode: e }) => {
|
|
60
|
+
e === "listening" && b.closeActiveTurn();
|
|
61
|
+
},
|
|
62
|
+
onInterruption: () => {
|
|
63
|
+
_ = [], b.bargeIn();
|
|
64
|
+
},
|
|
65
|
+
onError: (e) => {
|
|
66
|
+
let t = String(e);
|
|
67
|
+
d?.(t), f(`ElevenLabs error: ${t}`, "warn");
|
|
68
|
+
},
|
|
69
|
+
onDisconnect: () => {
|
|
70
|
+
x(), _ = [], m = null, h = !1, g = !1, p = null, !y && (b.abandonActiveTurn(), f("ElevenLabs disconnected"), u?.());
|
|
71
|
+
}
|
|
72
|
+
}), await p.setVolume({ volume: 0 }), f("Conversation started. Speak into your mic — the avatar should lip-sync."), {
|
|
73
|
+
isConnected: () => !!p && !y,
|
|
74
|
+
async end() {
|
|
75
|
+
if (y) return;
|
|
76
|
+
y = !0, x(), _ = [], await b.closeActiveTurn();
|
|
77
|
+
let e = p;
|
|
78
|
+
if (p = null, m = null, h = !1, g = !1, e) try {
|
|
79
|
+
await e.endSession();
|
|
80
|
+
} catch (e) {
|
|
81
|
+
console.error(e);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
export { t as MAX_PCM_CHUNK_BYTES, n as base64ToPcmBytes, l as connectElevenLabsAgent, i as parsePcmSampleRate, o as writePcmInChunks };
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/elevenlabs/connectElevenLabsAgent.ts"],"sourcesContent":["/**\n * ElevenLabs Agents → Liforma BYO voice helper.\n *\n * Mount a Liforma Experience (typically `mode=\"presenter\"`, `speechInputMode=\"off\"`),\n * wait until the player has started (audio unlocked), then call this helper.\n *\n * Docs: https://docs.liforma.ai/avatar-experiences/bring-your-own-voice/elevenlabs\n */\n\nimport { Conversation } from '@elevenlabs/client';\nimport {\n\tcreateTurnSession,\n\tparsePcmSampleRate,\n\ttype ByoBridge,\n\ttype ByoConnectCallbacks,\n\ttype ByoPcmExperience\n} from '@liforma/client/byo';\n\nexport type ElevenLabsAgentExperience = ByoPcmExperience;\n\nexport type ConnectElevenLabsAgentOptions = ByoConnectCallbacks & {\n\t/** Public agents: pass agentId. Private agents: prefer signedUrl instead. */\n\treadonly agentId?: string;\n\t/** From your backend signed-URL mint (never ship ElevenLabs API keys to browsers). */\n\treadonly signedUrl?: string;\n};\n\nexport type ElevenLabsAgentBridge = ByoBridge;\n\n/** How long to wait for `agent_output_audio_format` after the first PCM frame. */\nconst SAMPLE_RATE_TIMEOUT_MS = 2_000;\n/** Cap buffered agent audio while waiting for format metadata. */\nconst MAX_PENDING_AUDIO_FRAMES = 64;\n\n/**\n * Start an ElevenLabs Agents session and pipe agent PCM + transcript into\n * `experience.speech.createUtterance` so the Liforma avatar talks.\n */\nexport async function connectElevenLabsAgent(\n\texperience: ElevenLabsAgentExperience,\n\toptions: ConnectElevenLabsAgentOptions\n): Promise<ElevenLabsAgentBridge> {\n\tconst { agentId, signedUrl, onLog, onDisconnect, onError } = options;\n\tif (!signedUrl && !agentId) throw new Error('Pass signedUrl or agentId');\n\n\tconst log = (line: string, kind: 'info' | 'warn' = 'info') => onLog?.(line, kind);\n\n\tlet conversation: Awaited<ReturnType<typeof Conversation.startSession>> | null = null;\n\tlet sampleRate: number | null = null;\n\tlet sampleRateReady = false;\n\t/** True after we gave up waiting for format metadata (no silent rate guess). */\n\tlet sampleRateFailed = false;\n\tlet pendingAudioB64: string[] = [];\n\tlet sampleRateWatchdog = 0;\n\tlet ended = false;\n\n\tconst turns = createTurnSession(experience, {\n\t\tsampleRate: () => (sampleRateReady ? sampleRate : null),\n\t\tisEnded: () => ended,\n\t\tonWriteError: (err) => onError?.(err instanceof Error ? err.message : String(err))\n\t});\n\n\tfunction clearSampleRateWatchdog(): void {\n\t\tif (!sampleRateWatchdog) return;\n\t\twindow.clearTimeout(sampleRateWatchdog);\n\t\tsampleRateWatchdog = 0;\n\t}\n\n\tfunction failSampleRate(reason: string): void {\n\t\tif (sampleRateReady || sampleRateFailed) return;\n\t\tsampleRateFailed = true;\n\t\tclearSampleRateWatchdog();\n\t\tpendingAudioB64 = [];\n\t\tonError?.(reason);\n\t\tlog(reason, 'warn');\n\t}\n\n\tfunction flushPendingAudio(): void {\n\t\tif (!pendingAudioB64.length) return;\n\t\tconst queued = pendingAudioB64;\n\t\tpendingAudioB64 = [];\n\t\tfor (const b64 of queued) turns.writePcmBase64(b64);\n\t}\n\n\t/**\n\t * Lock sample rate from `agent_output_audio_format` before createUtterance.\n\t * A wrong rate makes STA/energy clocks drift (mouth can look stuck open).\n\t * Never guess — only lock from metadata (late metadata can recover after a fail).\n\t */\n\tfunction lockSampleRate(rate: number): void {\n\t\tif (sampleRateReady) return;\n\t\tif (!Number.isFinite(rate) || rate <= 0) return;\n\t\tsampleRate = rate;\n\t\tsampleRateReady = true;\n\t\tsampleRateFailed = false;\n\t\tclearSampleRateWatchdog();\n\t\tlog(`Agent output format: pcm_${sampleRate}`);\n\t\tflushPendingAudio();\n\t}\n\n\t/** Arm once when PCM arrives before format metadata — fail loudly, never guess. */\n\tfunction armSampleRateWatchdog(): void {\n\t\tif (sampleRateReady || sampleRateFailed || sampleRateWatchdog) return;\n\t\tsampleRateWatchdog = window.setTimeout(() => {\n\t\t\tsampleRateWatchdog = 0;\n\t\t\tfailSampleRate(\n\t\t\t\t'ElevenLabs agent_output_audio_format missing — buffered audio dropped (will not guess sample rate)'\n\t\t\t);\n\t\t}, SAMPLE_RATE_TIMEOUT_MS);\n\t}\n\n\tfunction handleAgentAudio(base64Audio: string): void {\n\t\tif (sampleRateFailed) return;\n\t\tif (!sampleRateReady) {\n\t\t\tif (pendingAudioB64.length >= MAX_PENDING_AUDIO_FRAMES) {\n\t\t\t\tfailSampleRate(\n\t\t\t\t\t'ElevenLabs agent_output_audio_format missing — pending audio buffer full (will not guess sample rate)'\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tpendingAudioB64.push(base64Audio);\n\t\t\tarmSampleRateWatchdog();\n\t\t\treturn;\n\t\t}\n\t\tturns.writePcmBase64(base64Audio);\n\t}\n\n\tconversation = await Conversation.startSession({\n\t\t// websocket is required for onAudio PCM frames.\n\t\tconnectionType: 'websocket',\n\t\t...(signedUrl ? { signedUrl } : { agentId: agentId! }),\n\n\t\tonConversationMetadata: (meta) => {\n\t\t\tconst rate = parsePcmSampleRate(meta?.agent_output_audio_format);\n\t\t\tif (rate != null) lockSampleRate(rate);\n\t\t},\n\n\t\tonAudio: (base64Audio) => {\n\t\t\thandleAgentAudio(base64Audio);\n\t\t},\n\n\t\tonMessage: ({ message, role }) => {\n\t\t\tif (role !== 'agent') return;\n\t\t\t// ElevenLabs delivers full agent message text — replace pending/active.\n\t\t\tturns.setTranscript(message, 'replace');\n\t\t},\n\n\t\tonModeChange: ({ mode }) => {\n\t\t\tif (mode !== 'listening') return;\n\t\t\tvoid turns.closeActiveTurn();\n\t\t},\n\n\t\tonInterruption: () => {\n\t\t\tpendingAudioB64 = [];\n\t\t\tturns.bargeIn();\n\t\t},\n\n\t\tonError: (message) => {\n\t\t\tconst text = String(message);\n\t\t\tonError?.(text);\n\t\t\tlog(`ElevenLabs error: ${text}`, 'warn');\n\t\t},\n\n\t\tonDisconnect: () => {\n\t\t\tclearSampleRateWatchdog();\n\t\t\tpendingAudioB64 = [];\n\t\t\tsampleRate = null;\n\t\t\tsampleRateReady = false;\n\t\t\tsampleRateFailed = false;\n\t\t\tconversation = null;\n\t\t\t// Skip after intentional end() — turn was already closed cleanly.\n\t\t\tif (ended) return;\n\t\t\tturns.abandonActiveTurn();\n\t\t\tlog('ElevenLabs disconnected');\n\t\t\tonDisconnect?.();\n\t\t}\n\t});\n\n\t// Critical: silence ElevenLabs' own speaker — Liforma owns playback.\n\tawait conversation.setVolume({ volume: 0 });\n\n\tlog('Conversation started. Speak into your mic — the avatar should lip-sync.');\n\n\treturn {\n\t\tisConnected: () => Boolean(conversation) && !ended,\n\t\tasync end() {\n\t\t\tif (ended) return;\n\t\t\tended = true;\n\t\t\tclearSampleRateWatchdog();\n\t\t\tpendingAudioB64 = [];\n\t\t\tawait turns.closeActiveTurn();\n\t\t\tconst active = conversation;\n\t\t\tconversation = null;\n\t\t\tsampleRate = null;\n\t\t\tsampleRateReady = false;\n\t\t\tsampleRateFailed = false;\n\t\t\tif (active) {\n\t\t\t\ttry {\n\t\t\t\t\tawait active.endSession();\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconsole.error(err);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini Live (BidiGenerateContent) → Liforma BYO voice helper.
|
|
3
|
+
*
|
|
4
|
+
* Mount a Liforma Experience (typically `mode="presenter"`, `speechInputMode="off"`),
|
|
5
|
+
* wait until the player has started (audio unlocked), then call this helper with a
|
|
6
|
+
* same-origin WebSocket proxy that terminates Gemini Live (never ship Google API keys).
|
|
7
|
+
*
|
|
8
|
+
* The helper expects proxy messages shaped like Gemini Live `serverContent` JSON, and
|
|
9
|
+
* (when captureMic is enabled) sends `realtimeInput.mediaChunks` PCM @ 16 kHz.
|
|
10
|
+
*
|
|
11
|
+
* Docs: https://docs.liforma.ai/avatar-experiences/bring-your-own-voice/google
|
|
12
|
+
*/
|
|
13
|
+
import { type ByoBridge, type ByoConnectCallbacks, type ByoMicCaptureOptions, type ByoPcmExperience } from '@liforma/client/byo';
|
|
14
|
+
export type GeminiLiveExperience = ByoPcmExperience;
|
|
15
|
+
/** Gemini Live output PCM rate expected by this helper. */
|
|
16
|
+
export declare const GEMINI_LIVE_OUTPUT_RATE = 24000;
|
|
17
|
+
/** Mic PCM rate streamed to Gemini Live input. */
|
|
18
|
+
export declare const GEMINI_LIVE_INPUT_RATE = 16000;
|
|
19
|
+
export type ConnectGeminiLiveOptions = ByoConnectCallbacks & ByoMicCaptureOptions & {
|
|
20
|
+
/**
|
|
21
|
+
* Same-origin (or CORS-friendly) WebSocket URL that proxies Gemini Live
|
|
22
|
+
* BidiGenerateContent. Keep Google credentials on the server.
|
|
23
|
+
*/
|
|
24
|
+
readonly proxyUrl: string;
|
|
25
|
+
readonly inputSampleRate?: number;
|
|
26
|
+
readonly outputSampleRate?: number;
|
|
27
|
+
};
|
|
28
|
+
export type GeminiLiveBridge = ByoBridge;
|
|
29
|
+
/**
|
|
30
|
+
* Connect to a Gemini Live proxy WebSocket and pipe agent PCM + transcript into
|
|
31
|
+
* `experience.speech.createUtterance` so the Liforma avatar talks.
|
|
32
|
+
*/
|
|
33
|
+
export declare function connectGeminiLive(experience: GeminiLiveExperience, options: ConnectGeminiLiveOptions): Promise<GeminiLiveBridge>;
|
|
34
|
+
//# sourceMappingURL=connectGeminiLive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connectGeminiLive.d.ts","sourceRoot":"","sources":["../../src/google/connectGeminiLive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAKN,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EAErB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,CAAC;AAEpD,2DAA2D;AAC3D,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,kDAAkD;AAClD,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C,MAAM,MAAM,wBAAwB,GAAG,mBAAmB,GACzD,oBAAoB,GAAG;IACtB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACnC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,SAAS,CAAC;AAEzC;;;GAGG;AACH,wBAAsB,iBAAiB,CACtC,UAAU,EAAE,oBAAoB,EAChC,OAAO,EAAE,wBAAwB,GAC/B,OAAO,CAAC,gBAAgB,CAAC,CA0K3B"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { connectGeminiLive, GEMINI_LIVE_INPUT_RATE, GEMINI_LIVE_OUTPUT_RATE, type ConnectGeminiLiveOptions, type GeminiLiveBridge, type GeminiLiveExperience } from './connectGeminiLive';
|
|
2
|
+
export type { ByoPcmSpeechController as GeminiLiveSpeechController, ByoSpeechUtterance as GeminiLiveSpeechUtterance } from '@liforma/client/byo';
|
|
3
|
+
export { MAX_PCM_CHUNK_BYTES, base64ToPcmBytes, floatToPcm16Le, pcmBytesToBase64, resampleFloat32, writePcmInChunks } from '@liforma/client/byo';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/google/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EACX,sBAAsB,IAAI,0BAA0B,EACpD,kBAAkB,IAAI,yBAAyB,EAC/C,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACN,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,MAAM,qBAAqB,CAAC"}
|