@hainiva/agent-sdk 0.0.0-stage → 0.3.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.
Files changed (119) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/LICENSE +21 -0
  3. package/README.md +278 -2
  4. package/SECURITY.md +33 -0
  5. package/dist/cjs/agent.cjs +320 -0
  6. package/dist/cjs/agent.d.cts +239 -0
  7. package/dist/cjs/calls.cjs +277 -0
  8. package/dist/cjs/calls.d.cts +232 -0
  9. package/dist/cjs/contract.cjs +14 -0
  10. package/dist/cjs/contract.d.cts +225 -0
  11. package/dist/cjs/embed.cjs +253 -0
  12. package/dist/cjs/embed.d.cts +187 -0
  13. package/dist/cjs/emitter.cjs +50 -0
  14. package/dist/cjs/emitter.d.cts +22 -0
  15. package/dist/cjs/errors.cjs +28 -0
  16. package/dist/cjs/errors.d.cts +15 -0
  17. package/dist/cjs/events.cjs +97 -0
  18. package/dist/cjs/events.d.cts +97 -0
  19. package/dist/cjs/index.cjs +72 -0
  20. package/dist/cjs/index.d.cts +28 -0
  21. package/dist/cjs/livekit.cjs +56 -0
  22. package/dist/cjs/livekit.d.cts +35 -0
  23. package/dist/cjs/log.cjs +15 -0
  24. package/dist/cjs/log.d.cts +5 -0
  25. package/dist/cjs/react-native.cjs +32 -0
  26. package/dist/cjs/react-native.d.cts +39 -0
  27. package/dist/cjs/reactNativeDesk.cjs +107 -0
  28. package/dist/cjs/reactNativeDesk.d.cts +53 -0
  29. package/dist/cjs/redact.cjs +71 -0
  30. package/dist/cjs/redact.d.cts +21 -0
  31. package/dist/cjs/rest.cjs +286 -0
  32. package/dist/cjs/rest.d.cts +172 -0
  33. package/dist/cjs/session.cjs +159 -0
  34. package/dist/cjs/session.d.cts +101 -0
  35. package/dist/cjs/singleFlight.cjs +49 -0
  36. package/dist/cjs/singleFlight.d.cts +32 -0
  37. package/dist/cjs/url.cjs +39 -0
  38. package/dist/cjs/url.d.cts +13 -0
  39. package/dist/cjs/web.cjs +28 -0
  40. package/dist/cjs/web.d.cts +23 -0
  41. package/dist/cjs/ws.cjs +246 -0
  42. package/dist/cjs/ws.d.cts +134 -0
  43. package/dist/esm/agent.d.ts +240 -0
  44. package/dist/esm/agent.d.ts.map +1 -0
  45. package/dist/esm/agent.js +317 -0
  46. package/dist/esm/agent.js.map +1 -0
  47. package/dist/esm/calls.d.ts +233 -0
  48. package/dist/esm/calls.d.ts.map +1 -0
  49. package/dist/esm/calls.js +271 -0
  50. package/dist/esm/calls.js.map +1 -0
  51. package/dist/esm/contract.d.ts +226 -0
  52. package/dist/esm/contract.d.ts.map +1 -0
  53. package/dist/esm/contract.js +12 -0
  54. package/dist/esm/contract.js.map +1 -0
  55. package/dist/esm/embed.d.ts +188 -0
  56. package/dist/esm/embed.d.ts.map +1 -0
  57. package/dist/esm/embed.js +247 -0
  58. package/dist/esm/embed.js.map +1 -0
  59. package/dist/esm/emitter.d.ts +23 -0
  60. package/dist/esm/emitter.d.ts.map +1 -0
  61. package/dist/esm/emitter.js +47 -0
  62. package/dist/esm/emitter.js.map +1 -0
  63. package/dist/esm/errors.d.ts +16 -0
  64. package/dist/esm/errors.d.ts.map +1 -0
  65. package/dist/esm/errors.js +25 -0
  66. package/dist/esm/errors.js.map +1 -0
  67. package/dist/esm/events.d.ts +98 -0
  68. package/dist/esm/events.d.ts.map +1 -0
  69. package/dist/esm/events.js +90 -0
  70. package/dist/esm/events.js.map +1 -0
  71. package/dist/esm/index.d.ts +29 -0
  72. package/dist/esm/index.d.ts.map +1 -0
  73. package/dist/esm/index.js +29 -0
  74. package/dist/esm/index.js.map +1 -0
  75. package/dist/esm/livekit.d.ts +36 -0
  76. package/dist/esm/livekit.d.ts.map +1 -0
  77. package/dist/esm/livekit.js +54 -0
  78. package/dist/esm/livekit.js.map +1 -0
  79. package/dist/esm/log.d.ts +6 -0
  80. package/dist/esm/log.d.ts.map +1 -0
  81. package/dist/esm/log.js +13 -0
  82. package/dist/esm/log.js.map +1 -0
  83. package/dist/esm/react-native.d.ts +40 -0
  84. package/dist/esm/react-native.d.ts.map +1 -0
  85. package/dist/esm/react-native.js +26 -0
  86. package/dist/esm/react-native.js.map +1 -0
  87. package/dist/esm/reactNativeDesk.d.ts +54 -0
  88. package/dist/esm/reactNativeDesk.d.ts.map +1 -0
  89. package/dist/esm/reactNativeDesk.js +103 -0
  90. package/dist/esm/reactNativeDesk.js.map +1 -0
  91. package/dist/esm/redact.d.ts +22 -0
  92. package/dist/esm/redact.d.ts.map +1 -0
  93. package/dist/esm/redact.js +66 -0
  94. package/dist/esm/redact.js.map +1 -0
  95. package/dist/esm/rest.d.ts +173 -0
  96. package/dist/esm/rest.d.ts.map +1 -0
  97. package/dist/esm/rest.js +281 -0
  98. package/dist/esm/rest.js.map +1 -0
  99. package/dist/esm/session.d.ts +102 -0
  100. package/dist/esm/session.d.ts.map +1 -0
  101. package/dist/esm/session.js +155 -0
  102. package/dist/esm/session.js.map +1 -0
  103. package/dist/esm/singleFlight.d.ts +33 -0
  104. package/dist/esm/singleFlight.d.ts.map +1 -0
  105. package/dist/esm/singleFlight.js +46 -0
  106. package/dist/esm/singleFlight.js.map +1 -0
  107. package/dist/esm/url.d.ts +14 -0
  108. package/dist/esm/url.d.ts.map +1 -0
  109. package/dist/esm/url.js +37 -0
  110. package/dist/esm/url.js.map +1 -0
  111. package/dist/esm/web.d.ts +24 -0
  112. package/dist/esm/web.d.ts.map +1 -0
  113. package/dist/esm/web.js +23 -0
  114. package/dist/esm/web.js.map +1 -0
  115. package/dist/esm/ws.d.ts +135 -0
  116. package/dist/esm/ws.d.ts.map +1 -0
  117. package/dist/esm/ws.js +242 -0
  118. package/dist/esm/ws.js.map +1 -0
  119. package/package.json +117 -4
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ReconnectingSocket = void 0;
4
+ exports.backoffDelay = backoffDelay;
5
+ const emitter_js_1 = require("./emitter.cjs");
6
+ const errors_js_1 = require("./errors.cjs");
7
+ const log_js_1 = require("./log.cjs");
8
+ const redact_js_1 = require("./redact.cjs");
9
+ const url_js_1 = require("./url.cjs");
10
+ /** Exponential backoff with full jitter: between half and all of min(max, initial·2^attempt). */
11
+ function backoffDelay(attempt, initial = 1000, max = 30000, rnd = Math.random) {
12
+ const cap = Math.min(max, initial * 2 ** Math.max(0, attempt));
13
+ return Math.round(cap / 2 + rnd() * (cap / 2));
14
+ }
15
+ const OPEN = 1;
16
+ /**
17
+ * A WebSocket that stays connected: exponential backoff with jitter, auth in the
18
+ * first frame (the token is never in the URL), heartbeat with a dead-connection
19
+ * timeout, and a resume hook carrying the last sequence number seen. Frame shapes
20
+ * are the caller's: pass `authFrame`, `isReady`, `resume`, `seqOf` for the
21
+ * protocol (`/agent/v1/ws` bindings: TODO(S3b)).
22
+ */
23
+ class ReconnectingSocket {
24
+ constructor(o) {
25
+ this.o = o;
26
+ this.ws = null;
27
+ this.st = "idle";
28
+ this.want = false;
29
+ this.authed = false;
30
+ this.attempt = 0;
31
+ this.seq = 0;
32
+ this.lastFrameAt = 0;
33
+ this.reconnectTimer = null;
34
+ this.authTimer = null;
35
+ this.pingTimer = null;
36
+ this.WS = o.WebSocket ?? globalThis.WebSocket;
37
+ this.redactor = o.redactor ?? new redact_js_1.Redactor();
38
+ this.log = (0, log_js_1.safeLogger)(o.log, this.redactor);
39
+ this.events = new emitter_js_1.TypedEmitter((e) => this.log("listener_error", e));
40
+ this.expiredCode = o.closeCodes?.tokenExpired ?? 4001;
41
+ this.revokedCode = o.closeCodes?.revoked ?? 4003;
42
+ }
43
+ get state() { return this.st; }
44
+ /** The highest sequence number received (what `resume` is called with). */
45
+ get lastSeq() { return this.seq; }
46
+ /** Restore a sequence number kept from an earlier run. */
47
+ setLastSeq(n) { if (Number.isFinite(n) && n >= 0)
48
+ this.seq = n; }
49
+ connect() {
50
+ this.want = true;
51
+ if (this.ws || this.reconnectTimer)
52
+ return;
53
+ if (!this.WS) {
54
+ this.fail(new errors_js_1.HainivaAgentError(0, "no_websocket"));
55
+ return;
56
+ }
57
+ void this.open();
58
+ }
59
+ disconnect() {
60
+ this.want = false;
61
+ this.clearTimers();
62
+ const ws = this.ws;
63
+ this.ws = null;
64
+ this.authed = false;
65
+ ws?.close(1000);
66
+ this.setState("closed");
67
+ }
68
+ /** Send a frame on an authenticated socket. False when not open (nothing is queued). */
69
+ send(frame) {
70
+ if (!this.ws || !this.authed || this.ws.readyState !== OPEN)
71
+ return false;
72
+ this.ws.send(this.encode(frame));
73
+ return true;
74
+ }
75
+ /** Hand a refreshed access token to the open socket (needs `tokenFrame`). */
76
+ updateToken(token) {
77
+ this.redactor.add(token);
78
+ return this.o.tokenFrame ? this.send(this.o.tokenFrame(token)) : false;
79
+ }
80
+ encode(f) { return (this.o.encode ?? JSON.stringify)(f); }
81
+ setState(s) {
82
+ if (this.st === s)
83
+ return;
84
+ this.st = s;
85
+ this.events.emit("state", s);
86
+ }
87
+ fail(e) {
88
+ this.log("socket_error", { code: e.code });
89
+ this.events.emit("error", e);
90
+ }
91
+ async open() {
92
+ this.setState(this.attempt ? "reconnecting" : "connecting");
93
+ let token;
94
+ let url;
95
+ try {
96
+ token = await this.o.getToken();
97
+ this.redactor.add(token);
98
+ url = typeof this.o.url === "function" ? await this.o.url() : this.o.url;
99
+ }
100
+ catch (e) {
101
+ this.fail(errors_js_1.HainivaAgentError.from(e, "socket_auth_failed", this.redactor));
102
+ return this.scheduleReconnect();
103
+ }
104
+ if (!this.want)
105
+ return;
106
+ try {
107
+ (0, url_js_1.assertSafeUrl)(url, { kind: "ws", ...(this.o.allowInsecureLocalhost ? { allowInsecureLocalhost: true } : {}) });
108
+ if (token && url.includes(token))
109
+ throw new errors_js_1.HainivaAgentError(0, "credentials_in_url");
110
+ }
111
+ catch (e) {
112
+ // A configuration mistake: retrying cannot fix it.
113
+ this.want = false;
114
+ this.setState("closed");
115
+ return this.fail(errors_js_1.HainivaAgentError.from(e, "invalid_url", this.redactor));
116
+ }
117
+ let ws;
118
+ try {
119
+ ws = new this.WS(url);
120
+ }
121
+ catch (e) {
122
+ this.fail(errors_js_1.HainivaAgentError.from(e, "socket_failed", this.redactor));
123
+ return this.scheduleReconnect();
124
+ }
125
+ this.ws = ws;
126
+ this.authed = false;
127
+ ws.onopen = () => {
128
+ if (this.ws !== ws)
129
+ return;
130
+ // First frame: the credential. Nothing else is sent before the server says ready.
131
+ ws.send(this.encode((this.o.authFrame ?? ((t) => ({ type: "auth", token: t })))(token)));
132
+ this.authTimer = setTimeout(() => {
133
+ if (this.ws === ws && !this.authed) {
134
+ this.log("auth_timeout");
135
+ ws.close(4000, "auth timeout");
136
+ }
137
+ }, this.o.authTimeoutMs ?? 10000);
138
+ };
139
+ ws.onmessage = (ev) => { if (this.ws === ws)
140
+ this.onFrame(ws, typeof ev.data === "string" ? ev.data : String(ev.data)); };
141
+ ws.onerror = () => { };
142
+ ws.onclose = (ev) => {
143
+ if (this.ws !== ws)
144
+ return;
145
+ this.ws = null;
146
+ this.authed = false;
147
+ this.clearTimers();
148
+ this.log("socket_closed", { code: ev.code });
149
+ if (!this.want)
150
+ return this.setState("closed");
151
+ if (ev.code === this.revokedCode) {
152
+ this.want = false;
153
+ this.setState("closed");
154
+ this.events.emit("revoked", { code: ev.code });
155
+ return;
156
+ }
157
+ if (this.o.closeCodes?.fatal?.includes(ev.code)) {
158
+ this.want = false;
159
+ this.setState("closed");
160
+ return this.fail(new errors_js_1.HainivaAgentError(0, `socket_closed_${ev.code}`, {}, this.redactor));
161
+ }
162
+ if (ev.code === this.expiredCode)
163
+ this.o.onTokenRejected?.();
164
+ this.scheduleReconnect();
165
+ };
166
+ }
167
+ onFrame(ws, raw) {
168
+ let f;
169
+ try {
170
+ f = (this.o.decode ?? JSON.parse)(raw);
171
+ }
172
+ catch {
173
+ return;
174
+ }
175
+ this.lastFrameAt = Date.now();
176
+ if (!this.authed) {
177
+ const ready = this.o.isReady ?? ((x) => x?.["type"] === "ready");
178
+ if (!ready(f))
179
+ return; // nothing is delivered before the server accepted the credential
180
+ this.authed = true;
181
+ this.attempt = 0;
182
+ if (this.authTimer) {
183
+ clearTimeout(this.authTimer);
184
+ this.authTimer = null;
185
+ }
186
+ this.startHeartbeat(ws);
187
+ this.setState("open");
188
+ const resume = this.seq > 0 ? this.o.resume?.(this.seq) : null;
189
+ if (resume)
190
+ ws.send(this.encode(resume));
191
+ this.events.emit("ready", f);
192
+ return;
193
+ }
194
+ const seqOf = this.o.seqOf ?? ((x) => { const s = x?.["seq"]; return typeof s === "number" ? s : undefined; });
195
+ const s = seqOf(f);
196
+ if (s !== undefined) {
197
+ if (s <= this.seq)
198
+ return; // a replayed frame already delivered
199
+ this.seq = s;
200
+ }
201
+ this.events.emit("frame", f);
202
+ }
203
+ startHeartbeat(ws) {
204
+ const hb = this.o.heartbeat;
205
+ if (hb === false)
206
+ return;
207
+ const every = hb?.intervalMs ?? 20000;
208
+ const dead = hb?.timeoutMs ?? 45000;
209
+ const frame = hb?.frame ?? (() => ({ type: "ping" }));
210
+ this.pingTimer = setInterval(() => {
211
+ if (this.ws !== ws)
212
+ return;
213
+ if (Date.now() - this.lastFrameAt > dead) {
214
+ this.log("heartbeat_timeout");
215
+ ws.close(4000, "heartbeat timeout");
216
+ return;
217
+ }
218
+ if (ws.readyState === OPEN)
219
+ ws.send(this.encode(frame()));
220
+ }, every);
221
+ }
222
+ scheduleReconnect() {
223
+ if (!this.want)
224
+ return;
225
+ const b = this.o.backoff;
226
+ const d = backoffDelay(this.attempt++, b?.initialMs, b?.maxMs, b?.random);
227
+ this.setState("reconnecting");
228
+ this.reconnectTimer = setTimeout(() => { this.reconnectTimer = null; if (this.want)
229
+ void this.open(); }, d);
230
+ }
231
+ clearTimers() {
232
+ if (this.reconnectTimer) {
233
+ clearTimeout(this.reconnectTimer);
234
+ this.reconnectTimer = null;
235
+ }
236
+ if (this.authTimer) {
237
+ clearTimeout(this.authTimer);
238
+ this.authTimer = null;
239
+ }
240
+ if (this.pingTimer) {
241
+ clearInterval(this.pingTimer);
242
+ this.pingTimer = null;
243
+ }
244
+ }
245
+ }
246
+ exports.ReconnectingSocket = ReconnectingSocket;
@@ -0,0 +1,134 @@
1
+ import { TypedEmitter } from "./emitter.cjs";
2
+ import { HainivaAgentError } from "./errors.cjs";
3
+ import { type Logger } from "./log.cjs";
4
+ import { Redactor } from "./redact.cjs";
5
+ export type ConnectionState = "idle" | "connecting" | "open" | "reconnecting" | "closed";
6
+ /** Exponential backoff with full jitter: between half and all of min(max, initial·2^attempt). */
7
+ export declare function backoffDelay(attempt: number, initial?: number, max?: number, rnd?: () => number): number;
8
+ /** The part of the WebSocket API this client uses (browser, React Native, `ws` in Node). */
9
+ export interface WebSocketLike {
10
+ readonly readyState: number;
11
+ send(data: string): void;
12
+ close(code?: number, reason?: string): void;
13
+ onopen: ((ev: unknown) => void) | null;
14
+ onmessage: ((ev: {
15
+ data: unknown;
16
+ }) => void) | null;
17
+ onclose: ((ev: {
18
+ code: number;
19
+ reason?: string;
20
+ }) => void) | null;
21
+ onerror: ((ev: unknown) => void) | null;
22
+ }
23
+ export type WebSocketCtor = new (url: string) => WebSocketLike;
24
+ type AnyFrame = Record<string, unknown>;
25
+ export interface SocketOptions<In = AnyFrame, Out = AnyFrame> {
26
+ /** wss:// URL, or a function returning it. Never carries a credential (checked). */
27
+ url: string | (() => string | Promise<string>);
28
+ /** A valid access token (e.g. `session.accessToken()`); sent in the first frame. */
29
+ getToken: () => Promise<string>;
30
+ /** The server refused the token (close code `tokenExpired`): e.g. `session.invalidate()`. */
31
+ onTokenRejected?: () => void;
32
+ WebSocket?: WebSocketCtor;
33
+ /** First frame after open. Default `{ type: "auth", token }`. */
34
+ authFrame?: (token: string) => Out;
35
+ /** Whether a frame is the server's "authenticated" answer. Default `type === "ready"`. */
36
+ isReady?: (frame: In) => boolean;
37
+ /** Give up on an open socket that is not ready after this long. Default 10 s. */
38
+ authTimeoutMs?: number;
39
+ /** Keep-alive. `false` turns it off. */
40
+ heartbeat?: false | {
41
+ /** Send `frame()` this often. Default 20 s. */
42
+ intervalMs?: number;
43
+ /** Reconnect when nothing at all was received for this long. Default 45 s. */
44
+ timeoutMs?: number;
45
+ /** Default `{ type: "ping" }`. */
46
+ frame?: () => Out;
47
+ };
48
+ /** Resume hook: the frame to send after `ready` when frames were seen before. */
49
+ resume?: (lastSeq: number) => Out | null;
50
+ /** A frame's sequence number, if it has one. Default: numeric `seq`. */
51
+ seqOf?: (frame: In) => number | undefined;
52
+ /** Frame to hand the server a refreshed access token on an open socket. */
53
+ tokenFrame?: (token: string) => Out;
54
+ encode?: (frame: Out) => string;
55
+ decode?: (raw: string) => In;
56
+ backoff?: {
57
+ initialMs?: number;
58
+ maxMs?: number;
59
+ random?: () => number;
60
+ };
61
+ /**
62
+ * Server close codes. Defaults: tokenExpired 4001 (refresh and reconnect),
63
+ * revoked 4003 (stop). `fatal`: codes reconnecting cannot fix (stop, emit
64
+ * `error` with code `socket_closed_<code>`). Default none.
65
+ */
66
+ closeCodes?: {
67
+ tokenExpired?: number;
68
+ revoked?: number;
69
+ fatal?: number[];
70
+ };
71
+ /** Allow ws://localhost in development. Default false. */
72
+ allowInsecureLocalhost?: boolean;
73
+ log?: Logger;
74
+ redactor?: Redactor;
75
+ }
76
+ export interface SocketEvents<In> {
77
+ state: ConnectionState;
78
+ /** Authenticated (after every (re)connect). */
79
+ ready: In;
80
+ /** Every frame after `ready`, except ones already seen (seq ≤ lastSeq). */
81
+ frame: In;
82
+ /** The server revoked the session: the socket stays closed until connect() is called again. */
83
+ revoked: {
84
+ code: number;
85
+ };
86
+ error: HainivaAgentError;
87
+ }
88
+ /**
89
+ * A WebSocket that stays connected: exponential backoff with jitter, auth in the
90
+ * first frame (the token is never in the URL), heartbeat with a dead-connection
91
+ * timeout, and a resume hook carrying the last sequence number seen. Frame shapes
92
+ * are the caller's: pass `authFrame`, `isReady`, `resume`, `seqOf` for the
93
+ * protocol (`/agent/v1/ws` bindings: TODO(S3b)).
94
+ */
95
+ export declare class ReconnectingSocket<In = AnyFrame, Out = AnyFrame> {
96
+ private readonly o;
97
+ readonly events: TypedEmitter<SocketEvents<In>>;
98
+ private ws;
99
+ private st;
100
+ private want;
101
+ private authed;
102
+ private attempt;
103
+ private seq;
104
+ private lastFrameAt;
105
+ private reconnectTimer;
106
+ private authTimer;
107
+ private pingTimer;
108
+ private readonly WS;
109
+ private readonly redactor;
110
+ private readonly log;
111
+ private readonly expiredCode;
112
+ private readonly revokedCode;
113
+ constructor(o: SocketOptions<In, Out>);
114
+ get state(): ConnectionState;
115
+ /** The highest sequence number received (what `resume` is called with). */
116
+ get lastSeq(): number;
117
+ /** Restore a sequence number kept from an earlier run. */
118
+ setLastSeq(n: number): void;
119
+ connect(): void;
120
+ disconnect(): void;
121
+ /** Send a frame on an authenticated socket. False when not open (nothing is queued). */
122
+ send(frame: Out): boolean;
123
+ /** Hand a refreshed access token to the open socket (needs `tokenFrame`). */
124
+ updateToken(token: string): boolean;
125
+ private encode;
126
+ private setState;
127
+ private fail;
128
+ private open;
129
+ private onFrame;
130
+ private startHeartbeat;
131
+ private scheduleReconnect;
132
+ private clearTimers;
133
+ }
134
+ export {};
@@ -0,0 +1,240 @@
1
+ /**
2
+ * HainivaAgent — one object for a staff member's app: sign-in with your
3
+ * backend's assertion, the REST API, the live event socket and the agent's
4
+ * calls, wired together.
5
+ *
6
+ * const agent = new HainivaAgent({ baseUrl, tenantId, getAssertion, media: joinCallRoom });
7
+ * agent.on("call.ringing", (r) => showRing(r));
8
+ * await agent.connect();
9
+ * await agent.calls.accept(callId); // answers, then joins the room with `media`
10
+ *
11
+ * Transport-agnostic: LiveKit, the DOM and React Native come in through options
12
+ * (`media`, `WebSocket`, `store`), so the core runs in Node too.
13
+ */
14
+ import { CallStore, type CallEffect, type CallsState, type TransferTarget } from "./calls.js";
15
+ import type { AgentAcceptDto, AgentCallEndedData, AgentDeskSessionDto, AgentCallRingingData, AgentConversationDto, AgentDispositionDto, AgentMeDto, AgentMessageDto, AgentPage, AgentPushSubscriptionDto, AgentPushSubscriptionInput, AgentRingDto, AgentSessionDto, SupervisorAgentDto, SupervisorLiveCallDto, SupervisorSuperviseDto } from "./contract.js";
16
+ import { TypedEmitter } from "./emitter.js";
17
+ import { HainivaAgentError } from "./errors.js";
18
+ import { type AgentEventFrame } from "./events.js";
19
+ import { type Logger } from "./log.js";
20
+ import { AgentRestClient, type AgentStatus, type ConversationListQuery, type MediaCredentials, type RequestOptions } from "./rest.js";
21
+ import { SessionManager, type AgentSession, type GetAssertion, type SessionStore } from "./session.js";
22
+ import { type ConnectionState, type WebSocketCtor } from "./ws.js";
23
+ /** A joined media room — what `joinCallRoom` from `/web` or `/react-native` returns. */
24
+ export interface MediaSession {
25
+ leave(): Promise<void>;
26
+ setMuted(muted: boolean): Promise<void>;
27
+ onDisconnected(fn: () => void): () => void;
28
+ }
29
+ /** Join a room: pass `joinCallRoom` from `@hainiva/agent-sdk/web` or `/react-native`. */
30
+ export type JoinMedia = (join: {
31
+ url: string;
32
+ token: string;
33
+ }) => Promise<MediaSession>;
34
+ export interface HainivaAgentOptions {
35
+ /** https://api.hainiva.com (or your organisation's API host). */
36
+ baseUrl: string;
37
+ tenantId: string;
38
+ /** Ask YOUR backend for a fresh assertion — every time; never cache one. */
39
+ getAssertion: GetAssertion;
40
+ /** Where the session is kept. Default in memory (a restart signs in again). */
41
+ store?: SessionStore;
42
+ /** Joins call media. Without it, `calls.accept` answers and returns the credentials only. */
43
+ media?: JoinMedia;
44
+ /** WebSocket implementation (browsers and React Native have one; Node 22 too). */
45
+ WebSocket?: WebSocketCtor;
46
+ fetch?: typeof fetch;
47
+ device?: {
48
+ label?: string;
49
+ platform?: "ios" | "android" | "web" | "other";
50
+ };
51
+ /** Per REST request. Default 15 s. */
52
+ timeoutMs?: number;
53
+ /** How long a ring with no server-known end may ring here. Default 60 s. */
54
+ ringTimeoutMs?: number;
55
+ /** A redacted log of what the SDK does. Nothing is logged without it. */
56
+ log?: Logger;
57
+ /** Local development only (http://localhost, ws://localhost). */
58
+ allowInsecureLocalhost?: boolean;
59
+ }
60
+ export interface HainivaAgentEvents {
61
+ /** Socket state. */
62
+ connection: ConnectionState;
63
+ /** The socket is authenticated (after every reconnect). */
64
+ ready: {
65
+ seq: number;
66
+ };
67
+ "call.ringing": AgentCallRingingData;
68
+ "call.ended": AgentCallEndedData;
69
+ "conversation.assigned": Record<string, unknown>;
70
+ "message.created": Record<string, unknown>;
71
+ "wrapup.required": {
72
+ callId: string;
73
+ conversationId: string;
74
+ deadlineAt: string | null;
75
+ };
76
+ "presence.changed": Record<string, unknown>;
77
+ "supervisor.alert": Record<string, unknown>;
78
+ /** An event name this SDK version does not know yet (the server added one). */
79
+ "event.unknown": AgentEventFrame;
80
+ /** The calls changed (state machine). */
81
+ calls: CallsState;
82
+ /** Something the host must do for a call (stop the native ring, leave media). Handled for media already. */
83
+ callEffect: CallEffect;
84
+ /** Events were missed beyond what the server keeps: state was refetched (rings); refetch your own lists. */
85
+ resync: {
86
+ fromSeq: number;
87
+ };
88
+ /** The session expires within a minute: the SDK is refreshing it. */
89
+ "session.expiring": {
90
+ expiresAt: string;
91
+ };
92
+ /** The session was ended by HAINIVA (sign-out elsewhere, suspended, app paused, key rotated). Call connect() to sign in again. */
93
+ revoked: {
94
+ reason: string;
95
+ };
96
+ error: HainivaAgentError;
97
+ }
98
+ /**
99
+ * The staff member's app: session, REST, socket and calls.
100
+ */
101
+ export declare class HainivaAgent {
102
+ private readonly o;
103
+ readonly events: TypedEmitter<HainivaAgentEvents>;
104
+ readonly session: SessionManager<AgentSession & AgentSessionDto>;
105
+ readonly rest: AgentRestClient;
106
+ readonly callStore: CallStore;
107
+ private socket;
108
+ private meDto;
109
+ private readonly media;
110
+ private readonly offs;
111
+ private readonly redactor;
112
+ private readonly log;
113
+ private readonly ringMs;
114
+ constructor(o: HainivaAgentOptions);
115
+ /** The signed-in member (after connect()). */
116
+ get me(): AgentMeDto | null;
117
+ /** The calls now (the state machine): ringing, connecting, active, held, ended. */
118
+ get callsState(): CallsState;
119
+ get connection(): ConnectionState;
120
+ on<K extends keyof HainivaAgentEvents>(event: K, fn: (payload: HainivaAgentEvents[K]) => void): () => void;
121
+ /** Sign in (or restore the stored session), load who you are and the rings waiting, open the event socket. */
122
+ connect(): Promise<AgentMeDto>;
123
+ /**
124
+ * Close the socket and leave every call's media. `signOut: true` also ends
125
+ * the session on the server (and removes it from the store) — do that when
126
+ * the staff member signs out; unregister this device's push first.
127
+ */
128
+ disconnect(opts?: {
129
+ signOut?: boolean;
130
+ }): Promise<void>;
131
+ setStatus(status: AgentStatus, o?: RequestOptions): Promise<{
132
+ status: AgentStatus;
133
+ }>;
134
+ readonly desk: {
135
+ /** A desk token for the embedded desk (`@hainiva/agent-sdk/embed` mountDesk / `<HainivaDesk/>` call this). */
136
+ session: () => Promise<AgentDeskSessionDto>;
137
+ };
138
+ readonly rings: {
139
+ /** Rings waiting for you now (also fed into the call state). */
140
+ pending: () => Promise<AgentRingDto[]>;
141
+ };
142
+ readonly calls: {
143
+ /**
144
+ * Answer: the server's answer race decides (409 = someone else was
145
+ * faster). With `media`, joins the room and returns the session.
146
+ */
147
+ accept: (callId: string, ring?: {
148
+ from?: string;
149
+ conversationId?: string;
150
+ }) => Promise<{
151
+ accepted: AgentAcceptDto;
152
+ media: MediaSession | null;
153
+ }>;
154
+ decline: (callId: string) => Promise<{
155
+ callId: string;
156
+ declined: true;
157
+ }>;
158
+ /** Hang up here: media is left at once, then the server ends the call. */
159
+ end: (callId: string) => Promise<{
160
+ callId: string;
161
+ ended: true;
162
+ }>;
163
+ hold: (callId: string, on: boolean) => Promise<{
164
+ callId: string;
165
+ onHold: boolean;
166
+ }>;
167
+ mute: (callId: string, muted: boolean) => Promise<void>;
168
+ /** Warm or blind to a colleague, to a queue (always blind), or to a phone number. */
169
+ transfer: (callId: string, target: TransferTarget, mode?: "warm" | "blind") => Promise<{
170
+ callId: string;
171
+ transferred: true;
172
+ }>;
173
+ get: (callId: string) => Promise<import("./contract.js").AgentCallDto>;
174
+ };
175
+ /** Join (or re-join) a call's media room. Without credentials, a fresh LiveKit token is minted. */
176
+ joinMedia(callId: string, creds?: MediaCredentials): Promise<MediaSession>;
177
+ readonly conversations: {
178
+ list: (q?: ConversationListQuery) => Promise<AgentPage<AgentConversationDto>>;
179
+ get: (id: string) => Promise<AgentConversationDto>;
180
+ /** Take it yourself (no agentId) or, a supervisor, give it to a colleague. */
181
+ assign: (id: string, agentId?: string) => Promise<unknown>;
182
+ release: (id: string, note?: string) => Promise<{
183
+ conversationId: string;
184
+ released: true;
185
+ }>;
186
+ transfer: (id: string, to: {
187
+ agentId: string;
188
+ } | {
189
+ queueId: string;
190
+ note?: string;
191
+ }) => Promise<unknown>;
192
+ close: (id: string) => Promise<{
193
+ conversationId: string;
194
+ closed: true;
195
+ }>;
196
+ };
197
+ readonly messages: {
198
+ list: (conversationId: string, q?: {
199
+ after?: string;
200
+ limit?: number;
201
+ }) => Promise<AgentPage<AgentMessageDto>>;
202
+ send: (conversationId: string, text: string) => Promise<Pick<AgentMessageDto, "id" | "conversationId" | "interactionId" | "createdAt">>;
203
+ typing: (conversationId: string) => Promise<{
204
+ ok: true;
205
+ }>;
206
+ };
207
+ /** After a call: a disposition code from dispositions() and notes. */
208
+ wrapUp(callId: string, w?: {
209
+ dispositionCode?: string;
210
+ notes?: string;
211
+ }): Promise<{
212
+ callId: string;
213
+ wrappedUp: true;
214
+ }>;
215
+ dispositions(channel?: string): Promise<AgentDispositionDto[]>;
216
+ readonly supervisor: {
217
+ liveCalls: () => Promise<SupervisorLiveCallDto[]>;
218
+ agents: () => Promise<SupervisorAgentDto[]>;
219
+ /** Listen in (monitor), coach the agent (whisper) or join the call (barge). Join `media` with your adapter. */
220
+ supervise: (callId: string, mode?: "monitor" | "whisper" | "barge") => Promise<SupervisorSuperviseDto>;
221
+ setMode: (callId: string, mode: "monitor" | "whisper" | "barge", supervisorTrackSid?: string) => Promise<{
222
+ callId: string;
223
+ mode: string;
224
+ }>;
225
+ };
226
+ readonly push: {
227
+ /** Register this device's FCM token (Android) or PushKit/APNs token (iPhone) for YOUR app's credentials. */
228
+ register: (sub: AgentPushSubscriptionInput) => Promise<AgentPushSubscriptionDto>;
229
+ unregister: (token: string) => Promise<void>;
230
+ };
231
+ /** A REST call; a final 401 session_revoked tells the app and forgets the session. */
232
+ private guard;
233
+ private onRevoked;
234
+ private openSocket;
235
+ private onFrame;
236
+ /** Rings waiting on the server → the call state (a cold start, a missed push, a resync). */
237
+ private syncRings;
238
+ private onEffect;
239
+ }
240
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/agent.ts"],"names":[],"mappings":"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"}