@agent-os-sdk/client 0.9.2 → 0.9.3

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.
@@ -192,15 +192,18 @@ export class RunsModule {
192
192
  }
193
193
  // ======================== Events ========================
194
194
  /**
195
- * Get run events for timeline (paged, no SSE).
196
- * @example
197
- * ```ts
198
- * const { data } = await client.runs.getEvents("run-uuid");
199
- * ```
195
+ * Get canonical run events from SSOT ledger (polling by seq).
200
196
  */
201
197
  async getEvents(runId, params) {
202
198
  return this.client.GET("/v1/api/runs/{runId}/events", {
203
- params: { path: { runId }, query: params },
199
+ params: {
200
+ path: { runId },
201
+ query: {
202
+ attemptId: params.attemptId,
203
+ afterSeq: params.afterSeq ?? 0,
204
+ limit: params.limit ?? 100,
205
+ },
206
+ },
204
207
  });
205
208
  }
206
209
  /** Alias: runs.events() -> runs.getEvents() */
@@ -216,8 +219,9 @@ export class RunsModule {
216
219
  * @example
217
220
  * ```ts
218
221
  * let afterSeq = 0;
222
+ * const attemptId = "attempt-uuid";
219
223
  * while (run.status === 'running') {
220
- * const { data } = await client.runs.pollEvents(runId, { afterSeq });
224
+ * const { data } = await client.runs.pollEvents(runId, { attemptId, afterSeq });
221
225
  * for (const event of data.events) {
222
226
  * console.log(event.type, event.payload);
223
227
  * }
@@ -227,12 +231,13 @@ export class RunsModule {
227
231
  * ```
228
232
  */
229
233
  async pollEvents(runId, params) {
230
- return this.client.GET("/v1/api/runs/{runId}/events/stream", {
234
+ return this.client.GET("/v1/api/runs/{runId}/events", {
231
235
  params: {
232
236
  path: { runId },
233
237
  query: {
234
- afterSeq: params?.afterSeq ?? 0,
235
- limit: params?.limit ?? 100
238
+ attemptId: params.attemptId,
239
+ afterSeq: params.afterSeq ?? 0,
240
+ limit: params.limit ?? 100
236
241
  }
237
242
  },
238
243
  });
@@ -248,66 +253,18 @@ export class RunsModule {
248
253
  }
249
254
  /** Alias: runs.checkpoints() -> runs.getCheckpoints() */
250
255
  checkpoints = (runId) => this.getCheckpoints(runId);
251
- // ======================== STREAMING ========================
256
+ // ======================== FOLLOW (SSOT SSE) ========================
252
257
  /**
253
- * Stream run events via SSE.
254
- * @example
255
- * ```ts
256
- * for await (const event of client.runs.stream("run-uuid")) {
257
- * console.log(event.data);
258
- * }
259
- * ```
260
- */
261
- async *stream(runId, options) {
262
- const response = await this.client.streamGet("/v1/api/runs/{runId}/stream", {
263
- params: { path: { runId } },
264
- headers: options?.headers,
265
- });
266
- yield* parseSSE(response, { onOpen: options?.onOpen });
267
- }
268
- /**
269
- * Create run and stream output.
270
- * @example
271
- * ```ts
272
- * for await (const event of client.runs.createAndStream({
273
- * agent_id: "...",
274
- * input: { message: "Hello" }
275
- * })) {
276
- * console.log(event);
277
- * }
278
- * ```
279
- */
280
- async *createAndStream(body, options) {
281
- const { data, error } = await this.create(body);
282
- if (error || !data) {
283
- throw new Error(`Failed to create run: ${JSON.stringify(error)}`);
284
- }
285
- const runId = data.run_id;
286
- yield* this.stream(runId, options);
287
- }
288
- /**
289
- * Join an existing run's stream (resume watching).
290
- */
291
- async *join(runId, options) {
292
- const response = await this.client.streamGet("/v1/api/runs/{runId}/join", {
293
- params: { path: { runId } },
294
- headers: options?.headers,
295
- });
296
- yield* parseSSE(response, { onOpen: options?.onOpen });
297
- }
298
- // ======================== FOLLOW (Enterprise SSE) ========================
299
- /**
300
- * Follow a run's event stream with automatic reconnection and resume.
258
+ * Follow a run attempt's canonical event stream with automatic reconnection.
301
259
  *
302
- * This is the enterprise-grade streaming method that:
303
- * - Reconnects automatically on connection drops
304
- * - Resumes from last received event using Last-Event-ID
305
- * - Never duplicates or loses events
306
- * - Terminates cleanly on 'close' event
260
+ * SSOT contract:
261
+ * - Stream is keyed by (run_id, attempt_id, seq)
262
+ * - Resume is driven by `afterSeq` (monotonic)
263
+ * - No implicit attempt inference
307
264
  *
308
265
  * @example
309
266
  * ```ts
310
- * for await (const event of client.runs.follow(runId)) {
267
+ * for await (const event of client.runs.follow(runId, attemptId)) {
311
268
  * if (event.type === "run_event") {
312
269
  * console.log(event.payload);
313
270
  * } else if (event.type === "close") {
@@ -316,7 +273,10 @@ export class RunsModule {
316
273
  * }
317
274
  * ```
318
275
  */
319
- async *follow(runId, options) {
276
+ async *follow(runId, attemptId, options) {
277
+ if (!attemptId) {
278
+ throw new Error("attemptId is required for run event streaming");
279
+ }
320
280
  const signal = options?.signal;
321
281
  const maxReconnects = options?.maxReconnects ?? 10;
322
282
  const baseDelayMs = options?.baseDelayMs ?? 1000;
@@ -335,10 +295,13 @@ export class RunsModule {
335
295
  if (nextSeq > 0) {
336
296
  headers["Last-Event-ID"] = String(nextSeq - 1);
337
297
  }
338
- const response = await this.client.streamGet("/v1/api/runs/{runId}/stream", {
298
+ const response = await this.client.streamGet("/v1/api/runs/{runId}/events/stream", {
339
299
  params: {
340
300
  path: { runId },
341
- query: nextSeq > 0 ? { seq: nextSeq } : undefined // Also send as query for backends that support it
301
+ query: {
302
+ attemptId,
303
+ afterSeq: nextSeq > 0 ? nextSeq : 0,
304
+ },
342
305
  },
343
306
  headers,
344
307
  });
@@ -373,12 +336,6 @@ export class RunsModule {
373
336
  if (typeof event.seq === "number" && event.seq >= 0) {
374
337
  nextSeq = event.seq + 1;
375
338
  }
376
- // Check for terminal events
377
- if (event.type === "close") {
378
- receivedTerminal = true;
379
- yield event;
380
- return;
381
- }
382
339
  if (event.type === "error") {
383
340
  // Error event from server - yield but continue (might reconnect)
384
341
  yield event;
@@ -386,6 +343,16 @@ export class RunsModule {
386
343
  break;
387
344
  }
388
345
  yield event;
346
+ if (event.type === "run_event" && isTerminalRunEvent(event)) {
347
+ receivedTerminal = true;
348
+ yield {
349
+ type: "close",
350
+ seq: event.seq,
351
+ timestamp: event.timestamp,
352
+ payload: { terminal: true },
353
+ };
354
+ return;
355
+ }
389
356
  }
390
357
  // Stream ended without close event - this is unexpected EOF
391
358
  // Reconnect unless we already received terminal
@@ -418,23 +385,38 @@ export class RunsModule {
418
385
  }
419
386
  }
420
387
  }
388
+ /**
389
+ * Create run and follow canonical SSOT stream for the current attempt.
390
+ */
391
+ async *createAndStream(body, options) {
392
+ const { data, error } = await this.create(body);
393
+ if (error || !data) {
394
+ throw new Error(`Failed to create run: ${JSON.stringify(error)}`);
395
+ }
396
+ const runId = data.run_id;
397
+ const runResponse = await this.get(runId);
398
+ if (runResponse.error || !runResponse.data?.current_attempt_id) {
399
+ throw new Error("Run created without current_attempt_id; cannot stream SSOT events");
400
+ }
401
+ yield* this.follow(runId, runResponse.data.current_attempt_id, options);
402
+ }
421
403
  /**
422
404
  * Wait for a specific event that matches a predicate.
423
405
  *
424
406
  * @example
425
407
  * ```ts
426
408
  * // Wait for run completion
427
- * const event = await client.runs.waitFor(runId, (e) => e.type === "close", {
409
+ * const event = await client.runs.waitFor(runId, attemptId, (e) => e.type === "close", {
428
410
  * timeoutMs: 60000
429
411
  * });
430
412
  *
431
413
  * // Wait for specific node execution
432
- * const nodeEvent = await client.runs.waitFor(runId, (e) =>
414
+ * const nodeEvent = await client.runs.waitFor(runId, attemptId, (e) =>
433
415
  * e.type === "run_event" && e.payload?.node === "my_node"
434
416
  * );
435
417
  * ```
436
418
  */
437
- async waitFor(runId, predicate, options) {
419
+ async waitFor(runId, attemptId, predicate, options) {
438
420
  const timeoutMs = options?.timeoutMs ?? 300_000; // 5 min default
439
421
  const controller = new AbortController();
440
422
  // Combine parent signal with our timeout
@@ -442,7 +424,7 @@ export class RunsModule {
442
424
  options?.signal?.addEventListener("abort", onAbort, { once: true });
443
425
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
444
426
  try {
445
- for await (const event of this.follow(runId, {
427
+ for await (const event of this.follow(runId, attemptId, {
446
428
  signal: controller.signal,
447
429
  startSeq: options?.startSeq,
448
430
  })) {
@@ -479,6 +461,26 @@ function narrowFollowEvent(raw) {
479
461
  return null;
480
462
  }
481
463
  const data = raw.data;
464
+ // Canonical payload from backend SSE is RunEventDto:
465
+ // { id, seq, type, timestamp, attempt_id, payload }
466
+ if (eventType === "run_event" && data) {
467
+ const seq = typeof data.seq === "number" ? data.seq : (raw.id ? parseInt(raw.id, 10) : -1);
468
+ const timestamp = typeof data.timestamp === "string" ? data.timestamp : new Date().toISOString();
469
+ const innerPayload = (typeof data.payload === "object" && data.payload !== null)
470
+ ? data.payload
471
+ : null;
472
+ return {
473
+ type: "run_event",
474
+ seq,
475
+ timestamp,
476
+ payload: {
477
+ ...(innerPayload ?? {}),
478
+ type: typeof data.type === "string" ? data.type : "UNKNOWN",
479
+ attempt_id: typeof data.attempt_id === "string" ? data.attempt_id : undefined,
480
+ },
481
+ node: typeof innerPayload?.node === "string" ? innerPayload.node : undefined,
482
+ };
483
+ }
482
484
  return {
483
485
  type: eventType,
484
486
  seq: typeof data?.seq === "number" ? data.seq : (raw.id ? parseInt(raw.id, 10) : -1),
@@ -487,6 +489,12 @@ function narrowFollowEvent(raw) {
487
489
  node: typeof data?.node === "string" ? data.node : undefined,
488
490
  };
489
491
  }
492
+ function isTerminalRunEvent(event) {
493
+ const type = typeof event.payload?.type === "string"
494
+ ? event.payload.type.toUpperCase()
495
+ : "";
496
+ return type === "RUN_FINISHED" || type === "RUN_FAILED" || type === "RUN_CANCELLED";
497
+ }
490
498
  /** Calculate exponential backoff with jitter */
491
499
  function calculateBackoff(attempt, baseMs, maxMs) {
492
500
  const exponential = baseMs * Math.pow(2, attempt - 1);
@@ -24,8 +24,8 @@ export type SSEOptions = {
24
24
  *
25
25
  * @example
26
26
  * ```ts
27
- * const response = await client.streamGet("/v1/api/runs/{runId}/stream", {
28
- * params: { path: { runId } }
27
+ * const response = await client.streamGet("/v1/api/runs/{runId}/events/stream", {
28
+ * params: { path: { runId }, query: { attemptId, afterSeq: 0 } }
29
29
  * });
30
30
  * for await (const event of parseSSE<RunStreamEvent>(response)) {
31
31
  * console.log(event);
@@ -12,8 +12,8 @@
12
12
  *
13
13
  * @example
14
14
  * ```ts
15
- * const response = await client.streamGet("/v1/api/runs/{runId}/stream", {
16
- * params: { path: { runId } }
15
+ * const response = await client.streamGet("/v1/api/runs/{runId}/events/stream", {
16
+ * params: { path: { runId }, query: { attemptId, afterSeq: 0 } }
17
17
  * });
18
18
  * for await (const event of parseSSE<RunStreamEvent>(response)) {
19
19
  * console.log(event);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-os-sdk/client",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
4
4
  "description": "Official TypeScript SDK for Agent OS platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -50,4 +50,4 @@
50
50
  "dist",
51
51
  "src"
52
52
  ]
53
- }
53
+ }
@@ -38,6 +38,7 @@ import { createRawClient, type RawClient } from "./raw.js";
38
38
 
39
39
  import { AgentsModule } from "../modules/agents.js";
40
40
  import { BuilderModule } from "../modules/builder.js";
41
+ import { ChatwootModule } from "../modules/chatwoot.js";
41
42
  import { CredentialsModule } from "../modules/credentials.js";
42
43
  import { KnowledgeModule } from "../modules/knowledge.js";
43
44
  import { MembersModule } from "../modules/members.js";
@@ -50,10 +51,10 @@ import { WorkspacesModule } from "../modules/workspaces.js";
50
51
 
51
52
  // Platform modules
52
53
  import { A2aModule } from "../modules/a2a.js";
53
- import { AuthModule } from "../modules/auth.js";
54
54
  import { ApiTokensModule } from "../modules/apiTokens.js";
55
55
  import { ApprovalsModule } from "../modules/approvals.js";
56
56
  import { AuditModule } from "../modules/audit.js";
57
+ import { AuthModule } from "../modules/auth.js";
57
58
  import { CatalogModule } from "../modules/catalog.js";
58
59
  import { CheckpointsModule } from "../modules/checkpoints.js";
59
60
  import { CronsModule } from "../modules/crons.js";
@@ -62,7 +63,6 @@ import { EvaluationModule } from "../modules/evaluation.js";
62
63
  import { FilesModule } from "../modules/files.js";
63
64
  import { GraphsModule } from "../modules/graphs.js";
64
65
  import { InfoModule } from "../modules/info.js";
65
- import { McpModule } from "../modules/mcp.js";
66
66
  import { MeModule } from "../modules/me.js";
67
67
  import { MembershipsModule } from "../modules/memberships.js";
68
68
  import { MetricsModule } from "../modules/metrics.js";
@@ -96,6 +96,7 @@ export class AgentOsClient {
96
96
  readonly members: MembersModule;
97
97
  readonly tenants: TenantsModule;
98
98
  readonly workspaces: WorkspacesModule;
99
+ readonly chatwoot: ChatwootModule;
99
100
 
100
101
  // Platform modules
101
102
  readonly prompts: PromptsModule;
@@ -110,7 +111,6 @@ export class AgentOsClient {
110
111
  readonly store: StoreModule;
111
112
  readonly audit: AuditModule;
112
113
  readonly usage: UsageModule;
113
- readonly mcp: McpModule;
114
114
  readonly a2a: A2aModule;
115
115
  readonly me: MeModule;
116
116
  readonly info: InfoModule;
@@ -164,6 +164,7 @@ export class AgentOsClient {
164
164
  this.members = new MembersModule(this._client, getHeaders);
165
165
  this.tenants = new TenantsModule(this._client, getHeaders);
166
166
  this.workspaces = new WorkspacesModule(this._client, getTenantId, getHeaders);
167
+ this.chatwoot = new ChatwootModule(this._client, getHeaders);
167
168
 
168
169
  // Initialize platform modules
169
170
  this.prompts = new PromptsModule(this._client, getHeaders);
@@ -178,7 +179,6 @@ export class AgentOsClient {
178
179
  this.store = new StoreModule(this._client, getHeaders);
179
180
  this.audit = new AuditModule(this._client, getHeaders);
180
181
  this.usage = new UsageModule(this._client, getHeaders);
181
- this.mcp = new McpModule(this._client, getHeaders);
182
182
  this.a2a = new A2aModule(this._client, getHeaders);
183
183
  this.me = new MeModule(this._client, getHeaders);
184
184
  this.info = new InfoModule(this._client, getHeaders);