@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.
- package/lib/cjs/InteractionBase.js +3 -3
- package/lib/cjs/InteractionBase.js.map +1 -1
- package/lib/cjs/InteractionsApi.js.map +1 -1
- package/lib/cjs/MCPOAuthApi.js +14 -7
- package/lib/cjs/MCPOAuthApi.js.map +1 -1
- package/lib/cjs/OAuthAppsApi.js +72 -0
- package/lib/cjs/OAuthAppsApi.js.map +1 -0
- package/lib/cjs/PromptsApi.js +2 -0
- package/lib/cjs/PromptsApi.js.map +1 -1
- package/lib/cjs/RunsApi.js +15 -0
- package/lib/cjs/RunsApi.js.map +1 -1
- package/lib/cjs/client.js +10 -0
- package/lib/cjs/client.js.map +1 -1
- package/lib/cjs/execute.js +38 -36
- package/lib/cjs/execute.js.map +1 -1
- package/lib/cjs/store/AgentsApi.js +639 -0
- package/lib/cjs/store/AgentsApi.js.map +1 -0
- package/lib/cjs/store/FilesApi.js +49 -2
- package/lib/cjs/store/FilesApi.js.map +1 -1
- package/lib/cjs/store/IndexingApi.js +47 -10
- package/lib/cjs/store/IndexingApi.js.map +1 -1
- package/lib/cjs/store/ObjectsApi.js +28 -5
- package/lib/cjs/store/ObjectsApi.js.map +1 -1
- package/lib/cjs/store/WorkflowsApi.js +234 -263
- package/lib/cjs/store/WorkflowsApi.js.map +1 -1
- package/lib/cjs/store/client.js +2 -0
- package/lib/cjs/store/client.js.map +1 -1
- package/lib/cjs/store/index.js +1 -0
- package/lib/cjs/store/index.js.map +1 -1
- package/lib/cjs/store/version.js +1 -1
- package/lib/esm/InteractionBase.js +3 -3
- package/lib/esm/InteractionBase.js.map +1 -1
- package/lib/esm/InteractionsApi.js.map +1 -1
- package/lib/esm/MCPOAuthApi.js +14 -7
- package/lib/esm/MCPOAuthApi.js.map +1 -1
- package/lib/esm/OAuthAppsApi.js +69 -0
- package/lib/esm/OAuthAppsApi.js.map +1 -0
- package/lib/esm/PromptsApi.js +2 -0
- package/lib/esm/PromptsApi.js.map +1 -1
- package/lib/esm/RunsApi.js +15 -0
- package/lib/esm/RunsApi.js.map +1 -1
- package/lib/esm/client.js +10 -0
- package/lib/esm/client.js.map +1 -1
- package/lib/esm/execute.js +38 -36
- package/lib/esm/execute.js.map +1 -1
- package/lib/esm/store/AgentsApi.js +635 -0
- package/lib/esm/store/AgentsApi.js.map +1 -0
- package/lib/esm/store/FilesApi.js +49 -2
- package/lib/esm/store/FilesApi.js.map +1 -1
- package/lib/esm/store/IndexingApi.js +47 -10
- package/lib/esm/store/IndexingApi.js.map +1 -1
- package/lib/esm/store/ObjectsApi.js +28 -5
- package/lib/esm/store/ObjectsApi.js.map +1 -1
- package/lib/esm/store/WorkflowsApi.js +234 -263
- package/lib/esm/store/WorkflowsApi.js.map +1 -1
- package/lib/esm/store/client.js +2 -0
- package/lib/esm/store/client.js.map +1 -1
- package/lib/esm/store/index.js +1 -0
- package/lib/esm/store/index.js.map +1 -1
- package/lib/esm/store/version.js +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/types/InteractionBase.d.ts +1 -1
- package/lib/types/InteractionBase.d.ts.map +1 -1
- package/lib/types/InteractionsApi.d.ts +1 -0
- package/lib/types/InteractionsApi.d.ts.map +1 -1
- package/lib/types/MCPOAuthApi.d.ts +10 -5
- package/lib/types/MCPOAuthApi.d.ts.map +1 -1
- package/lib/types/OAuthAppsApi.d.ts +51 -0
- package/lib/types/OAuthAppsApi.d.ts.map +1 -0
- package/lib/types/PromptsApi.d.ts +1 -1
- package/lib/types/PromptsApi.d.ts.map +1 -1
- package/lib/types/RunsApi.d.ts +17 -0
- package/lib/types/RunsApi.d.ts.map +1 -1
- package/lib/types/client.d.ts +6 -0
- package/lib/types/client.d.ts.map +1 -1
- package/lib/types/execute.d.ts +2 -4
- package/lib/types/execute.d.ts.map +1 -1
- package/lib/types/store/AgentsApi.d.ts +231 -0
- package/lib/types/store/AgentsApi.d.ts.map +1 -0
- package/lib/types/store/FilesApi.d.ts +20 -1
- package/lib/types/store/FilesApi.d.ts.map +1 -1
- package/lib/types/store/IndexingApi.d.ts +25 -4
- package/lib/types/store/IndexingApi.d.ts.map +1 -1
- package/lib/types/store/ObjectsApi.d.ts +14 -4
- package/lib/types/store/ObjectsApi.d.ts.map +1 -1
- package/lib/types/store/WorkflowsApi.d.ts +34 -84
- package/lib/types/store/WorkflowsApi.d.ts.map +1 -1
- package/lib/types/store/client.d.ts +2 -0
- package/lib/types/store/client.d.ts.map +1 -1
- package/lib/types/store/index.d.ts +1 -0
- package/lib/types/store/index.d.ts.map +1 -1
- package/lib/types/store/version.d.ts +1 -1
- package/lib/vertesia-client.js +1 -1
- package/lib/vertesia-client.js.map +1 -1
- package/package.json +8 -5
- package/src/InteractionBase.ts +3 -3
- package/src/InteractionsApi.ts +3 -1
- package/src/MCPOAuthApi.ts +14 -7
- package/src/OAuthAppsApi.ts +87 -0
- package/src/PromptsApi.ts +3 -1
- package/src/RunsApi.ts +17 -0
- package/src/client.test.ts +38 -0
- package/src/client.ts +11 -0
- package/src/execute.ts +37 -34
- package/src/store/AgentsApi.ts +765 -0
- package/src/store/FilesApi.ts +62 -2
- package/src/store/IndexingApi.ts +56 -12
- package/src/store/ObjectsApi.ts +45 -8
- package/src/store/WorkflowsApi.ts +250 -334
- package/src/store/client.ts +2 -0
- package/src/store/index.ts +1 -0
- package/src/store/version.ts +1 -1
- 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(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
227
|
-
const
|
|
228
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
236
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
245
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
310
|
+
// Skip historical messages - we already fetched them via GET /updates
|
|
311
|
+
streamUrl.searchParams.set("skipHistory", "true");
|
|
250
312
|
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
255
|
-
|
|
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(`
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
329
|
+
if (isClosed) return;
|
|
330
|
+
const sse = new EventSourceImpl(streamUrl.href);
|
|
331
|
+
currentSse = sse;
|
|
270
332
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const compactMessage = parseMessage(ev.data);
|
|
333
|
+
// Prevent Node from exiting prematurely
|
|
334
|
+
interval = setInterval(() => { }, 1000);
|
|
274
335
|
|
|
275
|
-
|
|
276
|
-
if (
|
|
277
|
-
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
289
|
-
|
|
350
|
+
try {
|
|
351
|
+
// Parse message using parseMessage() which handles both compact and legacy formats
|
|
352
|
+
const compactMessage = parseMessage(ev.data);
|
|
290
353
|
|
|
291
|
-
|
|
292
|
-
(compactMessage.
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
303
|
-
|
|
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
|
-
}
|
|
306
|
-
console.error("Failed to parse SSE message:", err, ev.data);
|
|
307
|
-
}
|
|
308
|
-
};
|
|
383
|
+
};
|
|
309
384
|
|
|
310
|
-
|
|
311
|
-
|
|
385
|
+
sse.onerror = (err: any) => {
|
|
386
|
+
if (isClosed) return;
|
|
312
387
|
|
|
313
|
-
|
|
314
|
-
|
|
388
|
+
console.warn(`SSE stream error for run ${runId}:`, err);
|
|
389
|
+
cleanup();
|
|
315
390
|
|
|
316
|
-
|
|
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
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
}
|