@happyrobot-ai/sdk 0.1.16 → 0.1.18
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 +52 -21
- package/chat-client.d.ts +19 -1
- package/chat-client.js +23 -0
- package/package.json +1 -1
- package/resources/integrations.d.ts +3 -3
- package/resources/integrations.js +1 -1
- package/types/adversarial-tests.types.d.ts +1 -1
- package/types/chat.types.d.ts +14 -1
- package/types/integrations.types.d.ts +7 -0
- package/types/voice.types.d.ts +7 -0
package/README.md
CHANGED
|
@@ -789,6 +789,8 @@ const client = new HappyRobotClient({ apiKey: process.env.HAPPYROBOT_API_KEY });
|
|
|
789
789
|
app.post("/api/chat-token", async (req, res) => {
|
|
790
790
|
const { token, expires_at } = await client.chat.createToken({
|
|
791
791
|
workflow_id: "your-workflow-id",
|
|
792
|
+
// Optional. Token lifetime in seconds. Default 3600 (1 hour), min 60, max 86400.
|
|
793
|
+
// ttl_seconds: 1800,
|
|
792
794
|
});
|
|
793
795
|
res.json({ token, expires_at });
|
|
794
796
|
});
|
|
@@ -825,8 +827,17 @@ const connection = chat.connect(session_id, {
|
|
|
825
827
|
onSessionClosed: (event) => {
|
|
826
828
|
// Session ended: event.status, event.reason, event.duration
|
|
827
829
|
},
|
|
830
|
+
// Auto-refresh: ~30s before the token expires the SDK calls this and
|
|
831
|
+
// forwards the result over the WebSocket. The connection stays open.
|
|
832
|
+
getToken: async () => {
|
|
833
|
+
const { token } = await fetch("/api/chat-token", { method: "POST" }).then(
|
|
834
|
+
(r) => r.json()
|
|
835
|
+
);
|
|
836
|
+
return token;
|
|
837
|
+
},
|
|
828
838
|
onTokenExpired: () => {
|
|
829
|
-
//
|
|
839
|
+
// Backstop: only fires if no valid refresh arrived in time
|
|
840
|
+
// (e.g. getToken not provided, network failure). Reconnect from scratch.
|
|
830
841
|
},
|
|
831
842
|
});
|
|
832
843
|
|
|
@@ -890,9 +901,9 @@ await connection.sendMessage({
|
|
|
890
901
|
|
|
891
902
|
### `HappyRobotClient` (server-side)
|
|
892
903
|
|
|
893
|
-
| Method
|
|
894
|
-
|
|
|
895
|
-
| `client.chat.createToken({ workflow_id, env? })` | Create a scoped client token (1 hour
|
|
904
|
+
| Method | Description |
|
|
905
|
+
| -------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
906
|
+
| `client.chat.createToken({ workflow_id, env?, ttl_seconds? })` | Create a scoped client token. `ttl_seconds` defaults to 3600 (1 hour). |
|
|
896
907
|
|
|
897
908
|
### `HappyRobotChatClient` (browser-side)
|
|
898
909
|
|
|
@@ -918,23 +929,39 @@ await connection.sendMessage({
|
|
|
918
929
|
|
|
919
930
|
**Server → Client:**
|
|
920
931
|
|
|
921
|
-
| Event
|
|
922
|
-
|
|
|
923
|
-
| `connected`
|
|
924
|
-
| `response-start`
|
|
925
|
-
| `response-chunk`
|
|
926
|
-
| `response-end`
|
|
927
|
-
| `session-closed`
|
|
928
|
-
| `token-
|
|
929
|
-
| `
|
|
930
|
-
| `
|
|
931
|
-
| `
|
|
932
|
+
| Event | Fields | Description |
|
|
933
|
+
| ------------------------ | --------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
934
|
+
| `connected` | `session_id` | Connection established |
|
|
935
|
+
| `response-start` | `content` | AI started generating a response |
|
|
936
|
+
| `response-chunk` | `content` | Partial response text |
|
|
937
|
+
| `response-end` | `content` | Complete response text |
|
|
938
|
+
| `session-closed` | `session_id`, `status`, `reason`, `duration`, `timestamp` | Session ended |
|
|
939
|
+
| `token-refresh-required` | — | Sent ~30s before exp. SDK handles automatically when `getToken` is provided |
|
|
940
|
+
| `token-refresh-ack` | `expires_at` | Server accepted the refreshed token; connection lifetime extended |
|
|
941
|
+
| `token-refresh-error` | `error` | Refresh token was invalid or scope-mismatched; previous token still in force |
|
|
942
|
+
| `token-expired` | — | Backstop: token expired without a valid refresh, connection is closing |
|
|
943
|
+
| `message-ack` | `id`, `message` | Server confirmed the message was sent |
|
|
944
|
+
| `message-error` | `id`, `error` | Server failed to send the message |
|
|
945
|
+
| `heartbeat` | — | Keep-alive (every 15s) |
|
|
932
946
|
|
|
933
947
|
**Client → Server:**
|
|
934
948
|
|
|
935
|
-
| Event
|
|
936
|
-
|
|
|
937
|
-
| `message`
|
|
949
|
+
| Event | Fields | Description |
|
|
950
|
+
| --------------- | ------------------------------ | ------------------------------------------------------------------------------------------------- |
|
|
951
|
+
| `message` | `content`, `artifacts?`, `id?` | Send a user message. `id` is echoed back in ack/error |
|
|
952
|
+
| `token-refresh` | `token` | Provide a fresh token to extend the connection. Sent automatically by the SDK when `getToken` set |
|
|
953
|
+
|
|
954
|
+
### Token refresh
|
|
955
|
+
|
|
956
|
+
Tokens have a finite lifetime (default 1 hour, configurable via `ttl_seconds` on `createToken`). To keep long-lived connections alive without forcing the client to reconnect, the SDK supports in-band refresh:
|
|
957
|
+
|
|
958
|
+
1. ~30s before the active token expires, the server sends `token-refresh-required`.
|
|
959
|
+
2. The SDK calls your `getToken` handler to fetch a fresh token from your backend.
|
|
960
|
+
3. The SDK sends `{ type: "token-refresh", token }` over the WebSocket.
|
|
961
|
+
4. The server verifies the new token (scope must match: same `workflow_id`, `org`, `env`) and replies with `token-refresh-ack`. `onTokenRefreshed(expiresAt)` fires on the client.
|
|
962
|
+
5. If no valid refresh arrives by exp, the server sends `token-expired` and closes the connection — `onTokenExpired` fires as a backstop.
|
|
963
|
+
|
|
964
|
+
If you don't provide `getToken`, refreshes are skipped and the connection will close at `exp` (legacy behavior). When the same token is shared across multiple concurrent connections, each connection refreshes independently — they diverge to per-connection tokens after the first refresh.
|
|
938
965
|
|
|
939
966
|
---
|
|
940
967
|
|
|
@@ -957,6 +984,10 @@ app.post("/api/voice-token", async (req, res) => {
|
|
|
957
984
|
const result = await client.voice.createToken({
|
|
958
985
|
workflow_id: "your-workflow-id",
|
|
959
986
|
data: { customer_name: "John" }, // optional — passed to the agent
|
|
987
|
+
// Optional. LiveKit token lifetime in seconds.
|
|
988
|
+
// Default 21600 (6 hours), min 60, max 86400 (24 hours).
|
|
989
|
+
// LiveKit does not refresh tokens on an active call.
|
|
990
|
+
// ttl_seconds: 3600,
|
|
960
991
|
});
|
|
961
992
|
res.json(result); // { url, token, room_name, run_id }
|
|
962
993
|
});
|
|
@@ -1008,9 +1039,9 @@ await connection.disconnect();
|
|
|
1008
1039
|
|
|
1009
1040
|
### `HappyRobotClient` (server-side)
|
|
1010
1041
|
|
|
1011
|
-
| Method
|
|
1012
|
-
|
|
|
1013
|
-
| `client.voice.createToken({ workflow_id, data?, env? })` | Create a LiveKit token for voice calls |
|
|
1042
|
+
| Method | Description |
|
|
1043
|
+
| ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
1044
|
+
| `client.voice.createToken({ workflow_id, data?, env?, ttl_seconds? })` | Create a LiveKit token for voice calls. `ttl_seconds` defaults to 21600 (min 60, max 86400). |
|
|
1014
1045
|
|
|
1015
1046
|
### `HappyRobotVoiceClient` (browser-side)
|
|
1016
1047
|
|
package/chat-client.d.ts
CHANGED
|
@@ -35,7 +35,25 @@ export interface ChatConnectionHandlers {
|
|
|
35
35
|
onSessionClosed?: (event: ChatWsSessionClosedEvent) => void;
|
|
36
36
|
/** Connection established. */
|
|
37
37
|
onConnected?: (sessionId: string) => void;
|
|
38
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Called when the server requests a fresh token before the current one
|
|
40
|
+
* expires. Provide an async function that fetches a new token from your
|
|
41
|
+
* backend; the SDK will send it over the WebSocket automatically and the
|
|
42
|
+
* connection stays open. If omitted, the connection will be closed at
|
|
43
|
+
* exp with `onTokenExpired`.
|
|
44
|
+
*
|
|
45
|
+
* Note: when one token is shared across multiple concurrent connections,
|
|
46
|
+
* each connection will refresh independently — they diverge to per-connection
|
|
47
|
+
* tokens after the first refresh.
|
|
48
|
+
*/
|
|
49
|
+
getToken?: () => Promise<string>;
|
|
50
|
+
/** Token was successfully refreshed in-band. */
|
|
51
|
+
onTokenRefreshed?: (expiresAt: string) => void;
|
|
52
|
+
/**
|
|
53
|
+
* Token expired and was not refreshed in time — reconnect with a fresh
|
|
54
|
+
* token to continue. Acts as a backstop if `getToken` is not provided or
|
|
55
|
+
* fails to deliver a valid token before expiry.
|
|
56
|
+
*/
|
|
39
57
|
onTokenExpired?: () => void;
|
|
40
58
|
/** WebSocket error. */
|
|
41
59
|
onError?: (error: Event) => void;
|
package/chat-client.js
CHANGED
|
@@ -146,6 +146,29 @@ class HappyRobotChatClient {
|
|
|
146
146
|
case "session-closed":
|
|
147
147
|
handlers.onSessionClosed?.(data);
|
|
148
148
|
break;
|
|
149
|
+
case "token-refresh-required": {
|
|
150
|
+
const getToken = handlers.getToken;
|
|
151
|
+
if (!getToken)
|
|
152
|
+
break;
|
|
153
|
+
getToken()
|
|
154
|
+
.then((newToken) => {
|
|
155
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
156
|
+
ws.send(JSON.stringify({ type: "token-refresh", token: newToken }));
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
.catch(() => {
|
|
160
|
+
// Server's expiry timer is the backstop — onTokenExpired
|
|
161
|
+
// will fire if no valid refresh arrives in time.
|
|
162
|
+
});
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case "token-refresh-ack":
|
|
166
|
+
handlers.onTokenRefreshed?.(data.expires_at);
|
|
167
|
+
break;
|
|
168
|
+
case "token-refresh-error":
|
|
169
|
+
// Connection stays alive on the previous token until exp; the
|
|
170
|
+
// backstop will close it if no valid refresh arrives in time.
|
|
171
|
+
break;
|
|
149
172
|
case "token-expired":
|
|
150
173
|
handlers.onTokenExpired?.();
|
|
151
174
|
break;
|
package/package.json
CHANGED
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
* GET /integrations/whatsapp/message-templates
|
|
25
25
|
*/
|
|
26
26
|
import type { HttpClient } from "../core/http";
|
|
27
|
-
import type { ListIntegrationsQuery, ListIntegrationsResponse, Integration, CreateCredentialBody, CreateCredentialResponse, CategoriesResponse, CredentialIdQuery, GoogleSheetsWorksheetsQuery, GoogleSheetsColumnsQuery, GoogleSheetsRowsQuery, TeamsChannelsQuery, WhatsAppCredentialQuery, WhatsAppBusinessAccountsQuery, WhatsAppPhoneNumbersQuery, WhatsAppMessageTemplatesQuery, ResourceListResponse, ResourceListWithDescriptionResponse, WhatsAppTemplateListResponse } from "../types/integrations.types";
|
|
27
|
+
import type { ListIntegrationsQuery, ListIntegrationsResponse, Integration, CreateCredentialBody, CreateCredentialResponse, CategoriesResponse, CredentialIdQuery, GoogleSheetsSpreadsheetsQuery, GoogleSheetsWorksheetsQuery, GoogleSheetsColumnsQuery, GoogleSheetsRowsQuery, TeamsChannelsQuery, WhatsAppCredentialQuery, WhatsAppBusinessAccountsQuery, WhatsAppPhoneNumbersQuery, WhatsAppMessageTemplatesQuery, PaginatedResourceListResponse, ResourceListResponse, ResourceListWithDescriptionResponse, WhatsAppTemplateListResponse } from "../types/integrations.types";
|
|
28
28
|
declare class GoogleSheetsResource {
|
|
29
29
|
private readonly http;
|
|
30
30
|
constructor(http: HttpClient);
|
|
31
|
-
/** List Google Sheets spreadsheets. */
|
|
32
|
-
spreadsheets(query?:
|
|
31
|
+
/** List Google Sheets spreadsheets. Supports search and cursor-based pagination. */
|
|
32
|
+
spreadsheets(query?: GoogleSheetsSpreadsheetsQuery): Promise<PaginatedResourceListResponse>;
|
|
33
33
|
/** List worksheets in a spreadsheet. */
|
|
34
34
|
worksheets(query: GoogleSheetsWorksheetsQuery): Promise<ResourceListResponse>;
|
|
35
35
|
/** List columns in a worksheet. */
|
|
@@ -32,7 +32,7 @@ class GoogleSheetsResource {
|
|
|
32
32
|
constructor(http) {
|
|
33
33
|
this.http = http;
|
|
34
34
|
}
|
|
35
|
-
/** List Google Sheets spreadsheets. */
|
|
35
|
+
/** List Google Sheets spreadsheets. Supports search and cursor-based pagination. */
|
|
36
36
|
async spreadsheets(query) {
|
|
37
37
|
return this.http.request({
|
|
38
38
|
method: "GET",
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Adversarial test types extracted from Zod schemas.
|
|
3
3
|
*/
|
|
4
4
|
import type { z } from "zod";
|
|
5
|
+
import type { EffectiveScopeApiSchema } from "../../routes/adversarial-tests/effective-scope/get";
|
|
5
6
|
import type { GetAdversarialTestByIdResponseSchema } from "../../routes/adversarial-tests/get";
|
|
6
7
|
import type { PatchAdversarialTestBodySchema, PatchAdversarialTestResponseSchema } from "../../routes/adversarial-tests/patch";
|
|
7
8
|
import type { RunAdversarialTestBodySchema, RunAdversarialTestResponseSchema } from "../../routes/adversarial-tests/run/post";
|
|
8
9
|
import type { AdversarialTestRunMessageSchema, GetAdversarialTestRunMessagesResponseSchema } from "../../routes/adversarial-tests/runs/:run_id/messages/get";
|
|
9
10
|
import type { AuditRemarkApiSchema, AdversarialTestRunApiSchema, GetAdversarialTestRunsResponseSchema } from "../../routes/adversarial-tests/runs/get";
|
|
10
|
-
import type { EffectiveScopeApiSchema } from "../../routes/adversarial-tests/effective-scope/get";
|
|
11
11
|
import type { AdversarialTestApiSchema } from "../../routes/nodes/:node_id/adversarial-tests/get";
|
|
12
12
|
export type AdversarialTest = z.infer<typeof AdversarialTestApiSchema>;
|
|
13
13
|
export type AdversarialTestEffectiveScope = z.infer<typeof EffectiveScopeApiSchema>;
|
package/types/chat.types.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export interface CreateChatTokenBody {
|
|
|
3
3
|
workflow_id: string;
|
|
4
4
|
data?: Record<string, unknown>;
|
|
5
5
|
env?: "production" | "staging" | "development";
|
|
6
|
+
/** Token lifetime in seconds. Defaults to 3600 (1 hour). Min 60s, max 24h. */
|
|
7
|
+
ttl_seconds?: number;
|
|
6
8
|
}
|
|
7
9
|
/** POST /chat/tokens response. */
|
|
8
10
|
export interface CreateChatTokenResponse {
|
|
@@ -129,6 +131,17 @@ export interface ChatWsHeartbeatEvent {
|
|
|
129
131
|
export interface ChatWsTokenExpiredEvent {
|
|
130
132
|
type: "token-expired";
|
|
131
133
|
}
|
|
134
|
+
export interface ChatWsTokenRefreshRequiredEvent {
|
|
135
|
+
type: "token-refresh-required";
|
|
136
|
+
}
|
|
137
|
+
export interface ChatWsTokenRefreshAckEvent {
|
|
138
|
+
type: "token-refresh-ack";
|
|
139
|
+
expires_at: string;
|
|
140
|
+
}
|
|
141
|
+
export interface ChatWsTokenRefreshErrorEvent {
|
|
142
|
+
type: "token-refresh-error";
|
|
143
|
+
error: string;
|
|
144
|
+
}
|
|
132
145
|
export interface ChatWsMessageAckEvent {
|
|
133
146
|
type: "message-ack";
|
|
134
147
|
id?: string;
|
|
@@ -145,4 +158,4 @@ export interface ChatWsMessageErrorEvent {
|
|
|
145
158
|
id?: string;
|
|
146
159
|
error: string;
|
|
147
160
|
}
|
|
148
|
-
export type ChatWsEvent = ChatWsConnectedEvent | ChatWsResponseStartEvent | ChatWsResponseChunkEvent | ChatWsResponseEndEvent | ChatWsSessionClosedEvent | ChatWsHeartbeatEvent | ChatWsTokenExpiredEvent | ChatWsMessageAckEvent | ChatWsMessageErrorEvent;
|
|
161
|
+
export type ChatWsEvent = ChatWsConnectedEvent | ChatWsResponseStartEvent | ChatWsResponseChunkEvent | ChatWsResponseEndEvent | ChatWsSessionClosedEvent | ChatWsHeartbeatEvent | ChatWsTokenExpiredEvent | ChatWsTokenRefreshRequiredEvent | ChatWsTokenRefreshAckEvent | ChatWsTokenRefreshErrorEvent | ChatWsMessageAckEvent | ChatWsMessageErrorEvent;
|
|
@@ -19,6 +19,13 @@ export type WhatsAppTemplateListResponse = z.infer<typeof WhatsAppTemplateListRe
|
|
|
19
19
|
export interface CredentialIdQuery {
|
|
20
20
|
credential_id?: string;
|
|
21
21
|
}
|
|
22
|
+
export interface GoogleSheetsSpreadsheetsQuery extends CredentialIdQuery {
|
|
23
|
+
search?: string;
|
|
24
|
+
page_token?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface PaginatedResourceListResponse extends ResourceListResponse {
|
|
27
|
+
next_page_token?: string;
|
|
28
|
+
}
|
|
22
29
|
export interface GoogleSheetsWorksheetsQuery extends CredentialIdQuery {
|
|
23
30
|
spreadsheet_id: string;
|
|
24
31
|
}
|
package/types/voice.types.d.ts
CHANGED
|
@@ -3,6 +3,13 @@ export interface CreateVoiceTokenBody {
|
|
|
3
3
|
workflow_id: string;
|
|
4
4
|
data?: Record<string, unknown>;
|
|
5
5
|
env?: "production" | "staging" | "development";
|
|
6
|
+
/**
|
|
7
|
+
* LiveKit token lifetime in seconds. Defaults to 21600 (6 hours) to match
|
|
8
|
+
* LiveKit's default. Min 60s, max 86400 (24h). LiveKit does not refresh
|
|
9
|
+
* tokens on an active call — the browser must reconnect if the call
|
|
10
|
+
* outlives the TTL.
|
|
11
|
+
*/
|
|
12
|
+
ttl_seconds?: number;
|
|
6
13
|
}
|
|
7
14
|
/** POST /voice/tokens response. */
|
|
8
15
|
export interface CreateVoiceTokenResponse {
|