@mirasoth/soothe-client 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.
@@ -0,0 +1,647 @@
1
+ // src/client.ts
2
+ import { EventEmitter } from "events";
3
+ import WebSocket from "ws";
4
+
5
+ // src/config.ts
6
+ function defaultConfig() {
7
+ return {
8
+ daemonURL: "ws://localhost:8765",
9
+ verbosityLevel: "normal",
10
+ maxRetries: 5,
11
+ reconnectDelay: 2e3,
12
+ heartbeatInterval: 3e4,
13
+ daemonReadyTimeout: 2e4,
14
+ loopStatusTimeout: 6e4,
15
+ subscriptionTimeout: 1e4
16
+ };
17
+ }
18
+ function loadConfigFromEnv() {
19
+ const config = defaultConfig();
20
+ if (typeof process === "undefined") return config;
21
+ const url = process.env.SOOTHE_DAEMON_URL;
22
+ if (url) config.daemonURL = url;
23
+ const verbosity = process.env.SOOTHE_VERBOSITY;
24
+ if (verbosity) config.verbosityLevel = verbosity;
25
+ const retries = process.env.SOOTHE_MAX_RETRIES;
26
+ if (retries) {
27
+ const val = parseInt(retries, 10);
28
+ if (!isNaN(val)) config.maxRetries = val;
29
+ }
30
+ const readyTimeout = process.env.SOOTHE_DAEMON_READY_TIMEOUT_SEC;
31
+ if (readyTimeout) {
32
+ const val = parseInt(readyTimeout, 10);
33
+ if (val > 0) config.daemonReadyTimeout = val * 1e3;
34
+ }
35
+ const statusTimeout = process.env.SOOTHE_LOOP_STATUS_TIMEOUT_SEC;
36
+ if (statusTimeout) {
37
+ const val = parseInt(statusTimeout, 10);
38
+ if (val > 0) config.loopStatusTimeout = val * 1e3;
39
+ }
40
+ const subTimeout = process.env.SOOTHE_SUBSCRIPTION_TIMEOUT_SEC;
41
+ if (subTimeout) {
42
+ const val = parseInt(subTimeout, 10);
43
+ if (val > 0) config.subscriptionTimeout = val * 1e3;
44
+ }
45
+ return config;
46
+ }
47
+
48
+ // src/protocol.ts
49
+ import { randomUUID } from "crypto";
50
+ function encodeMessage(msg) {
51
+ return JSON.stringify(msg) + "\n";
52
+ }
53
+ function decodeMessage(data) {
54
+ if (!data || data.length === 0) return null;
55
+ let parsed;
56
+ try {
57
+ parsed = JSON.parse(data);
58
+ } catch {
59
+ throw new Error(`invalid JSON: ${data}`);
60
+ }
61
+ const type = parsed.type;
62
+ if (!type) return parsed;
63
+ switch (type) {
64
+ // Client → Daemon (loop-first)
65
+ case "loop_input":
66
+ return { ...parsed };
67
+ case "command":
68
+ return { ...parsed };
69
+ case "daemon_status":
70
+ return { ...parsed };
71
+ case "daemon_shutdown":
72
+ return { ...parsed };
73
+ case "config_get":
74
+ return { ...parsed };
75
+ case "loop_new":
76
+ return { ...parsed };
77
+ case "loop_subscribe":
78
+ return { ...parsed };
79
+ case "loop_detach":
80
+ return { ...parsed };
81
+ case "loop_list":
82
+ return { ...parsed };
83
+ case "loop_get":
84
+ return { ...parsed };
85
+ case "loop_tree":
86
+ return { ...parsed };
87
+ case "loop_prune":
88
+ return { ...parsed };
89
+ case "loop_delete":
90
+ return { ...parsed };
91
+ case "loop_reattach":
92
+ return { ...parsed };
93
+ case "skills_list":
94
+ return { ...parsed };
95
+ case "models_list":
96
+ return { ...parsed };
97
+ case "invoke_skill":
98
+ return { ...parsed };
99
+ case "detach":
100
+ return { ...parsed };
101
+ // Daemon → Client
102
+ case "event":
103
+ return { ...parsed };
104
+ case "status": {
105
+ const msg = { ...parsed };
106
+ if (!msg.loop_id && parsed.loopId && typeof parsed.loopId === "string") {
107
+ msg.loop_id = parsed.loopId;
108
+ }
109
+ return msg;
110
+ }
111
+ case "subscription_confirmed":
112
+ return { ...parsed };
113
+ case "error":
114
+ return { ...parsed };
115
+ case "daemon_ready":
116
+ return { ...parsed };
117
+ case "daemon_status_response":
118
+ return { ...parsed };
119
+ case "shutdown_ack":
120
+ return { ...parsed };
121
+ case "loop_new_response":
122
+ return { ...parsed };
123
+ case "loop_subscribe_response":
124
+ return { ...parsed };
125
+ case "loop_detach_response":
126
+ return { ...parsed };
127
+ case "loop_list_response":
128
+ return { ...parsed };
129
+ case "loop_get_response":
130
+ return { ...parsed };
131
+ case "loop_tree_response":
132
+ return { ...parsed };
133
+ case "loop_prune_response":
134
+ return { ...parsed };
135
+ case "loop_delete_response":
136
+ return { ...parsed };
137
+ case "loop_reattach_response":
138
+ return { ...parsed };
139
+ case "history_replay":
140
+ return { ...parsed };
141
+ case "history_replay_complete":
142
+ case "replay_complete":
143
+ return { ...parsed };
144
+ case "loop_reattached":
145
+ return { ...parsed };
146
+ case "config_get_response":
147
+ case "invoke_skill_response":
148
+ return parsed;
149
+ case "skills_list_response":
150
+ return { ...parsed };
151
+ case "models_list_response":
152
+ return { ...parsed };
153
+ default:
154
+ return parsed;
155
+ }
156
+ }
157
+ function splitWirePayload(data) {
158
+ const trimmed = data.trim();
159
+ if (trimmed === "") return [];
160
+ const lines = trimmed.split("\n").map((l) => l.trim()).filter((l) => l !== "");
161
+ return lines.length > 0 ? lines : [data];
162
+ }
163
+ function extractSootheLoopID(msg) {
164
+ if (!msg || typeof msg !== "object") return ["", false];
165
+ const m = msg;
166
+ if (m.type === "status") {
167
+ const id = m.loop_id;
168
+ if (id && id !== "") return [id, true];
169
+ return ["", false];
170
+ }
171
+ if (m.type === "event") {
172
+ const top = m.loop_id;
173
+ if (top && top !== "") return [top, true];
174
+ const data = m.data;
175
+ if (data && typeof data === "object") {
176
+ const dataId = data["loop_id"] ?? data["loopId"];
177
+ if (dataId && dataId !== "") return [dataId, true];
178
+ }
179
+ }
180
+ const generic = m["loop_id"] ?? m["loopId"];
181
+ if (generic && generic !== "") return [generic, true];
182
+ return ["", false];
183
+ }
184
+ function newRequestID() {
185
+ return randomUUID();
186
+ }
187
+ function newLoopInputMessage(loopID, content) {
188
+ return {
189
+ request_id: newRequestID(),
190
+ type: "loop_input",
191
+ loop_id: loopID,
192
+ content,
193
+ autonomous: false
194
+ };
195
+ }
196
+ function newLoopNewMessage(opts) {
197
+ const options = typeof opts === "string" ? { client_workspace: opts } : opts ?? {};
198
+ const clientWorkspace = options.client_workspace ?? options.workspace;
199
+ const msg = {
200
+ request_id: newRequestID(),
201
+ type: "loop_new"
202
+ };
203
+ if (clientWorkspace?.trim()) {
204
+ msg.client_workspace = clientWorkspace.trim();
205
+ }
206
+ if (options.user_id?.trim()) {
207
+ msg.user_id = options.user_id.trim();
208
+ }
209
+ if (options.client_workspace_id?.trim()) {
210
+ msg.client_workspace_id = options.client_workspace_id.trim();
211
+ }
212
+ if (options.is_ephemeral) {
213
+ msg.is_ephemeral = true;
214
+ }
215
+ return msg;
216
+ }
217
+ function newLoopSubscribeMessage(loopID, verbosity, streamDelivery) {
218
+ const msg = {
219
+ request_id: newRequestID(),
220
+ type: "loop_subscribe",
221
+ loop_id: loopID,
222
+ verbosity
223
+ };
224
+ if (streamDelivery) {
225
+ msg.stream_delivery = streamDelivery;
226
+ }
227
+ return msg;
228
+ }
229
+
230
+ // src/client.ts
231
+ var Client = class extends EventEmitter {
232
+ url;
233
+ config;
234
+ ws = null;
235
+ messageBuffer = [];
236
+ resolvers = [];
237
+ constructor(url, config) {
238
+ super();
239
+ this.url = url;
240
+ this.config = config ?? defaultConfig();
241
+ }
242
+ // ---------------------------------------------------------------------------
243
+ // Connection lifecycle
244
+ // ---------------------------------------------------------------------------
245
+ /** Dials the Soothe daemon WebSocket. No WS-level ping/pong (RFC-0013). */
246
+ connect() {
247
+ return new Promise((resolve, reject) => {
248
+ const ws = new WebSocket(this.url, {
249
+ handshakeTimeout: 1e4
250
+ });
251
+ ws.on("open", () => {
252
+ this.ws = ws;
253
+ resolve();
254
+ });
255
+ ws.on("error", (err) => {
256
+ if (!this.ws) {
257
+ reject(new Error(`soothe dial: ${err.message}`));
258
+ }
259
+ });
260
+ ws.on("message", (data) => {
261
+ const text = data.toString();
262
+ for (const frame of splitWirePayload(text)) {
263
+ try {
264
+ const msg = decodeMessage(frame);
265
+ if (msg !== null) {
266
+ this.messageBuffer.push(msg);
267
+ this.emit("message", msg);
268
+ const resolver = this.resolvers.shift();
269
+ if (resolver) resolver(msg);
270
+ }
271
+ } catch {
272
+ }
273
+ }
274
+ });
275
+ ws.on("close", () => {
276
+ this.ws = null;
277
+ this.emit("close");
278
+ for (const resolver of this.resolvers) {
279
+ resolver(null);
280
+ }
281
+ this.resolvers = [];
282
+ });
283
+ });
284
+ }
285
+ /** Shuts down the WebSocket connection. */
286
+ close() {
287
+ if (!this.ws) return;
288
+ try {
289
+ this.ws.close(1e3, "");
290
+ } catch {
291
+ }
292
+ this.ws = null;
293
+ }
294
+ /** Returns whether the client has an active WebSocket connection. */
295
+ isConnected() {
296
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
297
+ }
298
+ // ---------------------------------------------------------------------------
299
+ // Core messaging
300
+ // ---------------------------------------------------------------------------
301
+ /** Serializes msg as JSON and sends it as a WebSocket text frame. */
302
+ sendMessage(msg) {
303
+ return new Promise((resolve, reject) => {
304
+ if (!this.ws) {
305
+ reject(new Error("soothe: not connected"));
306
+ return;
307
+ }
308
+ const payload = JSON.stringify(msg);
309
+ this.ws.send(payload, (err) => {
310
+ if (err) reject(err);
311
+ else resolve();
312
+ });
313
+ });
314
+ }
315
+ /** Returns an async iterable of decoded messages. Ends when connection closes. */
316
+ async *receiveMessages(signal) {
317
+ while (true) {
318
+ if (signal?.aborted) return;
319
+ while (this.messageBuffer.length > 0) {
320
+ const msg2 = this.messageBuffer.shift();
321
+ yield msg2;
322
+ }
323
+ const msg = await new Promise((resolve) => {
324
+ if (!this.ws) {
325
+ resolve(null);
326
+ return;
327
+ }
328
+ this.resolvers.push(resolve);
329
+ });
330
+ if (msg === null) return;
331
+ yield msg;
332
+ }
333
+ }
334
+ /** Reads a single event from the daemon. Returns null on connection close. */
335
+ async readEvent() {
336
+ if (this.messageBuffer.length > 0) {
337
+ const msg2 = this.messageBuffer.shift();
338
+ return msg2;
339
+ }
340
+ if (!this.ws) return null;
341
+ const msg = await new Promise((resolve) => {
342
+ this.resolvers.push(resolve);
343
+ });
344
+ if (msg === null) return null;
345
+ return msg;
346
+ }
347
+ /** Reads a single event with a timeout. Returns null on timeout or connection close. */
348
+ readEventWithTimeout(timeout) {
349
+ if (this.messageBuffer.length > 0) {
350
+ const msg = this.messageBuffer.shift();
351
+ return Promise.resolve(msg);
352
+ }
353
+ if (!this.ws) return Promise.resolve(null);
354
+ return new Promise((resolve) => {
355
+ const timer = setTimeout(() => {
356
+ const idx = this.resolvers.indexOf(resolver);
357
+ if (idx >= 0) this.resolvers.splice(idx, 1);
358
+ resolve(null);
359
+ }, timeout);
360
+ const resolver = (val) => {
361
+ clearTimeout(timer);
362
+ resolve(val);
363
+ };
364
+ this.resolvers.push(resolver);
365
+ });
366
+ }
367
+ // ---------------------------------------------------------------------------
368
+ // High-level API methods (Loop-first, RFC-503)
369
+ // ---------------------------------------------------------------------------
370
+ /** Sends user input to the daemon (loop_input; requires loopID). */
371
+ sendInput(text, options) {
372
+ const loopId = (options?.loopID ?? "").trim();
373
+ if (!loopId) {
374
+ return Promise.reject(new Error("sendInput requires options.loopID"));
375
+ }
376
+ const payload = {
377
+ type: "loop_input",
378
+ loop_id: loopId,
379
+ content: text,
380
+ autonomous: options?.autonomous ?? false
381
+ };
382
+ if (options?.maxIterations !== void 0) payload.max_iterations = options.maxIterations;
383
+ if (options?.subagent) payload.preferred_subagent = options.subagent;
384
+ if (options?.interactive) payload.interactive = true;
385
+ if (options?.model) payload.model = options.model;
386
+ if (options?.modelParams) payload.model_params = options.modelParams;
387
+ if (options?.attachments) payload.attachments = options.attachments;
388
+ return this.sendMessage(payload);
389
+ }
390
+ /** Sends a slash command to the daemon. */
391
+ sendCommand(cmd) {
392
+ return this.sendMessage({ type: "command", cmd });
393
+ }
394
+ // ---------------------------------------------------------------------------
395
+ // Loop lifecycle methods (RFC-503)
396
+ // ---------------------------------------------------------------------------
397
+ /** Requests the daemon to create a new AgentLoop. */
398
+ sendLoopNew(opts) {
399
+ return this.sendMessage(newLoopNewMessage(opts));
400
+ }
401
+ /** Subscribes to events for a loop. */
402
+ sendLoopSubscribe(loopID, verbosity, streamDelivery) {
403
+ const msg = newLoopSubscribeMessage(loopID, verbosity);
404
+ if (streamDelivery) {
405
+ msg.stream_delivery = streamDelivery;
406
+ }
407
+ return this.sendMessage(msg);
408
+ }
409
+ /** Detaches from a loop (keeps loop running). */
410
+ sendLoopDetach(loopID, requestID) {
411
+ return this.sendMessage({
412
+ type: "loop_detach",
413
+ loop_id: loopID,
414
+ request_id: requestID ?? newRequestID()
415
+ });
416
+ }
417
+ /** Notifies the daemon that this client is detaching. */
418
+ sendDetach() {
419
+ return this.sendMessage({ type: "detach" });
420
+ }
421
+ /** Sends the daemon_ready handshake message. */
422
+ sendDaemonReady() {
423
+ return this.sendMessage({ type: "daemon_ready" });
424
+ }
425
+ /** Requests daemon status check. */
426
+ sendDaemonStatus(requestID) {
427
+ return this.sendMessage({
428
+ type: "daemon_status",
429
+ request_id: requestID ?? newRequestID()
430
+ });
431
+ }
432
+ /** Requests daemon shutdown. */
433
+ sendDaemonShutdown(requestID) {
434
+ return this.sendMessage({
435
+ type: "daemon_shutdown",
436
+ request_id: requestID ?? newRequestID()
437
+ });
438
+ }
439
+ /** Requests a config section from the daemon. */
440
+ sendConfigGet(section, requestID) {
441
+ return this.sendMessage({
442
+ type: "config_get",
443
+ section,
444
+ request_id: requestID ?? newRequestID()
445
+ });
446
+ }
447
+ // ---------------------------------------------------------------------------
448
+ // Loop management RPC methods (RFC-504)
449
+ // ---------------------------------------------------------------------------
450
+ /** Requests the persisted loop list. */
451
+ sendLoopList(filter, limit, requestID) {
452
+ const msg = {
453
+ type: "loop_list",
454
+ request_id: requestID ?? newRequestID()
455
+ };
456
+ if (filter) msg.filter = filter;
457
+ if (limit !== void 0) msg.limit = limit;
458
+ return this.sendMessage(msg);
459
+ }
460
+ /** Requests detailed loop metadata. */
461
+ sendLoopGet(loopID, verbose, requestID) {
462
+ const msg = {
463
+ type: "loop_get",
464
+ loop_id: loopID,
465
+ request_id: requestID ?? newRequestID()
466
+ };
467
+ if (verbose) msg.verbose = verbose;
468
+ return this.sendMessage(msg);
469
+ }
470
+ /** Requests loop tree visualization. */
471
+ sendLoopTree(loopID, format, requestID) {
472
+ const msg = {
473
+ type: "loop_tree",
474
+ loop_id: loopID,
475
+ request_id: requestID ?? newRequestID()
476
+ };
477
+ if (format) msg.format = format;
478
+ return this.sendMessage(msg);
479
+ }
480
+ /** Requests pruning of old failed branches. */
481
+ sendLoopPrune(loopID, retentionDays, dryRun, requestID) {
482
+ const msg = {
483
+ type: "loop_prune",
484
+ loop_id: loopID,
485
+ request_id: requestID ?? newRequestID()
486
+ };
487
+ if (retentionDays !== void 0) msg.retention_days = retentionDays;
488
+ if (dryRun !== void 0) msg.dry_run = dryRun;
489
+ return this.sendMessage(msg);
490
+ }
491
+ /** Requests loop deletion. */
492
+ sendLoopDelete(loopID, requestID) {
493
+ return this.sendMessage({
494
+ type: "loop_delete",
495
+ loop_id: loopID,
496
+ request_id: requestID ?? newRequestID()
497
+ });
498
+ }
499
+ /** Requests reattachment to a loop with history replay. */
500
+ sendLoopReattach(loopID, requestID) {
501
+ return this.sendMessage({
502
+ type: "loop_reattach",
503
+ loop_id: loopID,
504
+ request_id: requestID ?? newRequestID()
505
+ });
506
+ }
507
+ // ---------------------------------------------------------------------------
508
+ // Skills and models
509
+ // ---------------------------------------------------------------------------
510
+ /** Requests the skills catalog (RFC-400). */
511
+ sendSkillsList(requestID) {
512
+ return this.sendMessage({
513
+ type: "skills_list",
514
+ request_id: requestID ?? newRequestID()
515
+ });
516
+ }
517
+ /** Requests the models catalog (RFC-400). */
518
+ sendModelsList(requestID) {
519
+ return this.sendMessage({
520
+ type: "models_list",
521
+ request_id: requestID ?? newRequestID()
522
+ });
523
+ }
524
+ /** Invokes a skill on the daemon (RFC-400). */
525
+ sendInvokeSkill(skill, args, requestID) {
526
+ const msg = {
527
+ type: "invoke_skill",
528
+ skill,
529
+ request_id: requestID ?? newRequestID()
530
+ };
531
+ if (args) msg.args = args;
532
+ return this.sendMessage(msg);
533
+ }
534
+ // ---------------------------------------------------------------------------
535
+ // Request-Response pattern
536
+ // ---------------------------------------------------------------------------
537
+ /** Sends a request with a unique request_id and waits for a matching response. */
538
+ async requestResponse(payload, responseType, timeout) {
539
+ const rid = newRequestID();
540
+ payload.request_id = rid;
541
+ await this.sendMessage(payload);
542
+ const deadline = Date.now() + timeout;
543
+ while (Date.now() < deadline) {
544
+ const remaining = deadline - Date.now();
545
+ if (remaining <= 0) break;
546
+ const ev = await this.readEventWithTimeout(remaining);
547
+ if (ev === null) {
548
+ break;
549
+ }
550
+ const evRid = ev.request_id;
551
+ if (evRid !== rid) continue;
552
+ const typ = ev.type;
553
+ if (typ === "error") {
554
+ const msg = ev.message ?? "unknown error";
555
+ throw new Error(`daemon error: ${msg}`);
556
+ }
557
+ if (typ === responseType) {
558
+ return ev;
559
+ }
560
+ }
561
+ throw new Error(`timeout after ${timeout}ms waiting for ${responseType}`);
562
+ }
563
+ // ---------------------------------------------------------------------------
564
+ // Convenience RPC methods
565
+ // ---------------------------------------------------------------------------
566
+ /** Requests the skills catalog and waits for the response. */
567
+ listSkills(timeout) {
568
+ return this.requestResponse({ type: "skills_list" }, "skills_list_response", timeout ?? 15e3);
569
+ }
570
+ /** Requests the models catalog and waits for the response. */
571
+ listModels(timeout) {
572
+ return this.requestResponse({ type: "models_list" }, "models_list_response", timeout ?? 15e3);
573
+ }
574
+ /** Invokes a skill on the daemon host and receives echo (RFC-400). */
575
+ invokeSkill(skill, args, timeout) {
576
+ return this.requestResponse({ type: "invoke_skill", skill, args }, "invoke_skill_response", timeout ?? 12e4);
577
+ }
578
+ /** Requests loop list and waits for response. */
579
+ listLoops(timeout) {
580
+ return this.requestResponse({ type: "loop_list" }, "loop_list_response", timeout ?? 15e3);
581
+ }
582
+ /** Requests loop details and waits for response. */
583
+ getLoop(loopID, timeout) {
584
+ return this.requestResponse({ type: "loop_get", loop_id: loopID }, "loop_get_response", timeout ?? 15e3);
585
+ }
586
+ /** Requests loop tree and waits for response. */
587
+ getLoopTree(loopID, timeout) {
588
+ return this.requestResponse({ type: "loop_tree", loop_id: loopID }, "loop_tree_response", timeout ?? 15e3);
589
+ }
590
+ /** Requests loop deletion and waits for response. */
591
+ deleteLoop(loopID, timeout) {
592
+ return this.requestResponse({ type: "loop_delete", loop_id: loopID }, "loop_delete_response", timeout ?? 15e3);
593
+ }
594
+ // ---------------------------------------------------------------------------
595
+ // Wait helpers
596
+ // ---------------------------------------------------------------------------
597
+ /** Reads events until a daemon_ready with state == "ready". */
598
+ async waitForDaemonReady(timeout) {
599
+ const t = timeout ?? 1e4;
600
+ const deadline = Date.now() + t;
601
+ while (Date.now() < deadline) {
602
+ const remaining = deadline - Date.now();
603
+ if (remaining <= 0) break;
604
+ const ev = await this.readEventWithTimeout(remaining);
605
+ if (ev === null) break;
606
+ if (ev.type !== "daemon_ready") continue;
607
+ if (ev.state === "ready") return ev;
608
+ const msg = ev.message ?? `daemon state is ${ev.state}`;
609
+ throw new Error(`daemon not ready: ${msg}`);
610
+ }
611
+ throw new Error(`timeout after ${t}ms waiting for daemon_ready`);
612
+ }
613
+ /** Waits for subscription confirmation matching loop id. */
614
+ async waitForSubscriptionConfirmed(loopID, _verbosity, timeout) {
615
+ const t = timeout ?? 5e3;
616
+ const deadline = Date.now() + t;
617
+ while (Date.now() < deadline) {
618
+ const remaining = deadline - Date.now();
619
+ if (remaining <= 0) break;
620
+ const ev = await this.readEventWithTimeout(remaining);
621
+ if (ev === null) break;
622
+ if (ev.type === "loop_subscribe_response" && ev.success === true) {
623
+ if (String(ev.loop_id ?? "") === loopID) return;
624
+ continue;
625
+ }
626
+ if (ev.type !== "subscription_confirmed") continue;
627
+ const lid = String(ev.loop_id ?? "");
628
+ if (lid === loopID) return;
629
+ }
630
+ throw new Error(`timeout after ${t}ms waiting for subscription_confirmed`);
631
+ }
632
+ };
633
+
634
+ export {
635
+ defaultConfig,
636
+ loadConfigFromEnv,
637
+ encodeMessage,
638
+ decodeMessage,
639
+ splitWirePayload,
640
+ extractSootheLoopID,
641
+ newRequestID,
642
+ newLoopInputMessage,
643
+ newLoopNewMessage,
644
+ newLoopSubscribeMessage,
645
+ Client
646
+ };
647
+ //# sourceMappingURL=chunk-OMAC7LA7.js.map