@vertesia/client 1.0.0-dev.20260305.083323Z → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/lib/cjs/InteractionBase.js +3 -3
  2. package/lib/cjs/InteractionBase.js.map +1 -1
  3. package/lib/cjs/InteractionsApi.js.map +1 -1
  4. package/lib/cjs/MCPOAuthApi.js +14 -7
  5. package/lib/cjs/MCPOAuthApi.js.map +1 -1
  6. package/lib/cjs/OAuthAppsApi.js +72 -0
  7. package/lib/cjs/OAuthAppsApi.js.map +1 -0
  8. package/lib/cjs/PromptsApi.js +2 -0
  9. package/lib/cjs/PromptsApi.js.map +1 -1
  10. package/lib/cjs/RunsApi.js +15 -0
  11. package/lib/cjs/RunsApi.js.map +1 -1
  12. package/lib/cjs/client.js +10 -0
  13. package/lib/cjs/client.js.map +1 -1
  14. package/lib/cjs/execute.js +38 -36
  15. package/lib/cjs/execute.js.map +1 -1
  16. package/lib/cjs/store/AgentsApi.js +639 -0
  17. package/lib/cjs/store/AgentsApi.js.map +1 -0
  18. package/lib/cjs/store/FilesApi.js +49 -2
  19. package/lib/cjs/store/FilesApi.js.map +1 -1
  20. package/lib/cjs/store/IndexingApi.js +47 -10
  21. package/lib/cjs/store/IndexingApi.js.map +1 -1
  22. package/lib/cjs/store/ObjectsApi.js +28 -5
  23. package/lib/cjs/store/ObjectsApi.js.map +1 -1
  24. package/lib/cjs/store/WorkflowsApi.js +234 -263
  25. package/lib/cjs/store/WorkflowsApi.js.map +1 -1
  26. package/lib/cjs/store/client.js +2 -0
  27. package/lib/cjs/store/client.js.map +1 -1
  28. package/lib/cjs/store/index.js +1 -0
  29. package/lib/cjs/store/index.js.map +1 -1
  30. package/lib/cjs/store/version.js +1 -1
  31. package/lib/esm/InteractionBase.js +3 -3
  32. package/lib/esm/InteractionBase.js.map +1 -1
  33. package/lib/esm/InteractionsApi.js.map +1 -1
  34. package/lib/esm/MCPOAuthApi.js +14 -7
  35. package/lib/esm/MCPOAuthApi.js.map +1 -1
  36. package/lib/esm/OAuthAppsApi.js +69 -0
  37. package/lib/esm/OAuthAppsApi.js.map +1 -0
  38. package/lib/esm/PromptsApi.js +2 -0
  39. package/lib/esm/PromptsApi.js.map +1 -1
  40. package/lib/esm/RunsApi.js +15 -0
  41. package/lib/esm/RunsApi.js.map +1 -1
  42. package/lib/esm/client.js +10 -0
  43. package/lib/esm/client.js.map +1 -1
  44. package/lib/esm/execute.js +38 -36
  45. package/lib/esm/execute.js.map +1 -1
  46. package/lib/esm/store/AgentsApi.js +635 -0
  47. package/lib/esm/store/AgentsApi.js.map +1 -0
  48. package/lib/esm/store/FilesApi.js +49 -2
  49. package/lib/esm/store/FilesApi.js.map +1 -1
  50. package/lib/esm/store/IndexingApi.js +47 -10
  51. package/lib/esm/store/IndexingApi.js.map +1 -1
  52. package/lib/esm/store/ObjectsApi.js +28 -5
  53. package/lib/esm/store/ObjectsApi.js.map +1 -1
  54. package/lib/esm/store/WorkflowsApi.js +234 -263
  55. package/lib/esm/store/WorkflowsApi.js.map +1 -1
  56. package/lib/esm/store/client.js +2 -0
  57. package/lib/esm/store/client.js.map +1 -1
  58. package/lib/esm/store/index.js +1 -0
  59. package/lib/esm/store/index.js.map +1 -1
  60. package/lib/esm/store/version.js +1 -1
  61. package/lib/tsconfig.tsbuildinfo +1 -1
  62. package/lib/types/InteractionBase.d.ts +1 -1
  63. package/lib/types/InteractionBase.d.ts.map +1 -1
  64. package/lib/types/InteractionsApi.d.ts +1 -0
  65. package/lib/types/InteractionsApi.d.ts.map +1 -1
  66. package/lib/types/MCPOAuthApi.d.ts +10 -5
  67. package/lib/types/MCPOAuthApi.d.ts.map +1 -1
  68. package/lib/types/OAuthAppsApi.d.ts +51 -0
  69. package/lib/types/OAuthAppsApi.d.ts.map +1 -0
  70. package/lib/types/PromptsApi.d.ts +1 -1
  71. package/lib/types/PromptsApi.d.ts.map +1 -1
  72. package/lib/types/RunsApi.d.ts +17 -0
  73. package/lib/types/RunsApi.d.ts.map +1 -1
  74. package/lib/types/client.d.ts +6 -0
  75. package/lib/types/client.d.ts.map +1 -1
  76. package/lib/types/execute.d.ts +2 -4
  77. package/lib/types/execute.d.ts.map +1 -1
  78. package/lib/types/store/AgentsApi.d.ts +231 -0
  79. package/lib/types/store/AgentsApi.d.ts.map +1 -0
  80. package/lib/types/store/FilesApi.d.ts +20 -1
  81. package/lib/types/store/FilesApi.d.ts.map +1 -1
  82. package/lib/types/store/IndexingApi.d.ts +25 -4
  83. package/lib/types/store/IndexingApi.d.ts.map +1 -1
  84. package/lib/types/store/ObjectsApi.d.ts +14 -4
  85. package/lib/types/store/ObjectsApi.d.ts.map +1 -1
  86. package/lib/types/store/WorkflowsApi.d.ts +34 -84
  87. package/lib/types/store/WorkflowsApi.d.ts.map +1 -1
  88. package/lib/types/store/client.d.ts +2 -0
  89. package/lib/types/store/client.d.ts.map +1 -1
  90. package/lib/types/store/index.d.ts +1 -0
  91. package/lib/types/store/index.d.ts.map +1 -1
  92. package/lib/types/store/version.d.ts +1 -1
  93. package/lib/vertesia-client.js +1 -1
  94. package/lib/vertesia-client.js.map +1 -1
  95. package/package.json +8 -5
  96. package/src/InteractionBase.ts +3 -3
  97. package/src/InteractionsApi.ts +3 -1
  98. package/src/MCPOAuthApi.ts +14 -7
  99. package/src/OAuthAppsApi.ts +87 -0
  100. package/src/PromptsApi.ts +3 -1
  101. package/src/RunsApi.ts +17 -0
  102. package/src/client.test.ts +38 -0
  103. package/src/client.ts +11 -0
  104. package/src/execute.ts +37 -34
  105. package/src/store/AgentsApi.ts +765 -0
  106. package/src/store/FilesApi.ts +62 -2
  107. package/src/store/IndexingApi.ts +56 -12
  108. package/src/store/ObjectsApi.ts +45 -8
  109. package/src/store/WorkflowsApi.ts +250 -334
  110. package/src/store/client.ts +2 -0
  111. package/src/store/index.ts +1 -0
  112. package/src/store/version.ts +1 -1
  113. package/tsconfig.dist.json +1 -1
@@ -1,41 +1,26 @@
1
1
  import { ApiTopic, ClientBase } from "@vertesia/api-fetch-client";
2
2
  import {
3
+ ActiveWorkstreamsQueryResult,
3
4
  ActivityCatalog,
4
- AgentEvent,
5
5
  AgentMessage,
6
6
  AgentMessageType,
7
7
  CompactMessage,
8
8
  CreateWorkflowRulePayload,
9
9
  DSLWorkflowDefinition,
10
10
  DSLWorkflowSpec,
11
- ErrorAnalyticsResponse,
12
11
  ExecuteWorkflowPayload,
13
- FirstResponseBehaviorAnalyticsResponse,
14
- LatencyAnalyticsResponse,
15
12
  ListWorkflowInteractionsResponse,
16
13
  ListWorkflowRunsPayload,
17
14
  ListWorkflowRunsResponse,
18
15
  parseMessage,
19
16
  toAgentMessage,
20
- PromptSizeAnalyticsResponse,
21
- RunsByAgentAnalyticsResponse,
22
- TimeToFirstResponseAnalyticsResponse,
23
- TokenUsageAnalyticsResponse,
24
- ToolAnalyticsResponse,
25
- ToolParameterAnalyticsResponse,
26
- TopPrincipalsAnalyticsResponse,
27
17
  WebSocketClientMessage,
28
18
  WebSocketServerMessage,
29
19
  WorkflowActionPayload,
30
- WorkflowAnalyticsFilterOptionsResponse,
31
- WorkflowAnalyticsSummaryQuery,
32
- WorkflowAnalyticsSummaryResponse,
33
- WorkflowAnalyticsTimeSeriesQuery,
34
20
  WorkflowDefinitionRef,
35
21
  WorkflowRule,
36
22
  WorkflowRuleItem,
37
23
  WorkflowRunWithDetails,
38
- WorkflowToolParametersQuery,
39
24
  } from "@vertesia/common";
40
25
  import { VertesiaClient } from "../client.js";
41
26
  import { EventSourceProvider } from "../execute.js";
@@ -117,6 +102,59 @@ export class WorkflowsApi extends ApiTopic {
117
102
  return this.get(`/runs/${workflowId}/${runId}/query/${queryName}`);
118
103
  }
119
104
 
105
+ // ========================================================================
106
+ // Workstream helpers
107
+ // ========================================================================
108
+
109
+ /**
110
+ * List active workstreams for a running conversation workflow.
111
+ * Each entry includes `child_workflow_id` / `child_workflow_run_id` which
112
+ * can be passed to `retrieveMessages` or `streamMessages` to fetch the
113
+ * child's own message stream.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * const { running } = await client.workflows.getActiveWorkstreams(wfId, runId);
118
+ * for (const ws of running) {
119
+ * const msgs = await client.workflows.retrieveWorkstreamMessages(ws);
120
+ * console.log(ws.workstream_id, msgs.length, 'messages');
121
+ * }
122
+ * ```
123
+ */
124
+ getActiveWorkstreams(workflowId: string, runId: string): Promise<ActiveWorkstreamsQueryResult> {
125
+ return this.query<ActiveWorkstreamsQueryResult>(workflowId, runId, 'ActiveWorkstreams');
126
+ }
127
+
128
+ /**
129
+ * Retrieve historical messages for a specific workstream (child workflow).
130
+ * Convenience wrapper — extracts child IDs from an `ActiveWorkstreamEntry`.
131
+ */
132
+ retrieveWorkstreamMessages(
133
+ workstream: { child_workflow_id: string; child_workflow_run_id?: string },
134
+ since?: number,
135
+ ): Promise<AgentMessage[]> {
136
+ if (!workstream.child_workflow_run_id) {
137
+ return Promise.resolve([]);
138
+ }
139
+ return this.retrieveMessages(workstream.child_workflow_id, workstream.child_workflow_run_id, since);
140
+ }
141
+
142
+ /**
143
+ * Stream messages for a specific workstream (child workflow) in real-time.
144
+ * Convenience wrapper — extracts child IDs from an `ActiveWorkstreamEntry`.
145
+ */
146
+ streamWorkstreamMessages(
147
+ workstream: { child_workflow_id: string; child_workflow_run_id?: string },
148
+ onMessage?: (message: AgentMessage, exitFn?: (payload: unknown) => void) => void,
149
+ since?: number,
150
+ signal?: AbortSignal,
151
+ ): Promise<unknown> {
152
+ if (!workstream.child_workflow_run_id) {
153
+ return Promise.resolve(null);
154
+ }
155
+ return this.streamMessages(workstream.child_workflow_id, workstream.child_workflow_run_id, onMessage, since, signal);
156
+ }
157
+
120
158
  execute(
121
159
  name: string,
122
160
  payload: ExecuteWorkflowPayload = {},
@@ -152,208 +190,247 @@ export class WorkflowsApi extends ApiTopic {
152
190
  * This approach provides better performance for conversations with large historical messages
153
191
  * since HTTP responses are compressed while SSE streams cannot be compressed.
154
192
  */
155
- async streamMessages(workflowId: string, runId: string, onMessage?: (message: AgentMessage, exitFn?: (payload: unknown) => void) => void, since?: number): Promise<unknown> {
156
- return new Promise<unknown>(async (resolve, reject) => {
157
- let reconnectAttempts = 0;
158
- let lastMessageTimestamp = since || 0;
159
- let isClosed = false;
160
- let currentSse: EventSource | null = null;
161
- let interval: NodeJS.Timeout | null = null;
162
-
163
- const maxReconnectAttempts = 10;
164
- const baseDelay = 1000; // 1 second base delay
165
- const maxDelay = 30000; // 30 seconds max delay
166
-
167
- const calculateBackoffDelay = (attempts: number): number => {
168
- const exponentialDelay = Math.min(baseDelay * Math.pow(2, attempts), maxDelay);
169
- // Add jitter to prevent thundering herd
170
- const jitter = Math.random() * 0.1 * exponentialDelay;
171
- return exponentialDelay + jitter;
172
- };
173
-
174
- const cleanup = () => {
175
- if (interval) {
176
- clearInterval(interval);
177
- interval = null;
178
- }
179
- if (currentSse) {
180
- currentSse.close();
181
- currentSse = null;
182
- }
183
- };
184
-
185
- const exit = (payload: unknown) => {
186
- if (!isClosed) {
187
- isClosed = true;
188
- cleanup();
189
- resolve(payload);
190
- }
191
- };
193
+ async streamMessages(
194
+ workflowId: string,
195
+ runId: string,
196
+ onMessage?: (message: AgentMessage, exitFn?: (payload: unknown) => void) => void,
197
+ since?: number,
198
+ signal?: AbortSignal,
199
+ ): Promise<unknown> {
200
+ return new Promise<unknown>((resolve, reject) => {
201
+ const run = async () => {
202
+ let reconnectAttempts = 0;
203
+ let lastMessageTimestamp = since || 0;
204
+ let isClosed = false;
205
+ let currentSse: EventSource | null = null;
206
+ let interval: NodeJS.Timeout | null = null;
207
+ let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
208
+ let abortHandler: (() => void) | null = null;
209
+
210
+ const maxReconnectAttempts = 10;
211
+ const baseDelay = 1000; // 1 second base delay
212
+ const maxDelay = 30000; // 30 seconds max delay
213
+
214
+ const calculateBackoffDelay = (attempts: number): number => {
215
+ const exponentialDelay = Math.min(baseDelay * Math.pow(2, attempts), maxDelay);
216
+ // Add jitter to prevent thundering herd
217
+ const jitter = Math.random() * 0.1 * exponentialDelay;
218
+ return exponentialDelay + jitter;
219
+ };
220
+
221
+ const cleanup = () => {
222
+ if (reconnectTimer) {
223
+ clearTimeout(reconnectTimer);
224
+ reconnectTimer = null;
225
+ }
226
+ if (interval) {
227
+ clearInterval(interval);
228
+ interval = null;
229
+ }
230
+ if (currentSse) {
231
+ currentSse.close();
232
+ currentSse = null;
233
+ }
234
+ if (signal && abortHandler) {
235
+ signal.removeEventListener("abort", abortHandler);
236
+ abortHandler = null;
237
+ }
238
+ };
192
239
 
193
- // 1. Fetch historical messages via GET /updates (gzip-compressed if > 3KB)
194
- // This is more efficient than receiving historical over uncompressed SSE
195
- try {
196
- const historical = await this.retrieveMessages(workflowId, runId, since);
197
- for (const msg of historical) {
198
- // Update timestamp for SSE connection
199
- lastMessageTimestamp = Math.max(lastMessageTimestamp, msg.timestamp || 0);
200
-
201
- // Deliver historical messages to consumer
202
- if (onMessage) {
203
- onMessage(msg, exit);
240
+ const exit = (payload: unknown) => {
241
+ if (!isClosed) {
242
+ isClosed = true;
243
+ cleanup();
244
+ resolve(payload);
204
245
  }
246
+ };
205
247
 
206
- // Check if workflow already completed
207
- const workstreamId = msg.workstream_id || 'main';
208
- const streamIsOver = msg.type === AgentMessageType.TERMINATED ||
209
- (msg.type === AgentMessageType.COMPLETE && workstreamId === 'main');
210
- if (streamIsOver) {
211
- console.log("Workflow already completed in historical messages");
248
+ // Allow callers to externally cancel the stream lifecycle.
249
+ if (signal) {
250
+ if (signal.aborted) {
251
+ isClosed = true;
252
+ cleanup();
212
253
  resolve(null);
213
254
  return;
214
255
  }
256
+ abortHandler = () => {
257
+ exit(null);
258
+ };
259
+ signal.addEventListener("abort", abortHandler, { once: true });
215
260
  }
216
- } catch (err) {
217
- console.warn("Failed to fetch historical messages, continuing with SSE:", err);
218
- // Continue to SSE - it will send historical if skipHistory is not set
219
- }
220
-
221
- // 2. Connect to SSE for real-time updates only (skipHistory=true)
222
- const setupStream = async (isReconnect: boolean = false) => {
223
- if (isClosed) return;
224
261
 
262
+ // 1. Fetch historical messages via GET /updates (gzip-compressed if > 3KB)
263
+ // This is more efficient than receiving historical over uncompressed SSE
225
264
  try {
226
- const EventSourceImpl = await EventSourceProvider();
227
- const client = this.client as VertesiaClient;
228
- const streamUrl = new URL(client.workflows.baseUrl + `/runs/${workflowId}/${runId}/stream`);
265
+ if (isClosed) return;
266
+ const historical = await this.retrieveMessages(workflowId, runId, since);
267
+ if (isClosed) return;
268
+ for (const msg of historical) {
269
+ if (isClosed) return;
270
+ // Update timestamp for SSE connection
271
+ lastMessageTimestamp = Math.max(lastMessageTimestamp, msg.timestamp || 0);
229
272
 
230
- // Use the timestamp of the last received message for reconnection
231
- if (lastMessageTimestamp > 0) {
232
- streamUrl.searchParams.set("since", lastMessageTimestamp.toString());
273
+ // Deliver historical messages to consumer
274
+ if (onMessage) {
275
+ onMessage(msg, exit);
276
+ }
277
+ if (isClosed) return;
278
+
279
+ // Check if workflow already completed
280
+ const workstreamId = msg.workstream_id || 'main';
281
+ const streamIsOver = msg.type === AgentMessageType.TERMINATED ||
282
+ (msg.type === AgentMessageType.COMPLETE && workstreamId === 'main');
283
+ if (streamIsOver) {
284
+ console.log("Workflow already completed in historical messages");
285
+ exit(null);
286
+ return;
287
+ }
233
288
  }
289
+ } catch (err) {
290
+ if (isClosed) return;
291
+ console.warn("Failed to fetch historical messages, continuing with SSE:", err);
292
+ // Continue to SSE - it will send historical if skipHistory is not set
293
+ }
234
294
 
235
- // Skip historical messages - we already fetched them via GET /updates
236
- streamUrl.searchParams.set("skipHistory", "true");
295
+ // 2. Connect to SSE for real-time updates only (skipHistory=true)
296
+ const setupStream = async (isReconnect: boolean = false) => {
297
+ if (isClosed) return;
237
298
 
238
- const bearerToken = client._auth ? await client._auth() : undefined;
239
- if (!bearerToken) {
240
- reject(new Error("No auth token available"));
241
- return;
242
- }
299
+ try {
300
+ const EventSourceImpl = await EventSourceProvider();
301
+ if (isClosed) return;
302
+ const client = this.client as VertesiaClient;
303
+ const streamUrl = new URL(client.workflows.baseUrl + `/runs/${workflowId}/${runId}/stream`);
243
304
 
244
- const token = bearerToken.split(" ")[1];
245
- streamUrl.searchParams.set("access_token", token);
305
+ // Use the timestamp of the last received message for reconnection
306
+ if (lastMessageTimestamp > 0) {
307
+ streamUrl.searchParams.set("since", lastMessageTimestamp.toString());
308
+ }
246
309
 
247
- if (isReconnect) {
248
- console.log(`Reconnecting to SSE stream for run ${runId} (attempt ${reconnectAttempts + 1}/${maxReconnectAttempts})`);
249
- }
310
+ // Skip historical messages - we already fetched them via GET /updates
311
+ streamUrl.searchParams.set("skipHistory", "true");
250
312
 
251
- const sse = new EventSourceImpl(streamUrl.href);
252
- currentSse = sse;
313
+ const bearerToken = client._auth ? await client._auth() : undefined;
314
+ if (isClosed) return;
315
+ if (!bearerToken) {
316
+ isClosed = true;
317
+ cleanup();
318
+ reject(new Error("No auth token available"));
319
+ return;
320
+ }
253
321
 
254
- // Prevent Node from exiting prematurely
255
- interval = setInterval(() => { }, 1000);
322
+ const token = bearerToken.split(" ")[1];
323
+ streamUrl.searchParams.set("access_token", token);
256
324
 
257
- sse.onopen = () => {
258
325
  if (isReconnect) {
259
- console.log(`Successfully reconnected to SSE stream for run ${runId}`);
326
+ console.log(`Reconnecting to SSE stream for run ${runId} (attempt ${reconnectAttempts + 1}/${maxReconnectAttempts})`);
260
327
  }
261
- // Reset reconnect attempts on successful connection
262
- reconnectAttempts = 0;
263
- };
264
328
 
265
- sse.onmessage = (ev: MessageEvent) => {
266
- if (!ev.data || ev.data.startsWith(":")) {
267
- console.log("Received comment or heartbeat; ignoring it.: ", ev.data);
268
- return;
269
- }
329
+ if (isClosed) return;
330
+ const sse = new EventSourceImpl(streamUrl.href);
331
+ currentSse = sse;
270
332
 
271
- try {
272
- // Parse message using parseMessage() which handles both compact and legacy formats
273
- const compactMessage = parseMessage(ev.data);
333
+ // Prevent Node from exiting prematurely
334
+ interval = setInterval(() => { }, 1000);
274
335
 
275
- // Update last message timestamp for reconnection (use ts field or current time)
276
- if (compactMessage.ts) {
277
- lastMessageTimestamp = Math.max(lastMessageTimestamp, compactMessage.ts);
278
- } else {
279
- lastMessageTimestamp = Date.now();
336
+ sse.onopen = () => {
337
+ if (isReconnect) {
338
+ console.log(`Successfully reconnected to SSE stream for run ${runId}`);
280
339
  }
281
-
282
- // Convert to AgentMessage for consumers (they shouldn't need to know about compact format)
283
- if (onMessage) {
284
- const agentMessage = toAgentMessage(compactMessage, runId);
285
- onMessage(agentMessage, exit);
340
+ // Reset reconnect attempts on successful connection
341
+ reconnectAttempts = 0;
342
+ };
343
+
344
+ sse.onmessage = (ev: MessageEvent) => {
345
+ if (isClosed) return;
346
+ if (!ev.data || ev.data.startsWith(":")) {
347
+ return;
286
348
  }
287
349
 
288
- // Get workstream ID (defaults to 'main' if not set)
289
- const workstreamId = compactMessage.w || 'main';
350
+ try {
351
+ // Parse message using parseMessage() which handles both compact and legacy formats
352
+ const compactMessage = parseMessage(ev.data);
290
353
 
291
- const streamIsOver = compactMessage.t === AgentMessageType.TERMINATED ||
292
- (compactMessage.t === AgentMessageType.COMPLETE && workstreamId === 'main');
354
+ // Update last message timestamp for reconnection (use ts field or current time)
355
+ if (compactMessage.ts) {
356
+ lastMessageTimestamp = Math.max(lastMessageTimestamp, compactMessage.ts);
357
+ } else {
358
+ lastMessageTimestamp = Date.now();
359
+ }
293
360
 
294
- // Only close the stream when the main workstream completes or terminates
295
- if (streamIsOver) {
296
- console.log("Closing stream due to COMPLETE message from main workstream");
297
- if (!isClosed) {
298
- isClosed = true;
299
- cleanup();
300
- resolve(null);
361
+ // Convert to AgentMessage for consumers (they shouldn't need to know about compact format)
362
+ if (onMessage) {
363
+ const agentMessage = toAgentMessage(compactMessage, runId);
364
+ onMessage(agentMessage, exit);
301
365
  }
302
- } else if (compactMessage.t === AgentMessageType.COMPLETE) {
303
- console.log(`Received COMPLETE message from non-main workstream: ${workstreamId}, keeping stream open`);
366
+
367
+ // Get workstream ID (defaults to 'main' if not set)
368
+ const workstreamId = compactMessage.w || 'main';
369
+
370
+ const streamIsOver = compactMessage.t === AgentMessageType.TERMINATED ||
371
+ (compactMessage.t === AgentMessageType.COMPLETE && workstreamId === 'main');
372
+
373
+ // Only close the stream when the main workstream completes or terminates
374
+ if (streamIsOver) {
375
+ console.log("Closing stream due to COMPLETE message from main workstream");
376
+ exit(null);
377
+ } else if (compactMessage.t === AgentMessageType.COMPLETE) {
378
+ console.log(`Received COMPLETE message from non-main workstream: ${workstreamId}, keeping stream open`);
379
+ }
380
+ } catch (err) {
381
+ console.error("Failed to parse SSE message:", err, ev.data);
304
382
  }
305
- } catch (err) {
306
- console.error("Failed to parse SSE message:", err, ev.data);
307
- }
308
- };
383
+ };
309
384
 
310
- sse.onerror = (err: any) => {
311
- if (isClosed) return;
385
+ sse.onerror = (err: any) => {
386
+ if (isClosed) return;
312
387
 
313
- console.warn(`SSE stream error for run ${runId}:`, err);
314
- cleanup();
388
+ console.warn(`SSE stream error for run ${runId}:`, err);
389
+ cleanup();
315
390
 
316
- // Check if we should attempt reconnection
391
+ // Check if we should attempt reconnection
392
+ if (reconnectAttempts < maxReconnectAttempts) {
393
+ const delay = calculateBackoffDelay(reconnectAttempts);
394
+ console.log(`Attempting to reconnect in ${delay}ms (attempt ${reconnectAttempts + 1}/${maxReconnectAttempts})`);
395
+
396
+ reconnectAttempts++;
397
+ reconnectTimer = setTimeout(() => {
398
+ reconnectTimer = null;
399
+ if (!isClosed) {
400
+ setupStream(true);
401
+ }
402
+ }, delay);
403
+ } else {
404
+ console.error(`Failed to reconnect to SSE stream for run ${runId} after ${maxReconnectAttempts} attempts`);
405
+ isClosed = true;
406
+ cleanup();
407
+ reject(new Error(`SSE connection failed after ${maxReconnectAttempts} reconnection attempts`));
408
+ }
409
+ };
410
+ } catch (err) {
411
+ if (isClosed) return;
412
+ console.error("Error setting up SSE stream:", err);
317
413
  if (reconnectAttempts < maxReconnectAttempts) {
318
414
  const delay = calculateBackoffDelay(reconnectAttempts);
319
- console.log(`Attempting to reconnect in ${delay}ms (attempt ${reconnectAttempts + 1}/${maxReconnectAttempts})`);
320
-
321
415
  reconnectAttempts++;
322
- setTimeout(() => {
416
+ reconnectTimer = setTimeout(() => {
417
+ reconnectTimer = null;
323
418
  if (!isClosed) {
324
419
  setupStream(true);
325
420
  }
326
421
  }, delay);
327
422
  } else {
328
- console.error(`Failed to reconnect to SSE stream for run ${runId} after ${maxReconnectAttempts} attempts`);
329
423
  isClosed = true;
330
- reject(new Error(`SSE connection failed after ${maxReconnectAttempts} reconnection attempts`));
424
+ cleanup();
425
+ reject(err);
331
426
  }
332
- };
333
- } catch (err) {
334
- console.error("Error setting up SSE stream:", err);
335
- if (reconnectAttempts < maxReconnectAttempts) {
336
- const delay = calculateBackoffDelay(reconnectAttempts);
337
- reconnectAttempts++;
338
- setTimeout(() => {
339
- if (!isClosed) {
340
- setupStream(true);
341
- }
342
- }, delay);
343
- } else {
344
- reject(err);
345
427
  }
346
- }
347
- };
348
-
349
- // Start the async setup process
350
- setupStream(false);
428
+ };
351
429
 
352
- // Return cleanup function for external cancellation
353
- return () => {
354
- isClosed = true;
355
- cleanup();
356
- };
430
+ // Start the async setup process
431
+ setupStream(false);
432
+ }
433
+ run().catch(reject);
357
434
  });
358
435
  }
359
436
 
@@ -530,167 +607,6 @@ export class WorkflowsApi extends ApiTopic {
530
607
  });
531
608
  }
532
609
 
533
- /**
534
- * Ingest telemetry events for a workflow run.
535
- * Workers use this to send telemetry to zeno-server for BigQuery storage.
536
- */
537
- ingestEvents(
538
- workflowId: string,
539
- runId: string,
540
- events: AgentEvent[]
541
- ): Promise<{ ingested: number; status?: string; error?: string }> {
542
- return this.post(`/runs/${workflowId}/${runId}/events`, {
543
- payload: { events },
544
- });
545
- }
546
-
547
- // ========================================================================
548
- // Analytics API
549
- // ========================================================================
550
-
551
- /**
552
- * Get workflow analytics summary.
553
- * Returns overall metrics including token usage, success rates, and run counts.
554
- */
555
- getAnalyticsSummary(
556
- query: WorkflowAnalyticsSummaryQuery = {}
557
- ): Promise<WorkflowAnalyticsSummaryResponse> {
558
- return this.post('/analytics/summary', { payload: query });
559
- }
560
-
561
- /**
562
- * Get token usage analytics.
563
- * Returns token consumption metrics by model, agent, tool, or over time.
564
- */
565
- getTokenUsageAnalytics(
566
- query: WorkflowAnalyticsTimeSeriesQuery = {}
567
- ): Promise<TokenUsageAnalyticsResponse> {
568
- return this.post('/analytics/tokens', { payload: query });
569
- }
570
-
571
- /**
572
- * Get LLM latency analytics.
573
- * Returns duration/latency metrics for LLM calls.
574
- */
575
- getLlmLatencyAnalytics(
576
- query: WorkflowAnalyticsTimeSeriesQuery = {}
577
- ): Promise<LatencyAnalyticsResponse> {
578
- return this.post('/analytics/latency/llm', { payload: query });
579
- }
580
-
581
- /**
582
- * Get tool latency analytics.
583
- * Returns duration/latency metrics for tool calls.
584
- */
585
- getToolLatencyAnalytics(
586
- query: WorkflowAnalyticsTimeSeriesQuery = {}
587
- ): Promise<LatencyAnalyticsResponse> {
588
- return this.post('/analytics/latency/tools', { payload: query });
589
- }
590
-
591
- /**
592
- * Get agent/workflow latency analytics.
593
- * Returns duration metrics for complete workflow runs.
594
- */
595
- getAgentLatencyAnalytics(
596
- query: WorkflowAnalyticsTimeSeriesQuery = {}
597
- ): Promise<LatencyAnalyticsResponse> {
598
- return this.post('/analytics/latency/agents', { payload: query });
599
- }
600
-
601
- /**
602
- * Get error analytics.
603
- * Returns error rates, types, and trends.
604
- */
605
- getErrorAnalytics(
606
- query: WorkflowAnalyticsTimeSeriesQuery = {}
607
- ): Promise<ErrorAnalyticsResponse> {
608
- return this.post('/analytics/errors', { payload: query });
609
- }
610
-
611
- /**
612
- * Get tool usage analytics.
613
- * Returns tool invocation counts, success rates, and performance metrics.
614
- */
615
- getToolAnalytics(
616
- query: WorkflowAnalyticsSummaryQuery = {}
617
- ): Promise<ToolAnalyticsResponse> {
618
- return this.post('/analytics/tools', { payload: query });
619
- }
620
-
621
- /**
622
- * Get tool parameter analytics.
623
- * Returns parameter value distributions for a specific tool.
624
- */
625
- getToolParameterAnalytics(
626
- query: WorkflowToolParametersQuery
627
- ): Promise<ToolParameterAnalyticsResponse> {
628
- return this.post('/analytics/tools/parameters', { payload: query });
629
- }
630
-
631
- /**
632
- * Get available filter options for analytics.
633
- * Returns unique agents, environments, and models from telemetry data.
634
- */
635
- getAnalyticsFilterOptions(
636
- query: WorkflowAnalyticsSummaryQuery = {}
637
- ): Promise<WorkflowAnalyticsFilterOptionsResponse> {
638
- return this.post('/analytics/filter-options', { payload: query });
639
- }
640
-
641
- /**
642
- * Get average prompt size (input tokens) by agent for startConversation calls.
643
- * This represents the initial prompt + tools size.
644
- */
645
- getPromptSizeAnalytics(
646
- query: WorkflowAnalyticsSummaryQuery = {}
647
- ): Promise<PromptSizeAnalyticsResponse> {
648
- return this.post('/analytics/prompt-size', { payload: query });
649
- }
650
-
651
- /**
652
- * Get top principals (users/API keys) who started the most agent runs.
653
- * Returns the top N principals sorted by run count descending.
654
- */
655
- getTopPrincipalsAnalytics(
656
- query: WorkflowAnalyticsSummaryQuery = {}
657
- ): Promise<TopPrincipalsAnalyticsResponse> {
658
- return this.post('/analytics/top-principals', { payload: query });
659
- }
660
-
661
- /**
662
- * Get agent run distribution - how many runs per agent/interaction type.
663
- * Returns the top N agents sorted by run count descending.
664
- */
665
- getRunsByAgentAnalytics(
666
- query: WorkflowAnalyticsSummaryQuery = {}
667
- ): Promise<RunsByAgentAnalyticsResponse> {
668
- return this.post('/analytics/runs-by-agent', { payload: query });
669
- }
670
-
671
- /**
672
- * Get time to first response analytics.
673
- * Measures the time from agent start to the completion of the first LLM call.
674
- * Returns average, min, max, median, p95, and p99 metrics.
675
- */
676
- getTimeToFirstResponseAnalytics(
677
- query: WorkflowAnalyticsTimeSeriesQuery = {}
678
- ): Promise<TimeToFirstResponseAnalyticsResponse> {
679
- return this.post('/analytics/time-to-first-response', { payload: query });
680
- }
681
-
682
- /**
683
- * Get first response behavior analytics.
684
- * Analyzes the agent's first LLM response behavior:
685
- * - Percentage of agents that start by making a plan
686
- * - Percentage of agents that return no tool calls at start
687
- */
688
- getFirstResponseBehaviorAnalytics(
689
- query: WorkflowAnalyticsTimeSeriesQuery = {}
690
- ): Promise<FirstResponseBehaviorAnalyticsResponse> {
691
- return this.post('/analytics/first-response-behavior', { payload: query });
692
- }
693
-
694
610
  rules = new WorkflowsRulesApi(this);
695
611
  definitions = new WorkflowsDefinitionApi(this);
696
612
  }