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.
- package/dist/CloudCruise.d.ts +29 -0
- package/dist/CloudCruise.js +113 -0
- package/dist/events/types.d.ts +186 -0
- package/dist/events/types.js +23 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +13 -0
- package/dist/runs/RunsClient.d.ts +101 -0
- package/dist/runs/RunsClient.js +400 -0
- package/dist/runs/types.d.ts +157 -0
- package/dist/runs/types.js +4 -0
- package/dist/utils/asyncQueue.d.ts +9 -0
- package/dist/utils/asyncQueue.js +43 -0
- package/dist/utils/connectionManager.d.ts +29 -0
- package/dist/utils/connectionManager.js +234 -0
- package/dist/utils/env.d.ts +2 -0
- package/dist/utils/env.js +9 -0
- package/dist/utils/events.d.ts +24 -0
- package/dist/utils/events.js +40 -0
- package/dist/utils/sse.d.ts +24 -0
- package/dist/utils/sse.js +122 -0
- package/dist/vault/VaultClient.d.ts +55 -0
- package/dist/vault/VaultClient.js +115 -0
- package/dist/vault/types.d.ts +62 -0
- package/dist/vault/types.js +4 -0
- package/dist/vault/utils.d.ts +34 -0
- package/dist/vault/utils.js +122 -0
- package/dist/webhook/WebhookClient.d.ts +15 -0
- package/dist/webhook/WebhookClient.js +18 -0
- package/dist/webhook/types.d.ts +7 -0
- package/dist/webhook/types.js +8 -0
- package/dist/webhook/utils.d.ts +3 -0
- package/dist/webhook/utils.js +49 -0
- package/dist/workflows/WorkflowsClient.d.ts +19 -0
- package/dist/workflows/WorkflowsClient.js +97 -0
- package/dist/workflows/types.d.ts +41 -0
- package/dist/workflows/types.js +15 -0
- package/package.json +2 -1
|
@@ -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,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 {};
|