@happyrobot-ai/sdk 0.1.45 → 0.1.47
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 +47 -8
- package/package.json +1 -1
- package/resources/api-key.d.ts +1 -0
- package/resources/voice.d.ts +11 -3
- package/resources/voice.js +11 -3
- package/types/voice.types.d.ts +7 -8
- package/voice/index.d.ts +1 -1
- package/voice-client.d.ts +16 -0
- package/voice-client.js +28 -1
package/README.md
CHANGED
|
@@ -998,6 +998,13 @@ app.post("/api/voice-token", async (req, res) => {
|
|
|
998
998
|
});
|
|
999
999
|
res.json(result); // { url, token, room_name, run_id }
|
|
1000
1000
|
});
|
|
1001
|
+
|
|
1002
|
+
app.post("/api/live-session/:sessionId/listen-token", async (req, res) => {
|
|
1003
|
+
const result = await client.voice.createToken({
|
|
1004
|
+
session_id: req.params.sessionId,
|
|
1005
|
+
});
|
|
1006
|
+
res.json(result); // Hidden, subscribe-only observer token
|
|
1007
|
+
});
|
|
1001
1008
|
```
|
|
1002
1009
|
|
|
1003
1010
|
### Browser-side: `HappyRobotVoiceClient`
|
|
@@ -1044,6 +1051,28 @@ await connection.unmute();
|
|
|
1044
1051
|
await connection.disconnect();
|
|
1045
1052
|
```
|
|
1046
1053
|
|
|
1054
|
+
To listen to an existing call without publishing audio or affecting the AI
|
|
1055
|
+
agent, create a token with `session_id` and use `listen()` in the browser:
|
|
1056
|
+
|
|
1057
|
+
```ts
|
|
1058
|
+
const { url, token } = await fetch(
|
|
1059
|
+
`/api/live-session/${sessionId}/listen-token`,
|
|
1060
|
+
{ method: "POST" }
|
|
1061
|
+
).then((r) => r.json());
|
|
1062
|
+
|
|
1063
|
+
const voice = new HappyRobotVoiceClient({ url, token });
|
|
1064
|
+
const listener = await voice.listen();
|
|
1065
|
+
|
|
1066
|
+
// If the browser later blocks playback, call this from a click handler:
|
|
1067
|
+
await listener.startAudio();
|
|
1068
|
+
|
|
1069
|
+
// Stop listening without affecting the call:
|
|
1070
|
+
await listener.disconnect();
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
Set `should_takeover: true` when creating a session token to retain the
|
|
1074
|
+
existing takeover behavior, which removes the AI agent from the call.
|
|
1075
|
+
|
|
1047
1076
|
### `HappyRobotClient` (server-side)
|
|
1048
1077
|
|
|
1049
1078
|
List the voices available to the current workspace in its cluster. Use a
|
|
@@ -1059,17 +1088,19 @@ for (const voice of voices) {
|
|
|
1059
1088
|
|
|
1060
1089
|
`voice.id` is the value used in workflow `agent.voices` configuration.
|
|
1061
1090
|
|
|
1062
|
-
| Method
|
|
1063
|
-
|
|
|
1064
|
-
| `client.voice.list({ language? })`
|
|
1065
|
-
| `client.voice.createToken({ workflow_id, data?, env?, ttl_seconds? })`
|
|
1091
|
+
| Method | Description |
|
|
1092
|
+
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
1093
|
+
| `client.voice.list({ language? })` | List voices available to the current workspace. `language` accepts prefixes like `en` or locales like `en-GB`. |
|
|
1094
|
+
| `client.voice.createToken({ workflow_id, data?, env?, ttl_seconds? })` | Create a LiveKit token for a new voice call. |
|
|
1095
|
+
| `client.voice.createToken({ session_id, should_takeover?, ttl_seconds? })` | Observe a live session, or take it over when `should_takeover` is true. |
|
|
1066
1096
|
|
|
1067
1097
|
### `HappyRobotVoiceClient` (browser-side)
|
|
1068
1098
|
|
|
1069
|
-
| Method | Description
|
|
1070
|
-
| ------------------------------------------- |
|
|
1071
|
-
| `new HappyRobotVoiceClient({ url, token })` | Create client with LiveKit credentials
|
|
1072
|
-
| `voice.connect(handlers?)` | Connect
|
|
1099
|
+
| Method | Description |
|
|
1100
|
+
| ------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
1101
|
+
| `new HappyRobotVoiceClient({ url, token })` | Create client with LiveKit credentials |
|
|
1102
|
+
| `voice.connect(handlers?)` | Connect and enable the microphone — returns `VoiceConnection` |
|
|
1103
|
+
| `voice.listen(handlers?)` | Connect without microphone access or publishing — returns `VoiceListenerConnection` |
|
|
1073
1104
|
|
|
1074
1105
|
### `VoiceConnection`
|
|
1075
1106
|
|
|
@@ -1081,6 +1112,14 @@ for (const voice of voices) {
|
|
|
1081
1112
|
| `connection.isMuted()` | Check mute state |
|
|
1082
1113
|
| `connection.room` | Underlying LiveKit `Room` instance for advanced use cases |
|
|
1083
1114
|
|
|
1115
|
+
### `VoiceListenerConnection`
|
|
1116
|
+
|
|
1117
|
+
| Method | Description |
|
|
1118
|
+
| ----------------------- | --------------------------------------------------------- |
|
|
1119
|
+
| `listener.disconnect()` | Stop listening without affecting the call |
|
|
1120
|
+
| `listener.startAudio()` | Resume audio playback from a browser user gesture |
|
|
1121
|
+
| `listener.room` | Underlying LiveKit `Room` instance for advanced use cases |
|
|
1122
|
+
|
|
1084
1123
|
### Room Events
|
|
1085
1124
|
|
|
1086
1125
|
| Handler | Description |
|
package/package.json
CHANGED
package/resources/api-key.d.ts
CHANGED
package/resources/voice.d.ts
CHANGED
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
* should_takeover: true,
|
|
22
22
|
* });
|
|
23
23
|
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example Silently listen to an in-progress call
|
|
26
|
+
* ```ts
|
|
27
|
+
* const { url, token } = await client.voice.createToken({
|
|
28
|
+
* session_id: "the-live-session-id",
|
|
29
|
+
* });
|
|
30
|
+
* // In the browser: await new HappyRobotVoiceClient({ url, token }).listen()
|
|
31
|
+
* ```
|
|
24
32
|
*/
|
|
25
33
|
import type { HttpClient } from "../core/http";
|
|
26
34
|
import type { CreateVoiceTokenBody, CreateVoiceTokenResponse, ListVoicesQuery, ListVoicesResponse } from "../types/voice.types";
|
|
@@ -35,9 +43,9 @@ export declare class VoiceResource {
|
|
|
35
43
|
* Call this from your server, then pass the returned `url` and `token`
|
|
36
44
|
* to the browser where they can be used to initialize `HappyRobotVoiceClient`.
|
|
37
45
|
*
|
|
38
|
-
* Provide `workflow_id` to start a new call, or `session_id`
|
|
39
|
-
*
|
|
40
|
-
*
|
|
46
|
+
* Provide `workflow_id` to start a new call, or `session_id` to silently
|
|
47
|
+
* observe an in-progress call. Set `should_takeover: true` to take over the
|
|
48
|
+
* call and drop the AI agent.
|
|
41
49
|
*/
|
|
42
50
|
createToken(body: CreateVoiceTokenBody): Promise<CreateVoiceTokenResponse>;
|
|
43
51
|
}
|
package/resources/voice.js
CHANGED
|
@@ -22,6 +22,14 @@
|
|
|
22
22
|
* should_takeover: true,
|
|
23
23
|
* });
|
|
24
24
|
* ```
|
|
25
|
+
*
|
|
26
|
+
* @example Silently listen to an in-progress call
|
|
27
|
+
* ```ts
|
|
28
|
+
* const { url, token } = await client.voice.createToken({
|
|
29
|
+
* session_id: "the-live-session-id",
|
|
30
|
+
* });
|
|
31
|
+
* // In the browser: await new HappyRobotVoiceClient({ url, token }).listen()
|
|
32
|
+
* ```
|
|
25
33
|
*/
|
|
26
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
35
|
exports.VoiceResource = void 0;
|
|
@@ -48,9 +56,9 @@ class VoiceResource {
|
|
|
48
56
|
* Call this from your server, then pass the returned `url` and `token`
|
|
49
57
|
* to the browser where they can be used to initialize `HappyRobotVoiceClient`.
|
|
50
58
|
*
|
|
51
|
-
* Provide `workflow_id` to start a new call, or `session_id`
|
|
52
|
-
*
|
|
53
|
-
*
|
|
59
|
+
* Provide `workflow_id` to start a new call, or `session_id` to silently
|
|
60
|
+
* observe an in-progress call. Set `should_takeover: true` to take over the
|
|
61
|
+
* call and drop the AI agent.
|
|
54
62
|
*/
|
|
55
63
|
async createToken(body) {
|
|
56
64
|
return this.http.request({
|
package/types/voice.types.d.ts
CHANGED
|
@@ -30,8 +30,8 @@ type CreateVoiceTokenBase = {
|
|
|
30
30
|
* POST /voice/tokens request body.
|
|
31
31
|
*
|
|
32
32
|
* Provide exactly one of `workflow_id` (start a new call) or `session_id`
|
|
33
|
-
* (
|
|
34
|
-
* `session_id`.
|
|
33
|
+
* (observe or take over an in-progress call). `should_takeover` is only
|
|
34
|
+
* available alongside `session_id`.
|
|
35
35
|
*/
|
|
36
36
|
export type CreateVoiceTokenBody = (CreateVoiceTokenBase & {
|
|
37
37
|
/** Workflow UUID or slug to start a new call with. */
|
|
@@ -40,17 +40,16 @@ export type CreateVoiceTokenBody = (CreateVoiceTokenBase & {
|
|
|
40
40
|
should_takeover?: never;
|
|
41
41
|
}) | (CreateVoiceTokenBase & {
|
|
42
42
|
/**
|
|
43
|
-
* ID of an in-progress session to take over. Generates a token
|
|
44
|
-
* session's existing LiveKit room.
|
|
43
|
+
* ID of an in-progress session to observe or take over. Generates a token
|
|
44
|
+
* for the session's existing LiveKit room.
|
|
45
45
|
*/
|
|
46
46
|
session_id: string;
|
|
47
47
|
workflow_id?: never;
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* without taking over is not supported.
|
|
49
|
+
* Defaults to false, creating a hidden subscribe-only observer while the
|
|
50
|
+
* AI agent remains active. Set to true to take over and drop the agent.
|
|
52
51
|
*/
|
|
53
|
-
should_takeover
|
|
52
|
+
should_takeover?: boolean;
|
|
54
53
|
});
|
|
55
54
|
/** POST /voice/tokens response. */
|
|
56
55
|
export interface CreateVoiceTokenResponse {
|
package/voice/index.d.ts
CHANGED
|
@@ -18,5 +18,5 @@
|
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
20
|
export { HappyRobotVoiceClient } from "../voice-client";
|
|
21
|
-
export type {
|
|
21
|
+
export type { VoiceClientConfig, VoiceConnection, VoiceConnectionHandlers, VoiceListenerConnection, } from "../voice-client";
|
|
22
22
|
export type { CreateVoiceTokenBody, CreateVoiceTokenResponse, ListVoicesQuery, ListVoicesResponse, Voice, } from "../types/voice.types";
|
package/voice-client.d.ts
CHANGED
|
@@ -67,6 +67,14 @@ export interface VoiceConnection {
|
|
|
67
67
|
/** The underlying LiveKit Room instance for advanced use cases. */
|
|
68
68
|
readonly room: Room;
|
|
69
69
|
}
|
|
70
|
+
export interface VoiceListenerConnection {
|
|
71
|
+
/** Stop listening and disconnect from the room. */
|
|
72
|
+
disconnect(): Promise<void>;
|
|
73
|
+
/** Resume audio playback after a browser autoplay block. Call from a user gesture. */
|
|
74
|
+
startAudio(): Promise<void>;
|
|
75
|
+
/** The underlying LiveKit Room instance for advanced use cases. */
|
|
76
|
+
readonly room: Room;
|
|
77
|
+
}
|
|
70
78
|
export interface VoiceClientConfig {
|
|
71
79
|
/** LiveKit WebSocket URL from `client.voice.createToken()`. */
|
|
72
80
|
url: string;
|
|
@@ -84,4 +92,12 @@ export declare class HappyRobotVoiceClient {
|
|
|
84
92
|
* Pass event handlers to react to room lifecycle events and remote tracks.
|
|
85
93
|
*/
|
|
86
94
|
connect(handlers?: VoiceConnectionHandlers): Promise<VoiceConnection>;
|
|
95
|
+
/**
|
|
96
|
+
* Silently listen to a live call without requesting microphone access.
|
|
97
|
+
*
|
|
98
|
+
* This must be used with a session observer token returned by
|
|
99
|
+
* `client.voice.createToken({ session_id })`.
|
|
100
|
+
*/
|
|
101
|
+
listen(handlers?: VoiceConnectionHandlers): Promise<VoiceListenerConnection>;
|
|
102
|
+
private connectToRoom;
|
|
87
103
|
}
|
package/voice-client.js
CHANGED
|
@@ -47,6 +47,25 @@ class HappyRobotVoiceClient {
|
|
|
47
47
|
* Pass event handlers to react to room lifecycle events and remote tracks.
|
|
48
48
|
*/
|
|
49
49
|
async connect(handlers = {}) {
|
|
50
|
+
return this.connectToRoom(handlers, true);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Silently listen to a live call without requesting microphone access.
|
|
54
|
+
*
|
|
55
|
+
* This must be used with a session observer token returned by
|
|
56
|
+
* `client.voice.createToken({ session_id })`.
|
|
57
|
+
*/
|
|
58
|
+
async listen(handlers = {}) {
|
|
59
|
+
const connection = await this.connectToRoom(handlers, false);
|
|
60
|
+
return {
|
|
61
|
+
disconnect: connection.disconnect,
|
|
62
|
+
startAudio: () => connection.room.startAudio(),
|
|
63
|
+
get room() {
|
|
64
|
+
return connection.room;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async connectToRoom(handlers, enableMicrophone) {
|
|
50
69
|
// Create Room with HappyRobot defaults (matching platform use-webcall.tsx)
|
|
51
70
|
const room = new livekit_client_1.Room({
|
|
52
71
|
adaptiveStream: true,
|
|
@@ -106,9 +125,17 @@ class HappyRobotVoiceClient {
|
|
|
106
125
|
});
|
|
107
126
|
// ── Connect ──
|
|
108
127
|
try {
|
|
128
|
+
if (!enableMicrophone) {
|
|
129
|
+
await room.startAudio().catch(() => undefined);
|
|
130
|
+
}
|
|
109
131
|
await room.prepareConnection(this.url, this.token);
|
|
110
132
|
await room.connect(this.url, this.token);
|
|
111
|
-
|
|
133
|
+
if (enableMicrophone) {
|
|
134
|
+
await room.localParticipant.setMicrophoneEnabled(true);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
await room.startAudio().catch(() => undefined);
|
|
138
|
+
}
|
|
112
139
|
}
|
|
113
140
|
catch (error) {
|
|
114
141
|
// Clean up on connection failure
|