cloudcruise 1.0.0 → 1.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,400 @@
1
+ import { EventType } from '../events/types.js';
2
+ import { AsyncEventQueue } from '../utils/asyncQueue.js';
3
+ import { SimpleEventEmitter } from '../utils/events.js';
4
+ /**
5
+ * Default error reporter for the recovery helpers. Writes to console.error
6
+ * so submission failures surface in customer logs even if no onError
7
+ * callback is provided. The runtime SimpleEventEmitter ignores async-handler
8
+ * returns, so without this default a rejection from /new_input_variables
9
+ * would become a silent unhandled rejection.
10
+ */
11
+ function defaultRecoverySubmitErrorLog(operation) {
12
+ return (err) => {
13
+ try {
14
+ const msg = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
15
+ console.error(`[CloudCruise SDK] ${operation} failed during recovery: ${msg}`);
16
+ }
17
+ catch {
18
+ // never throw from the error reporter
19
+ }
20
+ };
21
+ }
22
+ /**
23
+ * Helper for `verbose: true` mode on the recovery helpers. Writes a single
24
+ * line to console.error so customers can watch the recovery loop without
25
+ * instrumenting their own decider.
26
+ */
27
+ function verboseLog(operation, message) {
28
+ try {
29
+ console.error(`[CloudCruise SDK verbose] ${operation}: ${message}`);
30
+ }
31
+ catch {
32
+ // never throw
33
+ }
34
+ }
35
+ export class RunsClient {
36
+ makeRequest;
37
+ workflows;
38
+ connectionManager;
39
+ constructor(connectionManager, makeRequest, workflows) {
40
+ this.makeRequest = makeRequest;
41
+ this.workflows = workflows;
42
+ this.connectionManager = connectionManager;
43
+ }
44
+ /**
45
+ * Queues a new run and returns a RunHandle.
46
+ * The handle exposes sessionId immediately and subscribes to SSE under the hood.
47
+ */
48
+ async start(request, options) {
49
+ if (this.workflows) {
50
+ await this.workflows.validateWorkflowInput(request.workflow_id, request.run_input_variables);
51
+ }
52
+ // Ensure client_id and connection are ready to avoid missing early events
53
+ const clientId = await this.connectionManager.ensureClientId();
54
+ await this.connectionManager.connectIfNeeded();
55
+ request.client_id = clientId;
56
+ const { session_id } = await this.makeRequest('POST', '/run', request);
57
+ return this.subscribeToSession(session_id, options);
58
+ }
59
+ /**
60
+ * Subscribes to SSE events for a given session. Returns a handle with helpers.
61
+ */
62
+ subscribeToSession(sessionId, options) {
63
+ const emitter = new SimpleEventEmitter();
64
+ const stream = new AsyncEventQueue();
65
+ let ended = false;
66
+ let closed = false;
67
+ let sub = null;
68
+ const reconnectCfg = {
69
+ enabled: options?.reconnect?.enabled ?? true,
70
+ delays: options?.reconnect?.delays ?? [1000, 3000, 10000],
71
+ };
72
+ const isTerminalEvent = (status) => status === EventType.ExecutionSuccess ||
73
+ status === EventType.ExecutionFailed ||
74
+ status === EventType.ExecutionStopped;
75
+ const emit = (event, payload) => {
76
+ emitter.emit(event, payload);
77
+ // Mirror only SSE messages to 'message' for catch-all consumers
78
+ if (event === 'run.event' || event === 'ping') {
79
+ emitter.emit('message', payload);
80
+ }
81
+ };
82
+ const endAndCleanup = (status) => {
83
+ if (ended)
84
+ return;
85
+ ended = true;
86
+ closed = true;
87
+ try {
88
+ sub?.close();
89
+ }
90
+ catch { }
91
+ emit('end', { type: status });
92
+ stream.close();
93
+ emitter.clear();
94
+ };
95
+ const connect = () => {
96
+ sub = this.connectionManager.subscribe(sessionId, { signal: options?.signal });
97
+ const s = sub;
98
+ s.on('open', () => emit('open', undefined));
99
+ s.on('ping', (evt) => emit('ping', evt));
100
+ s.on('run.event', (msg) => {
101
+ const sseMsg = msg;
102
+ if (sseMsg.event !== 'run.event')
103
+ return;
104
+ stream.push(sseMsg);
105
+ emit('run.event', sseMsg);
106
+ // Emit typed per-event key for better DX
107
+ try {
108
+ emitter.emit(sseMsg.data.event, sseMsg);
109
+ }
110
+ catch { }
111
+ const eventType = sseMsg.data.event;
112
+ if (typeof eventType === 'string' && isTerminalEvent(eventType)) {
113
+ endAndCleanup(eventType);
114
+ }
115
+ });
116
+ s.on('error', (err) => {
117
+ emit('error', err);
118
+ if (!reconnectCfg.enabled || ended || closed)
119
+ return;
120
+ (async () => {
121
+ for (const base of reconnectCfg.delays) {
122
+ if (ended || closed)
123
+ return;
124
+ await new Promise(r => setTimeout(r, base));
125
+ if (ended || closed)
126
+ return;
127
+ try {
128
+ const snapshot = await this.getResults(sessionId);
129
+ const status = snapshot?.status;
130
+ if (isTerminalEvent(status)) {
131
+ endAndCleanup(status);
132
+ return;
133
+ }
134
+ }
135
+ catch { }
136
+ emit('reconnect', { attemptDelayMs: base });
137
+ return; // manager handles reconnect of mux
138
+ }
139
+ })();
140
+ });
141
+ s.on('reconnect', (e) => emit('reconnect', e));
142
+ s.on('end', (e) => {
143
+ const t = e?.type;
144
+ if (t && typeof t === 'string' && isTerminalEvent(t)) {
145
+ endAndCleanup(t);
146
+ }
147
+ else {
148
+ // End without explicit type; still clean up
149
+ endAndCleanup(EventType.ExecutionStopped);
150
+ }
151
+ });
152
+ };
153
+ connect();
154
+ const client = this;
155
+ const handle = {
156
+ sessionId,
157
+ on: (event, handler) => emitter.on(event, handler),
158
+ async wait() {
159
+ if (ended) {
160
+ return await client.getResults(sessionId);
161
+ }
162
+ return await new Promise((resolve, reject) => {
163
+ const offEnd = handle.on('end', async () => {
164
+ offErr();
165
+ try {
166
+ const result = await client.getResults(sessionId);
167
+ resolve(result);
168
+ }
169
+ catch (e) {
170
+ reject(e);
171
+ }
172
+ });
173
+ const offErr = handle.on('error', (e) => {
174
+ offEnd();
175
+ reject(e instanceof Error ? e : new Error('SSE error'));
176
+ });
177
+ });
178
+ },
179
+ close() {
180
+ closed = true;
181
+ try {
182
+ sub?.close();
183
+ }
184
+ catch { }
185
+ stream.close();
186
+ emitter.clear();
187
+ },
188
+ async *[Symbol.asyncIterator]() {
189
+ for await (const msg of stream) {
190
+ yield msg;
191
+ }
192
+ },
193
+ };
194
+ return handle;
195
+ }
196
+ /**
197
+ * Submits user interaction data during an active run
198
+ * @param sessionId - The unique identifier for the workflow execution session
199
+ * @param data - User input data as key-value pairs
200
+ */
201
+ async submitUserInteraction(sessionId, data) {
202
+ const path = `/run/${sessionId}/user_interaction`;
203
+ await this.makeRequest('POST', path, data);
204
+ }
205
+ /**
206
+ * Responds to an execution.input_required event whose reason is
207
+ * "non_dismissible_popup" by picking one of the CTA buttons surfaced in
208
+ * popup_context.available_actions. The backend dispatches a synthetic
209
+ * click on the chosen button and resumes the workflow.
210
+ *
211
+ * Only valid while the session is waiting for input. The backing endpoint
212
+ * returns 400 if the wait already expired (the workspace setting
213
+ * input_required_timeout_seconds, default 15s, max 300s).
214
+ *
215
+ * @param sessionId - The session waiting for input.
216
+ * @param actionId - One of the ids in popup_context.available_actions.
217
+ */
218
+ async submitModalAction(sessionId, actionId) {
219
+ const path = `/run/${sessionId}/new_input_variables`;
220
+ await this.makeRequest('POST', path, { modal_action: actionId });
221
+ }
222
+ /**
223
+ * Responds to an execution.input_required event whose reason is
224
+ * "input_required", "incorrect_form_input", or "multiple_matching_results"
225
+ * by supplying the corrected/required input variables. Backend resumes from
226
+ * the appropriate recovery node with the new values substituted in.
227
+ *
228
+ * Mutually exclusive with submitModalAction at the endpoint level.
229
+ *
230
+ * @param sessionId - The session waiting for input.
231
+ * @param inputVariables - Mapping of variable name to new value.
232
+ */
233
+ async submitInputVariables(sessionId, inputVariables) {
234
+ const path = `/run/${sessionId}/new_input_variables`;
235
+ await this.makeRequest('POST', path, { input_variables: inputVariables });
236
+ }
237
+ /**
238
+ * Registers a listener that auto-responds ONLY to non-dismissible modal
239
+ * input_required events (reason === "non_dismissible_popup"). The decider
240
+ * receives the popup_context and must return one of the action ids in
241
+ * popup_context.available_actions.
242
+ *
243
+ * The SDK never picks an action on its own. The customer's decider IS the
244
+ * decision point. If decider throws, the listener swallows it and skips
245
+ * submission; the backend's input wait will time out naturally.
246
+ *
247
+ * Other input_required reasons (incorrect_form_input, etc.) are ignored
248
+ * here and should be routed to onInputVariablesRequired.
249
+ *
250
+ * @returns An unsubscribe callable.
251
+ */
252
+ onPopupDecisionRequired(handle, decider, onError, options) {
253
+ const verbose = options?.verbose === true;
254
+ const reportError = (err) => {
255
+ const reporter = onError ?? defaultRecoverySubmitErrorLog('submitModalAction');
256
+ try {
257
+ reporter(err);
258
+ }
259
+ catch {
260
+ // defense: a buggy onError must not crash the event loop
261
+ }
262
+ };
263
+ if (verbose)
264
+ verboseLog('onPopupDecisionRequired', 'listener registered');
265
+ const listener = async (event) => {
266
+ const payload = (event?.data?.payload ?? event?.payload);
267
+ if (!payload || payload.reason !== 'non_dismissible_popup' || !payload.popup_context) {
268
+ if (verbose) {
269
+ verboseLog('onPopupDecisionRequired', `skipping reason=${payload?.reason ?? 'undefined'}`);
270
+ }
271
+ return;
272
+ }
273
+ if (verbose) {
274
+ const attempt = payload.popup_context.retry?.attempt;
275
+ const actions = payload.popup_context.available_actions?.map((a) => a.id) ?? [];
276
+ verboseLog('onPopupDecisionRequired', `event received attempt=${attempt} actions=${JSON.stringify(actions)}`);
277
+ }
278
+ let actionId;
279
+ try {
280
+ actionId = await decider(payload.popup_context);
281
+ }
282
+ catch (e) {
283
+ if (verbose)
284
+ verboseLog('onPopupDecisionRequired', `decider raised: ${e}`);
285
+ return;
286
+ }
287
+ if (typeof actionId !== 'string' || actionId.length === 0) {
288
+ if (verbose)
289
+ verboseLog('onPopupDecisionRequired', `decider returned non-string/empty (${JSON.stringify(actionId)}); skipping`);
290
+ return;
291
+ }
292
+ const sid = payload.session_id || handle.sessionId;
293
+ if (verbose)
294
+ verboseLog('onPopupDecisionRequired', `submitting modal_action=${JSON.stringify(actionId)} for session=${sid}`);
295
+ try {
296
+ await this.submitModalAction(sid, actionId);
297
+ if (verbose)
298
+ verboseLog('onPopupDecisionRequired', `submit ok for action=${JSON.stringify(actionId)}`);
299
+ }
300
+ catch (err) {
301
+ reportError(err);
302
+ }
303
+ };
304
+ const unsubscribe = handle.on(EventType.ExecutionInputRequired, listener);
305
+ return typeof unsubscribe === 'function' ? unsubscribe : () => { };
306
+ }
307
+ /**
308
+ * Registers a listener that auto-responds ONLY to workflow-variable
309
+ * input_required events (reason in {"input_required",
310
+ * "incorrect_form_input", "multiple_matching_results"}). The decider
311
+ * receives the full payload and must return the input_variables dict.
312
+ *
313
+ * Counterpart to onPopupDecisionRequired. Modal events
314
+ * (reason === "non_dismissible_popup") are routed there and ignored here.
315
+ *
316
+ * @returns An unsubscribe callable.
317
+ */
318
+ onInputVariablesRequired(handle, decider, onError, options) {
319
+ const verbose = options?.verbose === true;
320
+ const VARIABLE_REASONS = new Set([
321
+ 'input_required',
322
+ 'incorrect_form_input',
323
+ 'multiple_matching_results',
324
+ ]);
325
+ const reportError = (err) => {
326
+ const reporter = onError ?? defaultRecoverySubmitErrorLog('submitInputVariables');
327
+ try {
328
+ reporter(err);
329
+ }
330
+ catch {
331
+ // defense
332
+ }
333
+ };
334
+ if (verbose)
335
+ verboseLog('onInputVariablesRequired', 'listener registered');
336
+ const listener = async (event) => {
337
+ const payload = (event?.data?.payload ?? event?.payload);
338
+ if (!payload || !payload.reason || !VARIABLE_REASONS.has(payload.reason)) {
339
+ if (verbose)
340
+ verboseLog('onInputVariablesRequired', `skipping reason=${payload?.reason ?? 'undefined'}`);
341
+ return;
342
+ }
343
+ if (verbose)
344
+ verboseLog('onInputVariablesRequired', `event received reason=${payload.reason}`);
345
+ let inputVars;
346
+ try {
347
+ inputVars = await decider(payload);
348
+ }
349
+ catch (e) {
350
+ if (verbose)
351
+ verboseLog('onInputVariablesRequired', `decider raised: ${e}`);
352
+ return;
353
+ }
354
+ if (!inputVars || typeof inputVars !== 'object' || Array.isArray(inputVars)) {
355
+ if (verbose)
356
+ verboseLog('onInputVariablesRequired', `decider returned non-object (${Array.isArray(inputVars) ? 'array' : typeof inputVars}); skipping`);
357
+ return;
358
+ }
359
+ const sid = payload.session_id || handle.sessionId;
360
+ if (verbose)
361
+ verboseLog('onInputVariablesRequired', `submitting input_variables keys=${JSON.stringify(Object.keys(inputVars))} for session=${sid}`);
362
+ try {
363
+ await this.submitInputVariables(sid, inputVars);
364
+ if (verbose)
365
+ verboseLog('onInputVariablesRequired', 'submit ok');
366
+ }
367
+ catch (err) {
368
+ reportError(err);
369
+ }
370
+ };
371
+ const unsubscribe = handle.on(EventType.ExecutionInputRequired, listener);
372
+ return typeof unsubscribe === 'function' ? unsubscribe : () => { };
373
+ }
374
+ /**
375
+ * Retrieves comprehensive results and execution details for a specific run
376
+ * @param sessionId - The unique identifier for the workflow execution session
377
+ * @returns Promise resolving to complete run results
378
+ */
379
+ async getResults(sessionId) {
380
+ const path = `/run/${sessionId}`;
381
+ return await this.makeRequest('GET', path);
382
+ }
383
+ /**
384
+ * Interrupts a running browser agent run
385
+ * @param sessionId - The unique identifier for the workflow execution session
386
+ */
387
+ async interrupt(sessionId) {
388
+ const path = `/run/${sessionId}/interrupt`;
389
+ await this.makeRequest('POST', path);
390
+ }
391
+ /**
392
+ * Replays all webhooks that were sent during a session
393
+ * @param sessionId - The ID of the session to replay webhooks for
394
+ * @returns Promise resolving to webhook replay results
395
+ */
396
+ async replayWebhooks(sessionId) {
397
+ const path = `/webhooks/${sessionId}/replay`;
398
+ return await this.makeRequest('POST', path);
399
+ }
400
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * CloudCruise Runs API Type Definitions
3
+ */
4
+ import type { EventType, RunEventMessage, EventPayloadMap, ExecutionQueuedPayload, ExecutionStartPayload, ExecutionStepPayload, InteractionWaitingPayload, InteractionFinishedPayload, AgentErrorAnalysisPayload, ExecutionRequeuedPayload, EndRunPayload, EndRunError, ExecutionStoppedEarlyPayload, FileUploadedPayload, ScreenshotUploadedPayload } from '../events/types.js';
5
+ export type { EventType };
6
+ export type { ExecutionQueuedPayload, ExecutionStartPayload, ExecutionStepPayload, InteractionWaitingPayload, InteractionFinishedPayload, AgentErrorAnalysisPayload, ExecutionRequeuedPayload, EndRunPayload, EndRunError, ExecutionStoppedEarlyPayload, FileUploadedPayload, ScreenshotUploadedPayload, EventPayloadMap, };
7
+ export interface DryRun {
8
+ enabled: boolean;
9
+ add_to_output?: Record<string, any>;
10
+ }
11
+ export interface Metadata {
12
+ metadata: Record<string, any>;
13
+ }
14
+ export interface RunSpecificWebhook {
15
+ url: string;
16
+ event_types_subscribed: EventType[];
17
+ secret: string;
18
+ validity: number;
19
+ }
20
+ export type PayloadWebhook = Metadata | RunSpecificWebhook;
21
+ export interface StartRunRequest {
22
+ workflow_id: string;
23
+ run_input_variables: Record<string, any>;
24
+ dry_run?: DryRun;
25
+ webhook?: PayloadWebhook;
26
+ additional_context?: Record<string, any>;
27
+ client_id?: string;
28
+ }
29
+ export interface StartRunResponse {
30
+ session_id: string;
31
+ }
32
+ export type UserInteractionData = Record<string, any>;
33
+ export interface VideoUrl {
34
+ timestamp: string;
35
+ session_id: string;
36
+ signed_screen_recording_url: string;
37
+ signed_screen_recording_url_expires: string;
38
+ }
39
+ export interface RunError {
40
+ prompt?: string | null;
41
+ message?: string | null;
42
+ error_id?: string | null;
43
+ full_url?: string | null;
44
+ llm_model?: string | null;
45
+ created_at?: string | null;
46
+ error_code?: string | null;
47
+ action_type?: string | null;
48
+ action_display_name?: string | null;
49
+ }
50
+ export interface SignedFileUrl {
51
+ signed_file_url: string;
52
+ file_name: string;
53
+ timestamp: string;
54
+ signed_file_url_expires: string;
55
+ metadata: Record<string, any>;
56
+ }
57
+ export interface SignedScreenshotUrl {
58
+ signed_screenshot_url: string;
59
+ node_display_name: string;
60
+ timestamp: string;
61
+ signed_screenshot_url_expires: string;
62
+ error_screenshot: boolean;
63
+ full_length_screenshot?: boolean;
64
+ retry_index?: number;
65
+ }
66
+ export interface WorkflowError {
67
+ message: string;
68
+ error_id: string;
69
+ full_url?: string | null;
70
+ created_at?: string | null;
71
+ error_code?: string | null;
72
+ action_type?: string | null;
73
+ action_display_name?: string | null;
74
+ llm_error_category?: string | null;
75
+ original_error?: string | null;
76
+ }
77
+ export interface RunResult {
78
+ session_id: string;
79
+ status: EventType;
80
+ input_variables: Record<string, any>;
81
+ data: Record<string, any>;
82
+ video_urls: VideoUrl[];
83
+ file_urls: SignedFileUrl[];
84
+ screenshot_urls: SignedScreenshotUrl[];
85
+ errors: RunError[] | null;
86
+ }
87
+ export interface GetRunResult {
88
+ data: Record<string, any> | null;
89
+ session_id: string;
90
+ errors: WorkflowError[];
91
+ status: EventType;
92
+ input_variables: Record<string, any>;
93
+ workflow_id: string | null;
94
+ session_retries: number | null;
95
+ encrypted_variables: string[] | null;
96
+ video_urls: VideoUrl[] | null;
97
+ screenshot_urls?: SignedScreenshotUrl[] | null;
98
+ file_urls: SignedFileUrl[] | null;
99
+ }
100
+ export interface WebhookEvent {
101
+ success: boolean;
102
+ response: string;
103
+ error: string;
104
+ }
105
+ export interface WebhookReplayResponse {
106
+ status: string;
107
+ info: string;
108
+ nr_success: number;
109
+ nr_failed: number;
110
+ webhook_events: WebhookEvent[];
111
+ }
112
+ /**
113
+ * Streaming (SSE) types
114
+ */
115
+ export type SseEventName = 'run.event' | 'ping';
116
+ export type RunEventEnvelope<E extends EventType = EventType> = RunEventMessage<E>;
117
+ export interface PingEnvelope {
118
+ event: 'ping';
119
+ data: {
120
+ ts: number;
121
+ } | Record<string, any>;
122
+ }
123
+ export type SseMessage<E extends EventType = EventType> = RunEventEnvelope<E> | PingEnvelope;
124
+ export interface RunStreamOptions {
125
+ signal?: AbortSignal;
126
+ withCredentials?: boolean;
127
+ headers?: Record<string, string>;
128
+ reconnect?: {
129
+ enabled?: boolean;
130
+ delays?: number[];
131
+ jitter?: number;
132
+ };
133
+ }
134
+ export type RunEventMap = {
135
+ [K in EventType]: RunEventEnvelope<K>;
136
+ };
137
+ export type RunHandleEventMap = {
138
+ 'open': undefined;
139
+ 'close': undefined;
140
+ 'reconnect': {
141
+ attemptDelayMs: number;
142
+ };
143
+ 'error': unknown;
144
+ 'end': {
145
+ type: EventType;
146
+ };
147
+ 'run.event': SseMessage;
148
+ 'ping': PingEnvelope;
149
+ 'message': SseMessage | PingEnvelope;
150
+ } & RunEventMap;
151
+ export interface RunHandle {
152
+ sessionId: string;
153
+ on<K extends keyof RunHandleEventMap>(event: K, handler: (e: RunHandleEventMap[K]) => void): () => void;
154
+ wait(): Promise<GetRunResult>;
155
+ close(): void;
156
+ [Symbol.asyncIterator](): AsyncIterator<SseMessage>;
157
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * CloudCruise Runs API Type Definitions
3
+ */
4
+ export {};
@@ -0,0 +1,9 @@
1
+ export declare class AsyncEventQueue<T> implements AsyncIterable<T> {
2
+ private items;
3
+ private pending;
4
+ private done;
5
+ push(item: T): void;
6
+ close(): void;
7
+ next(): Promise<IteratorResult<T>>;
8
+ [Symbol.asyncIterator](): AsyncIterator<T>;
9
+ }
@@ -0,0 +1,43 @@
1
+ export class AsyncEventQueue {
2
+ items = [];
3
+ pending = null;
4
+ done = false;
5
+ push(item) {
6
+ if (this.done)
7
+ return;
8
+ if (this.pending) {
9
+ const resolve = this.pending;
10
+ this.pending = null;
11
+ resolve({ value: item, done: false });
12
+ }
13
+ else {
14
+ this.items.push(item);
15
+ }
16
+ }
17
+ close() {
18
+ if (this.done)
19
+ return;
20
+ this.done = true;
21
+ if (this.pending) {
22
+ const resolve = this.pending;
23
+ this.pending = null;
24
+ resolve({ value: undefined, done: true });
25
+ }
26
+ }
27
+ async next() {
28
+ if (this.items.length) {
29
+ return { value: this.items.shift(), done: false };
30
+ }
31
+ if (this.done) {
32
+ return { value: undefined, done: true };
33
+ }
34
+ return await new Promise(resolve => {
35
+ this.pending = resolve;
36
+ });
37
+ }
38
+ [Symbol.asyncIterator]() {
39
+ return {
40
+ next: () => this.next(),
41
+ };
42
+ }
43
+ }
@@ -0,0 +1,29 @@
1
+ import type { SseMessage } from '../runs/types.js';
2
+ type Listener = (e: unknown) => void;
3
+ interface SubscribeOptions {
4
+ signal?: AbortSignal;
5
+ }
6
+ export interface SessionSubscription {
7
+ on(event: string, handler: Listener): () => void;
8
+ close(): void;
9
+ [Symbol.asyncIterator](): AsyncIterator<SseMessage>;
10
+ }
11
+ export declare class ConnectionManager {
12
+ private readonly baseUrl;
13
+ private readonly apiKey;
14
+ private clientId;
15
+ private conn;
16
+ private connecting;
17
+ private connected;
18
+ private reconnecting;
19
+ private readonly reconnectDelays;
20
+ private readonly sessions;
21
+ constructor(baseUrl: string, apiKey: string);
22
+ ensureClientId(): Promise<string>;
23
+ private generateClientId;
24
+ connectIfNeeded(): Promise<void>;
25
+ subscribe(sessionId: string, opts?: SubscribeOptions): SessionSubscription;
26
+ private openMuxConnection;
27
+ private scheduleReconnect;
28
+ }
29
+ export {};