@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.
@@ -9,7 +9,7 @@
9
9
 
10
10
  import type { RawClient, APIResponse, components } from "../client/raw.js";
11
11
  import type { PaginationParams, PaginatedResponse } from "../client/helpers.js";
12
- import { parseSSE, type SSEEvent, type RunStreamEvent, type SSEOptions } from "../sse/client.js";
12
+ import { parseSSE, type SSEEvent } from "../sse/client.js";
13
13
 
14
14
  // Type aliases from OpenAPI
15
15
  type WaitRunResponse = components["schemas"]["WaitRunResponse"];
@@ -29,6 +29,9 @@ export interface Run {
29
29
  input?: unknown;
30
30
  output?: unknown;
31
31
  error?: unknown;
32
+ current_attempt_id?: string;
33
+ current_attempt_no?: number;
34
+ latest_seq?: number;
32
35
  created_at: string;
33
36
  started_at?: string;
34
37
  completed_at?: string;
@@ -54,15 +57,6 @@ export type RunStatus =
54
57
  | "waiting_for_human"
55
58
  | "resumed";
56
59
 
57
- export interface RunEvent {
58
- id: string;
59
- run_id: string;
60
- event_type: string;
61
- node?: string;
62
- data?: unknown;
63
- timestamp: string;
64
- }
65
-
66
60
  export interface CreateRunResponse {
67
61
  run_id: string;
68
62
  status: string;
@@ -70,13 +64,12 @@ export interface CreateRunResponse {
70
64
  }
71
65
 
72
66
  export type RunListResponse = PaginatedResponse<Run>;
73
- export type RunEventsResponse = PaginatedResponse<RunEvent>;
74
67
 
75
68
  /** Wave 2.3: Seq-based polling response for execution events */
76
69
  export interface RunEventsPollResponse {
77
70
  events: RunEventDto[];
78
71
  latest_seq: number;
79
- next_after_seq: number;
72
+ next_after_seq: number | null;
80
73
  has_more: boolean;
81
74
  }
82
75
 
@@ -321,20 +314,28 @@ export class RunsModule {
321
314
  // ======================== Events ========================
322
315
 
323
316
  /**
324
- * Get run events for timeline (paged, no SSE).
325
- * @example
326
- * ```ts
327
- * const { data } = await client.runs.getEvents("run-uuid");
328
- * ```
317
+ * Get canonical run events from SSOT ledger (polling by seq).
329
318
  */
330
- async getEvents(runId: string, params?: PaginationParams): Promise<APIResponse<RunEventsResponse>> {
331
- return this.client.GET<RunEventsResponse>("/v1/api/runs/{runId}/events", {
332
- params: { path: { runId }, query: params },
319
+ async getEvents(runId: string, params: {
320
+ attemptId: string;
321
+ afterSeq?: number;
322
+ limit?: number;
323
+ }): Promise<APIResponse<RunEventsPollResponse>> {
324
+ return this.client.GET<RunEventsPollResponse>("/v1/api/runs/{runId}/events", {
325
+ params: {
326
+ path: { runId },
327
+ query: {
328
+ attemptId: params.attemptId,
329
+ afterSeq: params.afterSeq ?? 0,
330
+ limit: params.limit ?? 100,
331
+ },
332
+ },
333
333
  });
334
334
  }
335
335
 
336
336
  /** Alias: runs.events() -> runs.getEvents() */
337
- events = (runId: string, params?: PaginationParams) => this.getEvents(runId, params);
337
+ events = (runId: string, params: { attemptId: string; afterSeq?: number; limit?: number }) =>
338
+ this.getEvents(runId, params);
338
339
 
339
340
  /**
340
341
  * Wave 2.3: Seq-based event polling for execution trace.
@@ -347,8 +348,9 @@ export class RunsModule {
347
348
  * @example
348
349
  * ```ts
349
350
  * let afterSeq = 0;
351
+ * const attemptId = "attempt-uuid";
350
352
  * while (run.status === 'running') {
351
- * const { data } = await client.runs.pollEvents(runId, { afterSeq });
353
+ * const { data } = await client.runs.pollEvents(runId, { attemptId, afterSeq });
352
354
  * for (const event of data.events) {
353
355
  * console.log(event.type, event.payload);
354
356
  * }
@@ -357,16 +359,18 @@ export class RunsModule {
357
359
  * }
358
360
  * ```
359
361
  */
360
- async pollEvents(runId: string, params?: {
362
+ async pollEvents(runId: string, params: {
363
+ attemptId: string;
361
364
  afterSeq?: number;
362
365
  limit?: number;
363
366
  }): Promise<APIResponse<RunEventsPollResponse>> {
364
- return this.client.GET<RunEventsPollResponse>("/v1/api/runs/{runId}/events/stream", {
367
+ return this.client.GET<RunEventsPollResponse>("/v1/api/runs/{runId}/events", {
365
368
  params: {
366
369
  path: { runId },
367
370
  query: {
368
- afterSeq: params?.afterSeq ?? 0,
369
- limit: params?.limit ?? 100
371
+ attemptId: params.attemptId,
372
+ afterSeq: params.afterSeq ?? 0,
373
+ limit: params.limit ?? 100
370
374
  }
371
375
  },
372
376
  });
@@ -386,79 +390,19 @@ export class RunsModule {
386
390
  /** Alias: runs.checkpoints() -> runs.getCheckpoints() */
387
391
  checkpoints = (runId: string) => this.getCheckpoints(runId);
388
392
 
389
- // ======================== STREAMING ========================
390
-
391
- /**
392
- * Stream run events via SSE.
393
- * @example
394
- * ```ts
395
- * for await (const event of client.runs.stream("run-uuid")) {
396
- * console.log(event.data);
397
- * }
398
- * ```
399
- */
400
- async *stream(runId: string, options?: SSEOptions): AsyncGenerator<SSEEvent<RunStreamEvent>> {
401
- const response = await this.client.streamGet("/v1/api/runs/{runId}/stream", {
402
- params: { path: { runId } },
403
- headers: options?.headers,
404
- });
405
- yield* parseSSE<RunStreamEvent>(response, { onOpen: options?.onOpen });
406
- }
407
-
408
- /**
409
- * Create run and stream output.
410
- * @example
411
- * ```ts
412
- * for await (const event of client.runs.createAndStream({
413
- * agent_id: "...",
414
- * input: { message: "Hello" }
415
- * })) {
416
- * console.log(event);
417
- * }
418
- * ```
419
- */
420
- async *createAndStream(
421
- body: {
422
- agent_id: string;
423
- thread?: { thread_id?: string } | { new_thread: true };
424
- input?: unknown;
425
- },
426
- options?: SSEOptions
427
- ): AsyncGenerator<SSEEvent<RunStreamEvent>> {
428
- const { data, error } = await this.create(body);
429
- if (error || !data) {
430
- throw new Error(`Failed to create run: ${JSON.stringify(error)}`);
431
- }
432
-
433
- const runId = data.run_id;
434
- yield* this.stream(runId, options);
435
- }
436
-
437
- /**
438
- * Join an existing run's stream (resume watching).
439
- */
440
- async *join(runId: string, options?: SSEOptions): AsyncGenerator<SSEEvent<RunStreamEvent>> {
441
- const response = await this.client.streamGet("/v1/api/runs/{runId}/join", {
442
- params: { path: { runId } },
443
- headers: options?.headers,
444
- });
445
- yield* parseSSE<RunStreamEvent>(response, { onOpen: options?.onOpen });
446
- }
447
-
448
- // ======================== FOLLOW (Enterprise SSE) ========================
393
+ // ======================== FOLLOW (SSOT SSE) ========================
449
394
 
450
395
  /**
451
- * Follow a run's event stream with automatic reconnection and resume.
452
- *
453
- * This is the enterprise-grade streaming method that:
454
- * - Reconnects automatically on connection drops
455
- * - Resumes from last received event using Last-Event-ID
456
- * - Never duplicates or loses events
457
- * - Terminates cleanly on 'close' event
396
+ * Follow a run attempt's canonical event stream with automatic reconnection.
397
+ *
398
+ * SSOT contract:
399
+ * - Stream is keyed by (run_id, attempt_id, seq)
400
+ * - Resume is driven by `afterSeq` (monotonic)
401
+ * - No implicit attempt inference
458
402
  *
459
403
  * @example
460
404
  * ```ts
461
- * for await (const event of client.runs.follow(runId)) {
405
+ * for await (const event of client.runs.follow(runId, attemptId)) {
462
406
  * if (event.type === "run_event") {
463
407
  * console.log(event.payload);
464
408
  * } else if (event.type === "close") {
@@ -467,7 +411,11 @@ export class RunsModule {
467
411
  * }
468
412
  * ```
469
413
  */
470
- async *follow(runId: string, options?: FollowOptions): AsyncGenerator<FollowEvent, void, unknown> {
414
+ async *follow(runId: string, attemptId: string, options?: FollowOptions): AsyncGenerator<FollowEvent, void, unknown> {
415
+ if (!attemptId) {
416
+ throw new Error("attemptId is required for run event streaming");
417
+ }
418
+
471
419
  const signal = options?.signal;
472
420
  const maxReconnects = options?.maxReconnects ?? 10;
473
421
  const baseDelayMs = options?.baseDelayMs ?? 1000;
@@ -490,10 +438,13 @@ export class RunsModule {
490
438
  headers["Last-Event-ID"] = String(nextSeq - 1);
491
439
  }
492
440
 
493
- const response = await this.client.streamGet("/v1/api/runs/{runId}/stream", {
441
+ const response = await this.client.streamGet("/v1/api/runs/{runId}/events/stream", {
494
442
  params: {
495
443
  path: { runId },
496
- query: nextSeq > 0 ? { seq: nextSeq } : undefined // Also send as query for backends that support it
444
+ query: {
445
+ attemptId,
446
+ afterSeq: nextSeq > 0 ? nextSeq : 0,
447
+ },
497
448
  },
498
449
  headers,
499
450
  });
@@ -533,13 +484,6 @@ export class RunsModule {
533
484
  nextSeq = event.seq + 1;
534
485
  }
535
486
 
536
- // Check for terminal events
537
- if (event.type === "close") {
538
- receivedTerminal = true;
539
- yield event;
540
- return;
541
- }
542
-
543
487
  if (event.type === "error") {
544
488
  // Error event from server - yield but continue (might reconnect)
545
489
  yield event;
@@ -548,6 +492,17 @@ export class RunsModule {
548
492
  }
549
493
 
550
494
  yield event;
495
+
496
+ if (event.type === "run_event" && isTerminalRunEvent(event)) {
497
+ receivedTerminal = true;
498
+ yield {
499
+ type: "close",
500
+ seq: event.seq,
501
+ timestamp: event.timestamp,
502
+ payload: { terminal: true },
503
+ };
504
+ return;
505
+ }
551
506
  }
552
507
 
553
508
  // Stream ended without close event - this is unexpected EOF
@@ -582,24 +537,52 @@ export class RunsModule {
582
537
  }
583
538
  }
584
539
 
540
+ /**
541
+ * Create run and follow canonical SSOT stream for the current attempt.
542
+ */
543
+ async *createAndStream(
544
+ body: {
545
+ agent_id: string;
546
+ thread?: { thread_id?: string } | { new_thread: true };
547
+ input?: unknown;
548
+ /** Idempotency key for safe retries. */
549
+ idempotency_key?: string;
550
+ },
551
+ options?: FollowOptions
552
+ ): AsyncGenerator<FollowEvent, void, unknown> {
553
+ const { data, error } = await this.create(body);
554
+ if (error || !data) {
555
+ throw new Error(`Failed to create run: ${JSON.stringify(error)}`);
556
+ }
557
+
558
+ const runId = data.run_id;
559
+ const runResponse = await this.get(runId);
560
+ if (runResponse.error || !runResponse.data?.current_attempt_id) {
561
+ throw new Error("Run created without current_attempt_id; cannot stream SSOT events");
562
+ }
563
+
564
+ yield* this.follow(runId, runResponse.data.current_attempt_id, options);
565
+ }
566
+
585
567
  /**
586
568
  * Wait for a specific event that matches a predicate.
587
569
  *
588
570
  * @example
589
571
  * ```ts
590
572
  * // Wait for run completion
591
- * const event = await client.runs.waitFor(runId, (e) => e.type === "close", {
573
+ * const event = await client.runs.waitFor(runId, attemptId, (e) => e.type === "close", {
592
574
  * timeoutMs: 60000
593
575
  * });
594
576
  *
595
577
  * // Wait for specific node execution
596
- * const nodeEvent = await client.runs.waitFor(runId, (e) =>
578
+ * const nodeEvent = await client.runs.waitFor(runId, attemptId, (e) =>
597
579
  * e.type === "run_event" && e.payload?.node === "my_node"
598
580
  * );
599
581
  * ```
600
582
  */
601
583
  async waitFor(
602
584
  runId: string,
585
+ attemptId: string,
603
586
  predicate: (event: FollowEvent) => boolean,
604
587
  options?: { timeoutMs?: number; signal?: AbortSignal; startSeq?: number }
605
588
  ): Promise<FollowEvent> {
@@ -613,7 +596,7 @@ export class RunsModule {
613
596
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
614
597
 
615
598
  try {
616
- for await (const event of this.follow(runId, {
599
+ for await (const event of this.follow(runId, attemptId, {
617
600
  signal: controller.signal,
618
601
  startSeq: options?.startSeq,
619
602
  })) {
@@ -689,6 +672,28 @@ function narrowFollowEvent(raw: SSEEvent<unknown>): FollowEvent | null {
689
672
 
690
673
  const data = raw.data as Record<string, unknown> | null;
691
674
 
675
+ // Canonical payload from backend SSE is RunEventDto:
676
+ // { id, seq, type, timestamp, attempt_id, payload }
677
+ if (eventType === "run_event" && data) {
678
+ const seq = typeof data.seq === "number" ? data.seq : (raw.id ? parseInt(raw.id, 10) : -1);
679
+ const timestamp = typeof data.timestamp === "string" ? data.timestamp : new Date().toISOString();
680
+ const innerPayload = (typeof data.payload === "object" && data.payload !== null)
681
+ ? (data.payload as Record<string, unknown>)
682
+ : null;
683
+
684
+ return {
685
+ type: "run_event",
686
+ seq,
687
+ timestamp,
688
+ payload: {
689
+ ...(innerPayload ?? {}),
690
+ type: typeof data.type === "string" ? data.type : "UNKNOWN",
691
+ attempt_id: typeof data.attempt_id === "string" ? data.attempt_id : undefined,
692
+ },
693
+ node: typeof innerPayload?.node === "string" ? innerPayload.node : undefined,
694
+ };
695
+ }
696
+
692
697
  return {
693
698
  type: eventType as FollowEvent["type"],
694
699
  seq: typeof data?.seq === "number" ? data.seq : (raw.id ? parseInt(raw.id, 10) : -1),
@@ -698,6 +703,14 @@ function narrowFollowEvent(raw: SSEEvent<unknown>): FollowEvent | null {
698
703
  };
699
704
  }
700
705
 
706
+ function isTerminalRunEvent(event: FollowEvent): boolean {
707
+ const type = typeof event.payload?.type === "string"
708
+ ? event.payload.type.toUpperCase()
709
+ : "";
710
+
711
+ return type === "RUN_FINISHED" || type === "RUN_FAILED" || type === "RUN_CANCELLED";
712
+ }
713
+
701
714
  /** Calculate exponential backoff with jitter */
702
715
  function calculateBackoff(attempt: number, baseMs: number, maxMs: number): number {
703
716
  const exponential = baseMs * Math.pow(2, attempt - 1);
package/src/sse/client.ts CHANGED
@@ -27,8 +27,8 @@ export type SSEOptions = {
27
27
  *
28
28
  * @example
29
29
  * ```ts
30
- * const response = await client.streamGet("/v1/api/runs/{runId}/stream", {
31
- * params: { path: { runId } }
30
+ * const response = await client.streamGet("/v1/api/runs/{runId}/events/stream", {
31
+ * params: { path: { runId }, query: { attemptId, afterSeq: 0 } }
32
32
  * });
33
33
  * for await (const event of parseSSE<RunStreamEvent>(response)) {
34
34
  * console.log(event);
@@ -1,39 +0,0 @@
1
- /**
2
- * MCP Module - Fully Typed (Model Context Protocol)
3
- */
4
- import type { RawClient, APIResponse } from "../client/raw.js";
5
- export interface McpServer {
6
- id: string;
7
- name: string;
8
- url: string;
9
- status: "connected" | "disconnected" | "error";
10
- capabilities?: string[];
11
- created_at: string;
12
- }
13
- export interface McpServerListResponse {
14
- items: McpServer[];
15
- total: number;
16
- }
17
- export interface McpToolCallResult {
18
- success: boolean;
19
- output?: unknown;
20
- error?: string;
21
- }
22
- export declare class McpModule {
23
- private client;
24
- private headers;
25
- constructor(client: RawClient, headers: () => Record<string, string>);
26
- /**
27
- * List MCP servers.
28
- */
29
- listServers(): Promise<APIResponse<McpServerListResponse>>;
30
- /**
31
- * Get an MCP server.
32
- */
33
- getServer(serverId: string): Promise<APIResponse<McpServer>>;
34
- /**
35
- * Call an MCP tool.
36
- */
37
- callTool(serverId: string, toolName: string, args: unknown): Promise<APIResponse<McpToolCallResult>>;
38
- }
39
- //# sourceMappingURL=mcp.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/modules/mcp.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/D,MAAM,WAAW,SAAS;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,WAAW,GAAG,cAAc,GAAG,OAAO,CAAC;IAC/C,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IAClC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,SAAS;IACN,OAAO,CAAC,MAAM;IAAa,OAAO,CAAC,OAAO;gBAAlC,MAAM,EAAE,SAAS,EAAU,OAAO,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAEpF;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,qBAAqB,CAAC,CAAC;IAMhE;;OAEG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IAOlE;;OAEG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC;CAO7G"}
@@ -1,38 +0,0 @@
1
- /**
2
- * MCP Module - Fully Typed (Model Context Protocol)
3
- */
4
- export class McpModule {
5
- client;
6
- headers;
7
- constructor(client, headers) {
8
- this.client = client;
9
- this.headers = headers;
10
- }
11
- /**
12
- * List MCP servers.
13
- */
14
- async listServers() {
15
- return this.client.GET("/v1/api/mcp/servers", {
16
- headers: this.headers(),
17
- });
18
- }
19
- /**
20
- * Get an MCP server.
21
- */
22
- async getServer(serverId) {
23
- return this.client.GET("/v1/api/mcp/servers/{id}", {
24
- params: { path: { id: serverId } },
25
- headers: this.headers(),
26
- });
27
- }
28
- /**
29
- * Call an MCP tool.
30
- */
31
- async callTool(serverId, toolName, args) {
32
- return this.client.POST("/v1/api/mcp/servers/{id}/tools/{toolName}/call", {
33
- params: { path: { id: serverId, toolName } },
34
- body: { arguments: args },
35
- headers: this.headers(),
36
- });
37
- }
38
- }
@@ -1,59 +0,0 @@
1
- /**
2
- * MCP Module - Fully Typed (Model Context Protocol)
3
- */
4
-
5
- import type { RawClient, APIResponse } from "../client/raw.js";
6
-
7
- export interface McpServer {
8
- id: string;
9
- name: string;
10
- url: string;
11
- status: "connected" | "disconnected" | "error";
12
- capabilities?: string[];
13
- created_at: string;
14
- }
15
-
16
- export interface McpServerListResponse {
17
- items: McpServer[];
18
- total: number;
19
- }
20
-
21
- export interface McpToolCallResult {
22
- success: boolean;
23
- output?: unknown;
24
- error?: string;
25
- }
26
-
27
- export class McpModule {
28
- constructor(private client: RawClient, private headers: () => Record<string, string>) { }
29
-
30
- /**
31
- * List MCP servers.
32
- */
33
- async listServers(): Promise<APIResponse<McpServerListResponse>> {
34
- return this.client.GET<McpServerListResponse>("/v1/api/mcp/servers", {
35
- headers: this.headers(),
36
- });
37
- }
38
-
39
- /**
40
- * Get an MCP server.
41
- */
42
- async getServer(serverId: string): Promise<APIResponse<McpServer>> {
43
- return this.client.GET<McpServer>("/v1/api/mcp/servers/{id}", {
44
- params: { path: { id: serverId } },
45
- headers: this.headers(),
46
- });
47
- }
48
-
49
- /**
50
- * Call an MCP tool.
51
- */
52
- async callTool(serverId: string, toolName: string, args: unknown): Promise<APIResponse<McpToolCallResult>> {
53
- return this.client.POST<McpToolCallResult>("/v1/api/mcp/servers/{id}/tools/{toolName}/call", {
54
- params: { path: { id: serverId, toolName } },
55
- body: { arguments: args },
56
- headers: this.headers(),
57
- });
58
- }
59
- }