@agent-os-sdk/client 0.9.25 → 0.9.27

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 (65) hide show
  1. package/dist/generated/openapi.d.ts +82 -0
  2. package/dist/generated/openapi.d.ts.map +1 -1
  3. package/dist/modules/runs.d.ts.map +1 -1
  4. package/dist/modules/templates.d.ts +23 -0
  5. package/dist/modules/templates.d.ts.map +1 -1
  6. package/dist/modules/templates.js +7 -0
  7. package/package.json +2 -2
  8. package/src/client/AgentOsClient.ts +0 -294
  9. package/src/client/HttpRequestBuilder.ts +0 -115
  10. package/src/client/OperationContext.ts +0 -22
  11. package/src/client/OperationContextProvider.ts +0 -89
  12. package/src/client/auth.ts +0 -136
  13. package/src/client/config.ts +0 -100
  14. package/src/client/helpers.ts +0 -98
  15. package/src/client/pagination.ts +0 -218
  16. package/src/client/raw.ts +0 -609
  17. package/src/client/retry.ts +0 -150
  18. package/src/client/sanitize.ts +0 -31
  19. package/src/client/timeout.ts +0 -59
  20. package/src/errors/factory.ts +0 -140
  21. package/src/errors/index.ts +0 -365
  22. package/src/generated/client.ts +0 -32
  23. package/src/generated/index.ts +0 -2
  24. package/src/generated/openapi.ts +0 -12302
  25. package/src/generated/swagger.json +0 -16851
  26. package/src/index.ts +0 -131
  27. package/src/modules/a2a.ts +0 -64
  28. package/src/modules/agents.ts +0 -604
  29. package/src/modules/apiTokens.ts +0 -101
  30. package/src/modules/approvals.ts +0 -151
  31. package/src/modules/audit.ts +0 -145
  32. package/src/modules/auth.ts +0 -33
  33. package/src/modules/catalog.ts +0 -241
  34. package/src/modules/chatwoot.ts +0 -242
  35. package/src/modules/checkpoints.ts +0 -87
  36. package/src/modules/contracts.ts +0 -80
  37. package/src/modules/credentials.ts +0 -216
  38. package/src/modules/crons.ts +0 -115
  39. package/src/modules/datasets.ts +0 -142
  40. package/src/modules/evaluation.ts +0 -269
  41. package/src/modules/files.ts +0 -208
  42. package/src/modules/improvements.ts +0 -71
  43. package/src/modules/info.ts +0 -143
  44. package/src/modules/me.ts +0 -74
  45. package/src/modules/members.ts +0 -199
  46. package/src/modules/memberships.ts +0 -42
  47. package/src/modules/metaAgent.ts +0 -131
  48. package/src/modules/metrics.ts +0 -34
  49. package/src/modules/observability.ts +0 -28
  50. package/src/modules/playground.ts +0 -68
  51. package/src/modules/presets.ts +0 -246
  52. package/src/modules/prompts.ts +0 -147
  53. package/src/modules/roles.ts +0 -112
  54. package/src/modules/runs.ts +0 -878
  55. package/src/modules/store.ts +0 -65
  56. package/src/modules/templates.ts +0 -40
  57. package/src/modules/tenants.ts +0 -79
  58. package/src/modules/threads.ts +0 -343
  59. package/src/modules/tools.ts +0 -91
  60. package/src/modules/traces.ts +0 -133
  61. package/src/modules/triggers.ts +0 -357
  62. package/src/modules/usage.ts +0 -117
  63. package/src/modules/vectorStores.ts +0 -257
  64. package/src/modules/workspaces.ts +0 -216
  65. package/src/sse/client.ts +0 -179
@@ -1,878 +0,0 @@
1
- /**
2
- * Runs Module - Core execution API (Fully Typed)
3
- *
4
- * Naming conventions:
5
- * - get* for singular items
6
- * - list* for collections
7
- * - create*, update*, delete* for mutations
8
- */
9
-
10
- import type { RawClient, APIResponse, components } from "../client/raw.js";
11
- import type { PaginationParams, PaginatedResponse } from "../client/helpers.js";
12
- import { parseSSE, type SSEEvent } from "../sse/client.js";
13
-
14
- // Type aliases from OpenAPI
15
- type WaitRunResponse = components["schemas"]["WaitRunResponse"];
16
- type BatchRunResponse = components["schemas"]["BatchRunResponse"];
17
- type CancelRunResponse = components["schemas"]["CancelRunResponse"];
18
- type CheckpointIndexResponse = components["schemas"]["CheckpointIndexResponse"];
19
- type RunReplayResponse = components["schemas"]["RunReplayResponse"];
20
-
21
- // Response types
22
- export interface Run {
23
- run_id: string;
24
- status: RunStatus;
25
- thread_id?: string;
26
- agent_id: string;
27
- bundle_id?: string;
28
- agent_version_id?: string;
29
- tenant_id: string;
30
- workspace_id: string;
31
- input?: unknown;
32
- output?: unknown;
33
- error?: unknown;
34
- current_attempt_id?: string;
35
- current_attempt_no?: number;
36
- latest_seq?: number;
37
- created_at: string;
38
- started_at?: string;
39
- completed_at?: string;
40
- duration_ms?: number;
41
- reused?: boolean;
42
- // HITL (Human-in-the-Loop) fields
43
- human_prompt?: string;
44
- checkpoint_id?: string;
45
- // Replay fields
46
- replayed_from_run_id?: string;
47
- replayed_from_checkpoint_id?: string;
48
- replay_mode?: "best_effort" | "strict";
49
- }
50
-
51
- // Run status values
52
- export type RunStatus =
53
- | "pending"
54
- | "queued"
55
- | "running"
56
- | "completed"
57
- | "failed"
58
- | "cancelled"
59
- | "waiting_for_human"
60
- | "resumed";
61
-
62
- export interface CreateRunResponse {
63
- run_id: string;
64
- status: string;
65
- reused?: boolean;
66
- }
67
-
68
- export type RunListResponse = PaginatedResponse<Run>;
69
-
70
- /** Wave 2.3: Seq-based polling response for execution events */
71
- export interface RunEventsPollResponse {
72
- events: RunEventDto[];
73
- latest_seq: number;
74
- next_after_seq: number | null;
75
- has_more: boolean;
76
- }
77
-
78
- /** Single event from seq-based polling */
79
- export interface RunEventDto {
80
- id: string;
81
- seq: number;
82
- type: string;
83
- timestamp: string;
84
- attempt_id: string;
85
- payload: Record<string, unknown> | null;
86
- operation_id: string;
87
- parent_operation_id?: string | null;
88
- root_operation_id: string;
89
- }
90
-
91
- export interface RunInspectionResponse {
92
- run: Record<string, unknown>;
93
- replay: Record<string, unknown>;
94
- failure: Record<string, unknown>;
95
- attempts: Array<Record<string, unknown>>;
96
- node_executions: Array<Record<string, unknown>>;
97
- events: Array<Record<string, unknown>>;
98
- }
99
-
100
- export interface RunFailureRetentionItem {
101
- failure_id: string;
102
- failure_scope: string;
103
- run_id: string;
104
- attempt_id: string;
105
- attempt_no: number;
106
- agent_id?: string | null;
107
- thread_id: string;
108
- node_execution_id?: string | null;
109
- operation_id?: string | null;
110
- capability_ref?: string | null;
111
- capability_version?: string | null;
112
- execution_binding?: string | null;
113
- source_kind: string;
114
- ancestry_kind: string;
115
- status: string;
116
- error_code: string;
117
- error_category: string;
118
- is_retryable: boolean;
119
- error_source: string;
120
- provider_error_code?: string | null;
121
- error_summary: string;
122
- retried_from_node_execution_id?: string | null;
123
- replayed_from_node_execution_id?: string | null;
124
- can_retry: boolean;
125
- retry_from_node_execution_id?: string | null;
126
- can_replay: boolean;
127
- replay_from_node_execution_id?: string | null;
128
- failed_at: string;
129
- created_at: string;
130
- }
131
-
132
- export interface RunFailureRetentionListResponse {
133
- items: RunFailureRetentionItem[];
134
- count: number;
135
- }
136
-
137
- export interface RunFailureRetentionDetailResponse {
138
- failure: RunFailureRetentionItem;
139
- payload?: unknown;
140
- }
141
-
142
- export interface RunObservabilitySummaryResponse {
143
- workspace_id: string;
144
- agent_id?: string | null;
145
- from?: string | null;
146
- to?: string | null;
147
- runs_total: number;
148
- runs_completed: number;
149
- runs_failed: number;
150
- node_executions_total: number;
151
- node_executions_failed: number;
152
- retry_count: number;
153
- replay_count: number;
154
- handoff_count: number;
155
- avg_run_latency_ms: number;
156
- p95_run_latency_ms: number;
157
- avg_node_latency_ms: number;
158
- failure_rate: number;
159
- top_error_categories: Array<{
160
- error_category: string;
161
- count: number;
162
- }>;
163
- }
164
-
165
- export interface RunObservabilityCapabilityListResponse {
166
- items: Array<Record<string, unknown>>;
167
- count: number;
168
- }
169
-
170
- export interface RunObservabilityBindingListResponse {
171
- items: Array<Record<string, unknown>>;
172
- count: number;
173
- }
174
-
175
- export class RunsModule {
176
- constructor(private client: RawClient) { }
177
-
178
- // ======================== CRUD ========================
179
-
180
- /**
181
- * Create a new run.
182
- * @example
183
- * ```ts
184
- * const { data } = await client.runs.create({
185
- * agent_id: "agent-uuid",
186
- * input: { message: "Hello" }
187
- * });
188
- *
189
- * // With idempotency (safe to retry)
190
- * const { data } = await client.runs.create({
191
- * agent_id: "agent-uuid",
192
- * input: { message: "Hello" },
193
- * idempotency_key: "my-unique-key"
194
- * });
195
- * ```
196
- */
197
- async create(body: {
198
- agent_id: string;
199
- thread?: { thread_id?: string } | { new_thread: true };
200
- input?: unknown;
201
- /** Optional bundle pinning (draft/published). When omitted, backend resolves live bundle. */
202
- bundle_id?: string;
203
- /** Idempotency key for safe retries. When set, duplicate requests with the same key return the original response. */
204
- idempotency_key?: string;
205
- }): Promise<APIResponse<CreateRunResponse>> {
206
- // Send canonical X-Idempotency-Key header + body idempotency_key for backend contract parity.
207
- const headers: Record<string, string> = {};
208
- if (body.idempotency_key) {
209
- headers["X-Idempotency-Key"] = body.idempotency_key;
210
- }
211
-
212
- return this.client.POST<CreateRunResponse>("/v1/api/runs", {
213
- body,
214
- headers,
215
- });
216
- }
217
-
218
- /**
219
- * Get a run by ID.
220
- */
221
- async get(runId: string): Promise<APIResponse<Run>> {
222
- return this.client.GET<Run>("/v1/api/runs/{runId}", {
223
- params: { path: { runId } },
224
- });
225
- }
226
-
227
- async getInspection(runId: string): Promise<APIResponse<RunInspectionResponse>> {
228
- return this.client.GET<RunInspectionResponse>("/v1/api/runs/{runId}/inspection", {
229
- params: { path: { runId } },
230
- });
231
- }
232
-
233
- async listFailures(params?: {
234
- agent_id?: string;
235
- capability_ref?: string;
236
- error_category?: string;
237
- retryable?: boolean;
238
- from?: string;
239
- to?: string;
240
- limit?: number;
241
- offset?: number;
242
- }): Promise<APIResponse<RunFailureRetentionListResponse>> {
243
- return this.client.GET<RunFailureRetentionListResponse>("/v1/api/runs/failures", {
244
- params: { query: params },
245
- });
246
- }
247
-
248
- async getFailure(failureId: string): Promise<APIResponse<RunFailureRetentionDetailResponse>> {
249
- return this.client.GET<RunFailureRetentionDetailResponse>("/v1/api/runs/failures/{failureId}", {
250
- params: { path: { failureId } },
251
- });
252
- }
253
-
254
- /**
255
- * List runs with optional filters.
256
- */
257
- async list(params?: PaginationParams & {
258
- thread_id?: string;
259
- agent_id?: string;
260
- status?: string;
261
- }): Promise<APIResponse<RunListResponse>> {
262
- return this.client.GET<RunListResponse>("/v1/api/runs", {
263
- params: { query: params },
264
- });
265
- }
266
-
267
- /**
268
- * Iterate through all runs with automatic pagination.
269
- *
270
- * @example
271
- * ```ts
272
- * // Stream all completed runs
273
- * for await (const run of client.runs.iterate({ status: "completed" })) {
274
- * console.log(run.run_id, run.status);
275
- * }
276
- *
277
- * // With limit
278
- * for await (const run of client.runs.iterate({ agent_id: "..." }, { maxItems: 100 })) {
279
- * processRun(run);
280
- * }
281
- * ```
282
- */
283
- async *iterate(
284
- filters?: {
285
- thread_id?: string;
286
- agent_id?: string;
287
- status?: string;
288
- },
289
- options?: { pageSize?: number; maxItems?: number; signal?: AbortSignal }
290
- ): AsyncGenerator<Run, void, unknown> {
291
- const pageSize = options?.pageSize ?? 100;
292
- const maxItems = options?.maxItems ?? Infinity;
293
- let offset = 0;
294
- let yielded = 0;
295
- let hasMore = true;
296
-
297
- while (hasMore && yielded < maxItems) {
298
- if (options?.signal?.aborted) return;
299
-
300
- const response = await this.list({
301
- ...filters,
302
- limit: Math.min(pageSize, maxItems - yielded),
303
- offset,
304
- });
305
-
306
- if (response.error) throw response.error;
307
- const data = response.data!;
308
-
309
- for (const run of data.items) {
310
- if (yielded >= maxItems) return;
311
- yield run;
312
- yielded++;
313
- }
314
-
315
- offset += data.items.length;
316
- hasMore = offset < data.total && data.items.length > 0;
317
- }
318
- }
319
-
320
- // ======================== Execution ========================
321
-
322
- /**
323
- * Wait for run completion synchronously.
324
- */
325
- async wait(body: {
326
- agent_id: string;
327
- thread?: { thread_id?: string } | { new_thread: true };
328
- input?: unknown;
329
- timeout_seconds?: number;
330
- }): Promise<APIResponse<WaitRunResponse>> {
331
- return this.client.POST<WaitRunResponse>("/v1/api/runs/wait", {
332
- body,
333
- });
334
- }
335
-
336
- /**
337
- * Create batch runs.
338
- */
339
- async batch(body: {
340
- agent_id: string;
341
- inputs: Array<{ input: unknown; idempotency_key?: string }>;
342
- }): Promise<APIResponse<BatchRunResponse>> {
343
- return this.client.POST<BatchRunResponse>("/v1/api/runs/batch", {
344
- body,
345
- });
346
- }
347
-
348
- /**
349
- * Cancel a running run.
350
- */
351
- async cancel(runId: string, reason?: string): Promise<APIResponse<CancelRunResponse>> {
352
- return this.client.POST<CancelRunResponse>("/v1/api/runs/{runId}/cancel", {
353
- params: { path: { runId } },
354
- body: { reason },
355
- });
356
- }
357
-
358
- async getObservabilitySummary(params?: {
359
- agent_id?: string;
360
- from?: string;
361
- to?: string;
362
- }): Promise<APIResponse<RunObservabilitySummaryResponse>> {
363
- return this.client.GET<RunObservabilitySummaryResponse>("/v1/api/runs/observability/summary", {
364
- params: { query: params },
365
- });
366
- }
367
-
368
- async getObservabilityCapabilities(params?: {
369
- agent_id?: string;
370
- from?: string;
371
- to?: string;
372
- limit?: number;
373
- offset?: number;
374
- }): Promise<APIResponse<RunObservabilityCapabilityListResponse>> {
375
- return this.client.GET<RunObservabilityCapabilityListResponse>("/v1/api/runs/observability/capabilities", {
376
- params: { query: params },
377
- });
378
- }
379
-
380
- async getObservabilityBindings(params?: {
381
- agent_id?: string;
382
- from?: string;
383
- to?: string;
384
- limit?: number;
385
- offset?: number;
386
- }): Promise<APIResponse<RunObservabilityBindingListResponse>> {
387
- return this.client.GET<RunObservabilityBindingListResponse>("/v1/api/runs/observability/bindings", {
388
- params: { query: params },
389
- });
390
- }
391
-
392
- /**
393
- * Resume a run waiting for human input (HITL).
394
- * After the run reaches status 'waiting_for_human', use this to provide
395
- * the human response and continue execution.
396
- *
397
- * @param runId - The run ID to resume
398
- * @param body - The human input to provide
399
- * @returns Updated run (status will change to 'resumed' then 'queued')
400
- *
401
- * @example
402
- * ```ts
403
- * // List runs waiting for human input
404
- * const waiting = await client.runs.list({ status: 'waiting_for_human' })
405
- *
406
- * // Get the prompt
407
- * const run = await client.runs.get(runId)
408
- * console.log(run.human_prompt) // "Deseja continuar com a compra?"
409
- *
410
- * // Respond
411
- * await client.runs.resume(runId, { input: { answer: "sim" } })
412
- * ```
413
- */
414
- async resume(runId: string, body: { input: unknown }): Promise<APIResponse<Run>> {
415
- return this.client.POST<Run>("/v1/api/runs/{runId}/resume", {
416
- params: { path: { runId } },
417
- body,
418
- });
419
- }
420
-
421
- /**
422
- * Rerun from start (stateless).
423
- */
424
- async rerun(runId: string): Promise<APIResponse<CreateRunResponse>> {
425
- return this.client.POST<CreateRunResponse>("/v1/api/runs/{runId}/rerun", {
426
- params: { path: { runId } },
427
- });
428
- }
429
-
430
- /**
431
- * Replay from checkpoint (stateful).
432
- * Creates a NEW run that starts from the specified checkpoint.
433
- */
434
- async replay(
435
- runId: string,
436
- checkpointId: string,
437
- options?: {
438
- mode?: "best_effort" | "strict";
439
- reason?: string;
440
- }
441
- ): Promise<APIResponse<RunReplayResponse>> {
442
- return this.client.POST<RunReplayResponse>("/v1/api/runs/{runId}/checkpoints/{checkpointId}/replay", {
443
- params: { path: { runId, checkpointId } },
444
- body: {
445
- mode: options?.mode,
446
- reason: options?.reason
447
- },
448
- });
449
- }
450
-
451
- // ======================== Events ========================
452
-
453
- /**
454
- * Get canonical run events from SSOT ledger (polling by seq).
455
- */
456
- async getEvents(runId: string, params: {
457
- attemptId: string;
458
- afterSeq?: number;
459
- limit?: number;
460
- }): Promise<APIResponse<RunEventsPollResponse>> {
461
- return this.client.GET<RunEventsPollResponse>("/v1/api/runs/{runId}/events", {
462
- params: {
463
- path: { runId },
464
- query: {
465
- attemptId: params.attemptId,
466
- afterSeq: params.afterSeq ?? 0,
467
- limit: params.limit ?? 100,
468
- },
469
- },
470
- });
471
- }
472
-
473
- /** Alias: runs.events() -> runs.getEvents() */
474
- events = (runId: string, params: { attemptId: string; afterSeq?: number; limit?: number }) =>
475
- this.getEvents(runId, params);
476
-
477
- /**
478
- * Wave 2.3: Seq-based event polling for execution trace.
479
- * Use this for incremental polling with reconnection support.
480
- *
481
- * @param runId - Run ID to poll events for
482
- * @param params - Polling options
483
- * @returns Events with seq cursors for pagination
484
- *
485
- * @example
486
- * ```ts
487
- * let afterSeq = 0;
488
- * const attemptId = "attempt-uuid";
489
- * while (run.status === 'running') {
490
- * const { data } = await client.runs.pollEvents(runId, { attemptId, afterSeq });
491
- * for (const event of data.events) {
492
- * console.log(event.type, event.payload);
493
- * }
494
- * afterSeq = data.next_after_seq;
495
- * await sleep(1000);
496
- * }
497
- * ```
498
- */
499
- async pollEvents(runId: string, params: {
500
- attemptId: string;
501
- afterSeq?: number;
502
- limit?: number;
503
- }): Promise<APIResponse<RunEventsPollResponse>> {
504
- return this.client.GET<RunEventsPollResponse>("/v1/api/runs/{runId}/events", {
505
- params: {
506
- path: { runId },
507
- query: {
508
- attemptId: params.attemptId,
509
- afterSeq: params.afterSeq ?? 0,
510
- limit: params.limit ?? 100
511
- }
512
- },
513
- });
514
- }
515
-
516
- // ======================== Checkpoints ========================
517
-
518
- /**
519
- * Get checkpoints for a run.
520
- */
521
- async getCheckpoints(runId: string): Promise<APIResponse<CheckpointIndexResponse>> {
522
- return this.client.GET<CheckpointIndexResponse>("/v1/api/runs/{runId}/checkpoints/index", {
523
- params: { path: { runId } },
524
- });
525
- }
526
-
527
- /** Alias: runs.checkpoints() -> runs.getCheckpoints() */
528
- checkpoints = (runId: string) => this.getCheckpoints(runId);
529
-
530
- // ======================== FOLLOW (SSOT SSE) ========================
531
-
532
- /**
533
- * Follow a run attempt's canonical event stream with automatic reconnection.
534
- *
535
- * SSOT contract:
536
- * - Stream is keyed by (run_id, attempt_id, seq)
537
- * - Resume is driven by `afterSeq` (monotonic)
538
- * - No implicit attempt inference
539
- *
540
- * @example
541
- * ```ts
542
- * for await (const event of client.runs.follow(runId, attemptId)) {
543
- * if (event.type === "run_event") {
544
- * console.log(event.payload);
545
- * } else if (event.type === "close") {
546
- * console.log("Run completed");
547
- * }
548
- * }
549
- * ```
550
- */
551
- async *follow(runId: string, attemptId: string, options?: FollowOptions): AsyncGenerator<FollowEvent, void, unknown> {
552
- if (!attemptId) {
553
- throw new Error("attemptId is required for run event streaming");
554
- }
555
-
556
- const signal = options?.signal;
557
- const maxReconnects = options?.maxReconnects ?? 10;
558
- const baseDelayMs = options?.baseDelayMs ?? 1000;
559
- const maxDelayMs = options?.maxDelayMs ?? 30000;
560
-
561
- // nextSeq = next seq we expect to receive (or 0 at start)
562
- let nextSeq = options?.startSeq ?? 0;
563
- let reconnectCount = 0;
564
- let receivedTerminal = false;
565
-
566
- options?.onConnect?.();
567
-
568
- while (!receivedTerminal && reconnectCount <= maxReconnects) {
569
- if (signal?.aborted) return;
570
-
571
- try {
572
- // Build headers - Last-Event-ID is the LAST seq we received (nextSeq - 1)
573
- const headers: Record<string, string> = {};
574
- if (nextSeq > 0) {
575
- headers["Last-Event-ID"] = String(nextSeq - 1);
576
- }
577
-
578
- const response = await this.client.streamGet("/v1/api/runs/{runId}/events/stream", {
579
- params: {
580
- path: { runId },
581
- query: {
582
- attemptId,
583
- afterSeq: nextSeq > 0 ? nextSeq : 0,
584
- },
585
- },
586
- headers,
587
- });
588
-
589
- // Check for auth errors - don't reconnect on these
590
- if (response.status === 401 || response.status === 403) {
591
- const errorEvent: FollowEvent = {
592
- type: "error",
593
- payload: { code: `HTTP_${response.status}`, message: response.statusText },
594
- seq: -1,
595
- timestamp: new Date().toISOString(),
596
- };
597
- yield errorEvent;
598
- return; // Don't reconnect on auth errors
599
- }
600
-
601
- if (!response.ok) {
602
- throw new Error(`SSE connection failed: ${response.status}`);
603
- }
604
-
605
- // Reset reconnect count on successful connection
606
- if (reconnectCount > 0) {
607
- options?.onReconnect?.(reconnectCount);
608
- }
609
- reconnectCount = 0;
610
-
611
- // Parse SSE stream
612
- for await (const rawEvent of parseSSE<unknown>(response)) {
613
- if (signal?.aborted) return;
614
-
615
- // Narrow to known event types
616
- const event = narrowFollowEvent(rawEvent);
617
- if (!event) continue; // Skip unknown event types
618
-
619
- // Update seq tracking if event has seq
620
- if (typeof event.seq === "number" && event.seq >= 0) {
621
- nextSeq = event.seq + 1;
622
- }
623
-
624
- if (event.type === "error") {
625
- // Error event from server - yield but continue (might reconnect)
626
- yield event;
627
- // If it's a fatal error, break and try reconnect
628
- break;
629
- }
630
-
631
- yield event;
632
-
633
- if (event.type === "run_event" && isTerminalRunEvent(event)) {
634
- receivedTerminal = true;
635
- yield {
636
- type: "close",
637
- seq: event.seq,
638
- timestamp: event.timestamp,
639
- payload: { terminal: true },
640
- };
641
- return;
642
- }
643
- }
644
-
645
- // Stream ended without close event - this is unexpected EOF
646
- // Reconnect unless we already received terminal
647
- if (!receivedTerminal && !signal?.aborted) {
648
- options?.onDisconnect?.("eof");
649
- reconnectCount++;
650
- if (reconnectCount <= maxReconnects) {
651
- await sleep(calculateBackoff(reconnectCount, baseDelayMs, maxDelayMs));
652
- }
653
- }
654
-
655
- } catch (err) {
656
- if (signal?.aborted) return;
657
-
658
- options?.onDisconnect?.("error");
659
- reconnectCount++;
660
-
661
- if (reconnectCount <= maxReconnects) {
662
- await sleep(calculateBackoff(reconnectCount, baseDelayMs, maxDelayMs));
663
- } else {
664
- // Max reconnects exceeded - yield error and stop
665
- yield {
666
- type: "error",
667
- payload: { code: "MAX_RECONNECTS", message: `Max reconnects (${maxReconnects}) exceeded` },
668
- seq: -1,
669
- timestamp: new Date().toISOString(),
670
- };
671
- return;
672
- }
673
- }
674
- }
675
- }
676
-
677
- /**
678
- * Create run and follow canonical SSOT stream for the current attempt.
679
- */
680
- async *createAndStream(
681
- body: {
682
- agent_id: string;
683
- thread?: { thread_id?: string } | { new_thread: true };
684
- input?: unknown;
685
- /** Idempotency key for safe retries. */
686
- idempotency_key?: string;
687
- },
688
- options?: FollowOptions
689
- ): AsyncGenerator<FollowEvent, void, unknown> {
690
- const { data, error } = await this.create(body);
691
- if (error || !data) {
692
- throw new Error(`Failed to create run: ${JSON.stringify(error)}`);
693
- }
694
-
695
- const runId = data.run_id;
696
- const runResponse = await this.get(runId);
697
- if (runResponse.error || !runResponse.data?.current_attempt_id) {
698
- throw new Error("Run created without current_attempt_id; cannot stream SSOT events");
699
- }
700
-
701
- yield* this.follow(runId, runResponse.data.current_attempt_id, options);
702
- }
703
-
704
- /**
705
- * Wait for a specific event that matches a predicate.
706
- *
707
- * @example
708
- * ```ts
709
- * // Wait for run completion
710
- * const event = await client.runs.waitFor(runId, attemptId, (e) => e.type === "close", {
711
- * timeoutMs: 60000
712
- * });
713
- *
714
- * // Wait for specific node execution
715
- * const nodeEvent = await client.runs.waitFor(runId, attemptId, (e) =>
716
- * e.type === "run_event" && e.payload?.node === "my_node"
717
- * );
718
- * ```
719
- */
720
- async waitFor(
721
- runId: string,
722
- attemptId: string,
723
- predicate: (event: FollowEvent) => boolean,
724
- options?: { timeoutMs?: number; signal?: AbortSignal; startSeq?: number }
725
- ): Promise<FollowEvent> {
726
- const timeoutMs = options?.timeoutMs ?? 300_000; // 5 min default
727
- const controller = new AbortController();
728
-
729
- // Combine parent signal with our timeout
730
- const onAbort = () => controller.abort();
731
- options?.signal?.addEventListener("abort", onAbort, { once: true });
732
-
733
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
734
-
735
- try {
736
- for await (const event of this.follow(runId, attemptId, {
737
- signal: controller.signal,
738
- startSeq: options?.startSeq,
739
- })) {
740
- if (predicate(event)) {
741
- return event;
742
- }
743
-
744
- // If we got close without matching predicate, error
745
- if (event.type === "close") {
746
- throw new Error("Run completed without matching event");
747
- }
748
- }
749
-
750
- // Stream ended without match or terminal
751
- throw new Error("Stream ended without matching event");
752
-
753
- } catch (err) {
754
- if (controller.signal.aborted && !options?.signal?.aborted) {
755
- // Our timeout triggered the abort
756
- const { TimeoutError } = await import("../errors/index.js");
757
- throw new TimeoutError(timeoutMs);
758
- }
759
- throw err;
760
- } finally {
761
- clearTimeout(timeoutId);
762
- options?.signal?.removeEventListener("abort", onAbort);
763
- }
764
- }
765
- }
766
-
767
- // ============================================================================
768
- // Follow Types & Helpers
769
- // ============================================================================
770
-
771
- /** Options for runs.follow() */
772
- export interface FollowOptions {
773
- /** Starting sequence number (default: 0 = from beginning) */
774
- startSeq?: number;
775
- /** AbortSignal for cancellation */
776
- signal?: AbortSignal;
777
- /** Max reconnection attempts (default: 10) */
778
- maxReconnects?: number;
779
- /** Base delay for backoff in ms (default: 1000) */
780
- baseDelayMs?: number;
781
- /** Max delay for backoff in ms (default: 30000) */
782
- maxDelayMs?: number;
783
- /** Called when initially connected */
784
- onConnect?: () => void;
785
- /** Called when reconnecting (with attempt number) */
786
- onReconnect?: (attempt: number) => void;
787
- /** Called when disconnected (with reason) */
788
- onDisconnect?: (reason: "eof" | "error") => void;
789
- }
790
-
791
- /** Event emitted by runs.follow() */
792
- export interface FollowEvent {
793
- type: "run_event" | "heartbeat" | "close" | "error";
794
- seq: number;
795
- timestamp: string;
796
- payload?: Record<string, unknown> | null;
797
- operation_id?: string;
798
- parent_operation_id?: string | null;
799
- root_operation_id?: string;
800
- /** For run_event: the node that emitted this event */
801
- node?: string;
802
- }
803
-
804
- /** Narrow raw SSE event to FollowEvent */
805
- export function narrowFollowEvent(raw: SSEEvent<unknown>): FollowEvent | null {
806
- const eventType = raw.event ?? "message";
807
- const validTypes = ["run_event", "heartbeat", "close", "error"];
808
-
809
- if (!validTypes.includes(eventType)) {
810
- return null;
811
- }
812
-
813
- const data = raw.data as Record<string, unknown> | null;
814
-
815
- // Canonical payload from backend SSE is RunEventDto:
816
- // { id, seq, type, timestamp, attempt_id, payload }
817
- if (eventType === "run_event" && data) {
818
- const seq = typeof data.seq === "number" ? data.seq : (raw.id ? parseInt(raw.id, 10) : -1);
819
- const timestamp = typeof data.timestamp === "string" ? data.timestamp : new Date().toISOString();
820
- const innerPayload = (typeof data.payload === "object" && data.payload !== null)
821
- ? (data.payload as Record<string, unknown>)
822
- : null;
823
- const operationId = typeof data.operation_id === "string" ? data.operation_id : undefined;
824
- const parentOperationId = typeof data.parent_operation_id === "string"
825
- ? data.parent_operation_id
826
- : data.parent_operation_id === null
827
- ? null
828
- : undefined;
829
- const rootOperationId = typeof data.root_operation_id === "string" ? data.root_operation_id : undefined;
830
-
831
- return {
832
- type: "run_event",
833
- seq,
834
- timestamp,
835
- payload: {
836
- ...(innerPayload ?? {}),
837
- type: typeof data.type === "string" ? data.type : "UNKNOWN",
838
- attempt_id: typeof data.attempt_id === "string" ? data.attempt_id : undefined,
839
- operation_id: operationId,
840
- parent_operation_id: parentOperationId,
841
- root_operation_id: rootOperationId,
842
- },
843
- operation_id: operationId,
844
- parent_operation_id: parentOperationId,
845
- root_operation_id: rootOperationId,
846
- node: typeof innerPayload?.node === "string" ? innerPayload.node : undefined,
847
- };
848
- }
849
-
850
- return {
851
- type: eventType as FollowEvent["type"],
852
- seq: typeof data?.seq === "number" ? data.seq : (raw.id ? parseInt(raw.id, 10) : -1),
853
- timestamp: typeof data?.timestamp === "string" ? data.timestamp : new Date().toISOString(),
854
- payload: data,
855
- node: typeof data?.node === "string" ? data.node : undefined,
856
- };
857
- }
858
-
859
- function isTerminalRunEvent(event: FollowEvent): boolean {
860
- const type = typeof event.payload?.type === "string"
861
- ? event.payload.type.toUpperCase()
862
- : "";
863
-
864
- return type === "RUN_FINISHED" || type === "RUN_FAILED" || type === "RUN_CANCELLED";
865
- }
866
-
867
- /** Calculate exponential backoff with jitter */
868
- function calculateBackoff(attempt: number, baseMs: number, maxMs: number): number {
869
- const exponential = baseMs * Math.pow(2, attempt - 1);
870
- const capped = Math.min(exponential, maxMs);
871
- const jitter = capped * 0.2 * Math.random();
872
- return Math.floor(capped + jitter);
873
- }
874
-
875
- /** Sleep utility */
876
- function sleep(ms: number): Promise<void> {
877
- return new Promise(resolve => setTimeout(resolve, ms));
878
- }