@babelforce/babelconnect-sdk 0.1.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/dist/client.js ADDED
@@ -0,0 +1,322 @@
1
+ import { createClient } from "@connectrpc/connect";
2
+ import { createGrpcWebTransport } from "@connectrpc/connect-web";
3
+ import { Agent } from "./gen/babelconnect/v1/babelconnect_connect.js";
4
+ import { AddConferenceMember, AnswerCall, CallDirection, CallLifecycle, CallSource, Command, EndConference, Error as BcError, FlagRecording, Hangup, HistoryRequest, SmsThreadRequest, PhonebookRequest, HoldConferenceMember, Hold, KickConferenceMember, LeaveConference, MarkConversationRead, Mute, MuteConferenceMember, PlaceCall, Register, ResetLineStatus, SendDigits, SendSmsRequest, SetAgentNumber, SetConversationOpen, SetDisplayAs, SetPresence, SetWebrtc, StartConference, StartRecording, StopRecording, SetRecordingTags, SubscribeRequest, Transfer, WrapUpCancel, WrapUpExtend, } from "./gen/babelconnect/v1/babelconnect_pb.js";
5
+ import { browserMediaFactory, toRTCIceServers } from "./media.js";
6
+ import { StateCache } from "./state-cache.js";
7
+ /**
8
+ * The TypeScript "dumb renderer" client: opens the `Subscribe`/`Send` gRPC-web
9
+ * split, mirrors the server's `AgentView` in a {@link StateCache}, exposes typed
10
+ * intent senders, and drives a pluggable WebRTC {@link Media} leg. UI binds to
11
+ * {@link subscribe} and dispatches intents — no call/agent logic lives here.
12
+ */
13
+ export class BabelconnectClient {
14
+ opts;
15
+ rpc;
16
+ cache = new StateCache();
17
+ media = new Map();
18
+ answering = new Set();
19
+ abort = new AbortController();
20
+ ready = false;
21
+ pending = [];
22
+ closed = false;
23
+ constructor(opts) {
24
+ this.opts = opts;
25
+ const transport = createGrpcWebTransport({
26
+ baseUrl: opts.serverUrl,
27
+ interceptors: [authInterceptor(opts.token)],
28
+ });
29
+ this.rpc = createClient(Agent, transport);
30
+ }
31
+ /** Open the session and start mirroring server state. */
32
+ static connect(opts) {
33
+ const c = new BabelconnectClient(opts);
34
+ void c.runSubscribe();
35
+ return c;
36
+ }
37
+ /** The current view (deep copy). */
38
+ get view() {
39
+ return this.cache.current;
40
+ }
41
+ /** Register a render callback (fired immediately and on every state update). Returns an unsubscribe fn. */
42
+ subscribe(fn) {
43
+ return this.cache.subscribe(fn);
44
+ }
45
+ /** The first active call, or undefined. */
46
+ activeCall() {
47
+ return this.cache.current.activeCalls[0];
48
+ }
49
+ // --- intents ---
50
+ register(capabilities = ["webrtc"]) {
51
+ this.send(new Command({ command: { case: "register", value: new Register({ capabilities }) } }));
52
+ }
53
+ placeCall(to, opts = {}) {
54
+ this.send(new Command({
55
+ command: {
56
+ case: "placeCall",
57
+ value: new PlaceCall({
58
+ to,
59
+ displayAsTo: opts.displayAsTo ?? "",
60
+ displayAsFrom: opts.displayAsFrom ?? "",
61
+ record: opts.record ?? false,
62
+ session: opts.session ?? {},
63
+ }),
64
+ },
65
+ }));
66
+ }
67
+ /** Manually answer a RINGING call by id (no-op under autoAnswer once already answered). */
68
+ async answerCall(callId) {
69
+ const call = this.cache.current.activeCalls.find((c) => c.id === callId);
70
+ if (call)
71
+ await this.doAnswer(call);
72
+ }
73
+ hangup(callId) {
74
+ this.send(new Command({ command: { case: "hangup", value: new Hangup({ callId }) } }));
75
+ }
76
+ mute(callId, on) {
77
+ this.send(new Command({ command: { case: "mute", value: new Mute({ callId, on }) } }));
78
+ }
79
+ hold(callId, on) {
80
+ this.send(new Command({ command: { case: "hold", value: new Hold({ callId, on }) } }));
81
+ }
82
+ sendDigits(callId, digits) {
83
+ this.send(new Command({ command: { case: "dtmf", value: new SendDigits({ callId, digits }) } }));
84
+ }
85
+ setDisplayAs(number) {
86
+ this.send(new Command({ command: { case: "setDisplayAs", value: new SetDisplayAs({ number }) } }));
87
+ }
88
+ /** Switch presence (the selector): "available" or a configured pause reason (see `AgentInfo.presenceOptions`). */
89
+ setPresence(name) {
90
+ this.send(new Command({ command: { case: "setPresence", value: new SetPresence({ name }) } }));
91
+ }
92
+ /** Transfer to exactly one of `to` (number), `agentId`, or `applicationId`; `warm` = attended. */
93
+ transfer(callId, to, opts = {}) {
94
+ this.send(new Command({
95
+ command: {
96
+ case: "transfer",
97
+ value: new Transfer({
98
+ callId,
99
+ to,
100
+ warm: opts.warm ?? false,
101
+ agentId: opts.agentId ?? "",
102
+ applicationId: opts.applicationId ?? "",
103
+ }),
104
+ },
105
+ }));
106
+ }
107
+ // --- Conferences ---
108
+ startConference(hold = false) {
109
+ this.send(new Command({ command: { case: "startConference", value: new StartConference({ hold }) } }));
110
+ }
111
+ addConferenceMember(opts) {
112
+ this.send(new Command({
113
+ command: {
114
+ case: "addConferenceMember",
115
+ value: new AddConferenceMember({ agentId: opts.agentId ?? "", number: opts.number ?? "" }),
116
+ },
117
+ }));
118
+ }
119
+ kickConferenceMember(memberId) {
120
+ this.send(new Command({ command: { case: "kickConferenceMember", value: new KickConferenceMember({ memberId }) } }));
121
+ }
122
+ holdConferenceMember(memberId, on) {
123
+ this.send(new Command({ command: { case: "holdConferenceMember", value: new HoldConferenceMember({ memberId, on }) } }));
124
+ }
125
+ muteConferenceMember(memberId, on) {
126
+ this.send(new Command({ command: { case: "muteConferenceMember", value: new MuteConferenceMember({ memberId, on }) } }));
127
+ }
128
+ endConference() {
129
+ this.send(new Command({ command: { case: "endConference", value: new EndConference({}) } }));
130
+ }
131
+ leaveConference() {
132
+ this.send(new Command({ command: { case: "leaveConference", value: new LeaveConference({}) } }));
133
+ }
134
+ wrapUpExtend(seconds = 30) {
135
+ this.send(new Command({ command: { case: "wrapUpExtend", value: new WrapUpExtend({ seconds }) } }));
136
+ }
137
+ wrapUpCancel() {
138
+ this.send(new Command({ command: { case: "wrapUpCancel", value: new WrapUpCancel({}) } }));
139
+ }
140
+ resetLineStatus() {
141
+ this.send(new Command({ command: { case: "resetLineStatus", value: new ResetLineStatus({}) } }));
142
+ }
143
+ startRecording(callId) {
144
+ this.send(new Command({ command: { case: "startRecording", value: new StartRecording({ callId }) } }));
145
+ }
146
+ stopRecording(callId) {
147
+ this.send(new Command({ command: { case: "stopRecording", value: new StopRecording({ callId }) } }));
148
+ }
149
+ flagRecording(callId) {
150
+ this.send(new Command({ command: { case: "flagRecording", value: new FlagRecording({ callId }) } }));
151
+ }
152
+ setRecordingTags(callId, tags) {
153
+ this.send(new Command({ command: { case: "setRecordingTags", value: new SetRecordingTags({ callId, tags }) } }));
154
+ }
155
+ setWebrtc(on) {
156
+ this.send(new Command({ command: { case: "setWebrtc", value: new SetWebrtc({ on }) } }));
157
+ }
158
+ setAgentNumber(number) {
159
+ this.send(new Command({ command: { case: "setAgentNumber", value: new SetAgentNumber({ number }) } }));
160
+ }
161
+ /** Send an SMS. `from` may be empty (server picks the default); `session` carries CTI/embedding correlation. */
162
+ sendSms(to, text, opts = {}) {
163
+ this.send(new Command({
164
+ command: {
165
+ case: "sendSms",
166
+ value: new SendSmsRequest({ to, text, from: opts.from ?? "", session: opts.session ?? {} }),
167
+ },
168
+ }));
169
+ }
170
+ /** Open (reopen) or close (resolve) an SMS conversation. */
171
+ setConversationOpen(conversationId, open) {
172
+ this.send(new Command({
173
+ command: { case: "setConversationOpen", value: new SetConversationOpen({ conversationId, open }) },
174
+ }));
175
+ }
176
+ /** Clear the unread count on an SMS conversation (the agent opened its thread). */
177
+ markConversationRead(conversationId) {
178
+ this.send(new Command({
179
+ command: { case: "markConversationRead", value: new MarkConversationRead({ conversationId }) },
180
+ }));
181
+ }
182
+ /** Fetch a page of the agent's call history (the History tab). */
183
+ async getHistory(max = 50, page = 1) {
184
+ const resp = await this.rpc.getHistory(new HistoryRequest({ max, page }));
185
+ return resp.calls;
186
+ }
187
+ /** Fetch the messages of one SMS conversation (the chat thread), oldest first. */
188
+ async getSmsThread(conversationId, max = 50, page = 1) {
189
+ const resp = await this.rpc.getSmsThread(new SmsThreadRequest({ conversationId, max, page }));
190
+ return resp.messages;
191
+ }
192
+ /**
193
+ * Re-pull the agent's merged contacts + recent numbers (the Contacts tab) — an
194
+ * on-demand refresh of the register-time `AgentInfo.phonebook` snapshot.
195
+ * `query` filters by label/number.
196
+ */
197
+ async getPhonebook(max = 200, page = 1, query = "") {
198
+ const resp = await this.rpc.getPhonebook(new PhonebookRequest({ max, page, query }));
199
+ return resp.entries;
200
+ }
201
+ /** Tear down media legs and the connection. */
202
+ async close() {
203
+ this.closed = true;
204
+ this.abort.abort();
205
+ await this.closeAllMedia();
206
+ }
207
+ // --- internals ---
208
+ async runSubscribe() {
209
+ try {
210
+ for await (const u of this.rpc.subscribe(new SubscribeRequest({}), { signal: this.abort.signal })) {
211
+ this.onUpdate(u);
212
+ }
213
+ }
214
+ catch (e) {
215
+ if (!this.closed)
216
+ this.opts.onError?.(new BcError({ code: "disconnected", message: String(e) }));
217
+ }
218
+ }
219
+ onUpdate(u) {
220
+ // First message = the snapshot ⇒ the session is registered server-side, so
221
+ // flush intents queued during connect() (register(), an early dial, …).
222
+ if (!this.ready) {
223
+ this.ready = true;
224
+ const queued = this.pending.splice(0);
225
+ for (const cmd of queued)
226
+ void this.rawSend(cmd);
227
+ }
228
+ if (u.update.case === "error") {
229
+ this.opts.onError?.(u.update.value);
230
+ return;
231
+ }
232
+ if (u.update.case === "patch" && u.update.value.change.case === "notification") {
233
+ this.opts.onNotification?.(u.update.value.change.value);
234
+ return;
235
+ }
236
+ if (this.cache.apply(u))
237
+ this.opts.onGap?.();
238
+ this.reconcile();
239
+ }
240
+ reconcile() {
241
+ const present = new Set();
242
+ const autoAnswer = this.opts.autoAnswer ?? true;
243
+ for (const call of this.cache.current.activeCalls) {
244
+ present.add(call.id);
245
+ // Auto-answer the agent's own dialed OUTBOUND leg. INBOUND calls — and
246
+ // OUTBOUND *callbacks* (a scheduled call to accept) — wait for answerCall().
247
+ if (autoAnswer &&
248
+ call.state === CallLifecycle.RINGING &&
249
+ call.webrtcOffer !== "" &&
250
+ call.direction === CallDirection.OUTBOUND &&
251
+ call.source !== CallSource.CALLBACK) {
252
+ void this.doAnswer(call);
253
+ }
254
+ }
255
+ for (const [id, m] of this.media) {
256
+ if (!present.has(id)) {
257
+ void m.close();
258
+ this.media.delete(id);
259
+ this.answering.delete(id);
260
+ }
261
+ }
262
+ }
263
+ async doAnswer(call) {
264
+ if (this.answering.has(call.id) || this.media.has(call.id))
265
+ return;
266
+ const factory = this.opts.mediaFactory === undefined ? browserMediaFactory : this.opts.mediaFactory;
267
+ if (!factory) {
268
+ this.opts.onError?.(new BcError({ code: "no_media", message: "no media factory configured", callId: call.id }));
269
+ return;
270
+ }
271
+ this.answering.add(call.id);
272
+ try {
273
+ const media = factory(call.id);
274
+ // Thread the server-advertised STUN/TURN servers into the peer (off-host NAT
275
+ // traversal); when none are advertised (in-cluster/LAN) toRTCIceServers returns
276
+ // undefined so the Media's own fallback applies.
277
+ const answerSdp = await media.answer(call.webrtcOffer, toRTCIceServers(call.iceServers));
278
+ this.media.set(call.id, media);
279
+ this.send(new Command({ command: { case: "answer", value: new AnswerCall({ callId: call.id, sdp: answerSdp }) } }));
280
+ }
281
+ catch (e) {
282
+ this.opts.onError?.(new BcError({ code: "media_answer_failed", message: String(e), callId: call.id }));
283
+ }
284
+ finally {
285
+ this.answering.delete(call.id);
286
+ }
287
+ }
288
+ send(cmd) {
289
+ if (this.closed)
290
+ return;
291
+ // Hold intents until the Subscribe stream is live server-side (first snapshot),
292
+ // else the unary Send races stream registration and is rejected.
293
+ if (!this.ready) {
294
+ this.pending.push(cmd);
295
+ return;
296
+ }
297
+ void this.rawSend(cmd);
298
+ }
299
+ async rawSend(cmd) {
300
+ // Fire-and-forget: a command's RESULT (or rejection) arrives as state / an
301
+ // Error on the Subscribe stream. Only a transport failure surfaces here.
302
+ try {
303
+ await this.rpc.send(cmd);
304
+ }
305
+ catch (e) {
306
+ this.opts.onError?.(new BcError({ code: "send_failed", message: String(e) }));
307
+ }
308
+ }
309
+ async closeAllMedia() {
310
+ const all = [...this.media.values()];
311
+ this.media.clear();
312
+ this.answering.clear();
313
+ await Promise.all(all.map((m) => m.close()));
314
+ }
315
+ }
316
+ /** An interceptor that attaches the bearer token to every gRPC-web call. */
317
+ function authInterceptor(token) {
318
+ return (next) => (req) => {
319
+ req.header.set("Authorization", `Bearer ${token}`);
320
+ return next(req);
321
+ };
322
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `@babelforce/babelconnect-sdk/embed` — embed the babelconnect agent app (the
3
+ * server-served Flutter web app) into a host page via an `<iframe>` and a two-way
4
+ * `postMessage` bridge. See the Embedding guide at
5
+ * https://babelforce.github.io/babelconnect-sdk-docs/ for the full protocol.
6
+ * The embedded app talks only to babelconnect-server.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { BabelconnectEmbed } from "@babelforce/babelconnect-sdk/embed";
11
+ * const bc = BabelconnectEmbed.mount({
12
+ * container: document.getElementById("bc")!,
13
+ * serverUrl: "https://agent.example.com",
14
+ * token: await myLogin(),
15
+ * });
16
+ * bc.on("cti.call", (e) => console.log("call", e));
17
+ * bc.calls.dial("+49301234567");
18
+ * ```
19
+ */
20
+ /** Options for {@link BabelconnectEmbed.mount}. */
21
+ export interface EmbedOptions {
22
+ /** Element the iframe is appended to. */
23
+ container: HTMLElement;
24
+ /** babelconnect-server origin (the iframe `src` + the only origin messages are exchanged with). */
25
+ serverUrl: string;
26
+ /** Bearer token, handed to the app via `postMessage` after its `ready` event (never in the URL). */
27
+ token: string;
28
+ /** Optional initial session correlation (also settable later via {@link session}). */
29
+ session?: Record<string, unknown>;
30
+ /** Optional initial shared context (also settable later via {@link context}). */
31
+ context?: Record<string, unknown>;
32
+ /** Path within the app (default `/`). */
33
+ path?: string;
34
+ /** Optional className for the iframe. */
35
+ className?: string;
36
+ }
37
+ /** A handler for an app→host event's `data` payload. */
38
+ export type EmbedEventHandler = (data: unknown) => void;
39
+ /**
40
+ * A mounted embed. Use the verb groups ({@link calls}, {@link session},
41
+ * {@link context}, {@link app}) to drive the app, and {@link on} to receive
42
+ * app→host events (`agent.loaded`, `cti.call`, `cti.error`, …).
43
+ */
44
+ export declare class BabelconnectEmbed {
45
+ private readonly opts;
46
+ private readonly iframe;
47
+ private readonly serverOrigin;
48
+ private readonly handlers;
49
+ private readonly onMessage;
50
+ private disposed;
51
+ private constructor();
52
+ /** Mount the embed: inject the iframe and start the bridge. */
53
+ static mount(opts: EmbedOptions): BabelconnectEmbed;
54
+ /** Click-to-dial: place a call (or `dial=false` to only pre-fill the dialer). */
55
+ readonly calls: {
56
+ dial: (number: string, dial?: boolean) => void;
57
+ };
58
+ /** Attach session correlation; a `number` pre-fills a new SMS. */
59
+ readonly session: {
60
+ set: (args: Record<string, unknown>) => void;
61
+ };
62
+ /** Merge into the persisted shared context carried onto subsequent calls/SMS. */
63
+ readonly context: {
64
+ set: (args: Record<string, unknown>) => void;
65
+ };
66
+ /** Route the agent to a tab: `phone` | `chat` | `history` | `outbound`. */
67
+ readonly app: {
68
+ setTab: (tab: string) => void;
69
+ };
70
+ /** Subscribe to an app→host event. Returns an unsubscribe fn. */
71
+ on(name: string, fn: EmbedEventHandler): () => void;
72
+ /** The underlying iframe (e.g. to adjust sizing). */
73
+ get element(): HTMLIFrameElement;
74
+ /** Remove the iframe and stop listening. */
75
+ dispose(): void;
76
+ private post;
77
+ private handleMessage;
78
+ private emit;
79
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * `@babelforce/babelconnect-sdk/embed` — embed the babelconnect agent app (the
3
+ * server-served Flutter web app) into a host page via an `<iframe>` and a two-way
4
+ * `postMessage` bridge. See the Embedding guide at
5
+ * https://babelforce.github.io/babelconnect-sdk-docs/ for the full protocol.
6
+ * The embedded app talks only to babelconnect-server.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { BabelconnectEmbed } from "@babelforce/babelconnect-sdk/embed";
11
+ * const bc = BabelconnectEmbed.mount({
12
+ * container: document.getElementById("bc")!,
13
+ * serverUrl: "https://agent.example.com",
14
+ * token: await myLogin(),
15
+ * });
16
+ * bc.on("cti.call", (e) => console.log("call", e));
17
+ * bc.calls.dial("+49301234567");
18
+ * ```
19
+ */
20
+ /**
21
+ * A mounted embed. Use the verb groups ({@link calls}, {@link session},
22
+ * {@link context}, {@link app}) to drive the app, and {@link on} to receive
23
+ * app→host events (`agent.loaded`, `cti.call`, `cti.error`, …).
24
+ */
25
+ export class BabelconnectEmbed {
26
+ opts;
27
+ iframe;
28
+ serverOrigin;
29
+ handlers = new Map();
30
+ onMessage;
31
+ disposed = false;
32
+ constructor(opts) {
33
+ this.opts = opts;
34
+ this.serverOrigin = new URL(opts.serverUrl).origin;
35
+ const iframe = document.createElement("iframe");
36
+ iframe.src = opts.serverUrl.replace(/\/+$/, "") + (opts.path ?? "/");
37
+ iframe.allow = "microphone; autoplay";
38
+ iframe.style.border = "0";
39
+ iframe.style.width = "100%";
40
+ iframe.style.height = "100%";
41
+ if (opts.className)
42
+ iframe.className = opts.className;
43
+ this.iframe = iframe;
44
+ this.onMessage = (e) => this.handleMessage(e);
45
+ window.addEventListener("message", this.onMessage);
46
+ opts.container.appendChild(iframe);
47
+ }
48
+ /** Mount the embed: inject the iframe and start the bridge. */
49
+ static mount(opts) {
50
+ return new BabelconnectEmbed(opts);
51
+ }
52
+ /** Click-to-dial: place a call (or `dial=false` to only pre-fill the dialer). */
53
+ calls = {
54
+ dial: (number, dial = true) => this.post("calls", "calls.dial", { number, dial }),
55
+ };
56
+ /** Attach session correlation; a `number` pre-fills a new SMS. */
57
+ session = {
58
+ set: (args) => this.post("session", "session.set", args),
59
+ };
60
+ /** Merge into the persisted shared context carried onto subsequent calls/SMS. */
61
+ context = {
62
+ set: (args) => this.post("context", "context.set", args),
63
+ };
64
+ /** Route the agent to a tab: `phone` | `chat` | `history` | `outbound`. */
65
+ app = {
66
+ setTab: (tab) => this.post("app", "app.setTab", { tab }),
67
+ };
68
+ /** Subscribe to an app→host event. Returns an unsubscribe fn. */
69
+ on(name, fn) {
70
+ let set = this.handlers.get(name);
71
+ if (!set) {
72
+ set = new Set();
73
+ this.handlers.set(name, set);
74
+ }
75
+ set.add(fn);
76
+ return () => set.delete(fn);
77
+ }
78
+ /** The underlying iframe (e.g. to adjust sizing). */
79
+ get element() {
80
+ return this.iframe;
81
+ }
82
+ /** Remove the iframe and stop listening. */
83
+ dispose() {
84
+ if (this.disposed)
85
+ return;
86
+ this.disposed = true;
87
+ window.removeEventListener("message", this.onMessage);
88
+ this.iframe.remove();
89
+ this.handlers.clear();
90
+ }
91
+ post(module, name, args) {
92
+ this.iframe.contentWindow?.postMessage({ type: "connect", module, name, args }, this.serverOrigin);
93
+ }
94
+ handleMessage(e) {
95
+ if (e.origin !== this.serverOrigin)
96
+ return; // only trust the app's origin
97
+ const msg = e.data;
98
+ if (!msg || msg.type !== "bcConnect" || typeof msg.name !== "string")
99
+ return;
100
+ // Token handoff: on the app's `ready`, hand off the bearer token (+ optional
101
+ // initial session/context) over postMessage — never via the iframe URL.
102
+ if (msg.name === "ready") {
103
+ this.post("auth", "auth.set", {
104
+ token: this.opts.token,
105
+ session: this.opts.session,
106
+ context: this.opts.context,
107
+ });
108
+ }
109
+ this.emit(msg.name, msg.data);
110
+ }
111
+ emit(name, data) {
112
+ for (const fn of this.handlers.get(name) ?? [])
113
+ fn(data);
114
+ }
115
+ }
@@ -0,0 +1,132 @@
1
+ import { Ack, AgentView, AuthenticateRequest, Command, GetStateRequest, HistoryRequest, HistoryResponse, Identity, PhonebookRequest, PhonebookResponse, SendSmsRequest, SmsConversation, SmsThreadRequest, SmsThreadResponse, StateUpdate, SubscribeRequest } from "./babelconnect_pb.js";
2
+ import { MethodKind } from "@bufbuild/protobuf";
3
+ /**
4
+ * @generated from service babelconnect.v1.Agent
5
+ */
6
+ export declare const Agent: {
7
+ readonly typeName: "babelconnect.v1.Agent";
8
+ readonly methods: {
9
+ /**
10
+ * Authenticate validates the caller's bearer token and returns the resolved
11
+ * agent identity (id, account, presence).
12
+ *
13
+ * @generated from rpc babelconnect.v1.Agent.Authenticate
14
+ */
15
+ readonly authenticate: {
16
+ readonly name: "Authenticate";
17
+ readonly I: typeof AuthenticateRequest;
18
+ readonly O: typeof Identity;
19
+ readonly kind: MethodKind.Unary;
20
+ };
21
+ /**
22
+ * Session opens the long-lived control stream. The client sends Command
23
+ * intents; the server streams StateUpdate (one snapshot on open, then
24
+ * entity-level patches). UI = f(AgentView).
25
+ *
26
+ * Native clients (Go/Dart desktop/mobile over HTTP/2) use this bidi stream.
27
+ *
28
+ * @generated from rpc babelconnect.v1.Agent.Session
29
+ */
30
+ readonly session: {
31
+ readonly name: "Session";
32
+ readonly I: typeof Command;
33
+ readonly O: typeof StateUpdate;
34
+ readonly kind: MethodKind.BiDiStreaming;
35
+ };
36
+ /**
37
+ * Subscribe + Send are the browser-friendly split of Session: a browser speaks
38
+ * gRPC-web, which cannot client-stream, so the bidi Session is impossible there.
39
+ * Subscribe is the server→client half (the StateUpdate stream); Send is the
40
+ * client→server half (one Command per unary call). Together they are equivalent
41
+ * to Session for a single agent — the server keys both to the agent's one state
42
+ * store. Command rejections still arrive as an Error on the Subscribe stream
43
+ * (never on Send's reply), exactly as on Session.
44
+ *
45
+ * @generated from rpc babelconnect.v1.Agent.Subscribe
46
+ */
47
+ readonly subscribe: {
48
+ readonly name: "Subscribe";
49
+ readonly I: typeof SubscribeRequest;
50
+ readonly O: typeof StateUpdate;
51
+ readonly kind: MethodKind.ServerStreaming;
52
+ };
53
+ /**
54
+ * @generated from rpc babelconnect.v1.Agent.Send
55
+ */
56
+ readonly send: {
57
+ readonly name: "Send";
58
+ readonly I: typeof Command;
59
+ readonly O: typeof Ack;
60
+ readonly kind: MethodKind.Unary;
61
+ };
62
+ /**
63
+ * GetHistory returns the agent's past calls (the History tab). A unary query —
64
+ * history is on-demand reference data, not part of the live AgentView snapshot.
65
+ *
66
+ * @generated from rpc babelconnect.v1.Agent.GetHistory
67
+ */
68
+ readonly getHistory: {
69
+ readonly name: "GetHistory";
70
+ readonly I: typeof HistoryRequest;
71
+ readonly O: typeof HistoryResponse;
72
+ readonly kind: MethodKind.Unary;
73
+ };
74
+ /**
75
+ * GetSmsThread returns the messages of one SMS conversation (the chat thread).
76
+ * A unary query like GetHistory — the live AgentView carries only the
77
+ * SmsConversation summary; the full thread is fetched on demand. conversation_id
78
+ * is a query param (it can be a peer phone number when no conversation id is
79
+ * available), so this stays distinct from the POST /v1/agent/sms send.
80
+ *
81
+ * @generated from rpc babelconnect.v1.Agent.GetSmsThread
82
+ */
83
+ readonly getSmsThread: {
84
+ readonly name: "GetSmsThread";
85
+ readonly I: typeof SmsThreadRequest;
86
+ readonly O: typeof SmsThreadResponse;
87
+ readonly kind: MethodKind.Unary;
88
+ };
89
+ /**
90
+ * GetPhonebook returns the agent's dial-from contacts + recent numbers (the
91
+ * Contacts tab). A unary query like GetHistory: the live AgentView carries a
92
+ * phonebook snapshot frozen at register (AgentInfo.phonebook); this re-pulls the
93
+ * merged contacts+recents list on demand (refresh) so a long-running session can
94
+ * see new contacts. `query` filters by label/number.
95
+ *
96
+ * @generated from rpc babelconnect.v1.Agent.GetPhonebook
97
+ */
98
+ readonly getPhonebook: {
99
+ readonly name: "GetPhonebook";
100
+ readonly I: typeof PhonebookRequest;
101
+ readonly O: typeof PhonebookResponse;
102
+ readonly kind: MethodKind.Unary;
103
+ };
104
+ /**
105
+ * GetState returns the agent's current AgentView as a single unary call — the
106
+ * REST/web "get current state" (the Subscribe stream is the realtime twin). When
107
+ * no live session backs the caller, the server builds the snapshot on demand
108
+ * (agent + presence + wrap-up); live call state requires a stream.
109
+ *
110
+ * @generated from rpc babelconnect.v1.Agent.GetState
111
+ */
112
+ readonly getState: {
113
+ readonly name: "GetState";
114
+ readonly I: typeof GetStateRequest;
115
+ readonly O: typeof AgentView;
116
+ readonly kind: MethodKind.Unary;
117
+ };
118
+ /**
119
+ * SendSms sends an SMS and returns the (upserted) conversation summary. Exposed
120
+ * over REST as POST /v1/agent/sms and as the Command.send_sms intent on the
121
+ * stream — both send the same message.
122
+ *
123
+ * @generated from rpc babelconnect.v1.Agent.SendSms
124
+ */
125
+ readonly sendSms: {
126
+ readonly name: "SendSms";
127
+ readonly I: typeof SendSmsRequest;
128
+ readonly O: typeof SmsConversation;
129
+ readonly kind: MethodKind.Unary;
130
+ };
131
+ };
132
+ };