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