@nylorun/agents 0.29.1-beta → 0.31.0-beta

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/dist/index.js CHANGED
@@ -23,3 +23,5 @@ export { RuntimeError, IncompatibleRuntimeError } from "./http.js";
23
23
  export { loadMcpServers, loadPlugin, PluginError } from "./plugins/load.js";
24
24
  export { hashFolder, loadSkillsFromDirectory, resolveSkillsRoot, parseSkill, SkillsError, } from "./skills/index.js";
25
25
  export { McpError } from "./mcp/index.js";
26
+ // The A2A client (`@nylorun/agents/a2a` also has the gateway handler).
27
+ export * from "./a2a/client-api.js";
@@ -439,10 +439,15 @@ export declare class SessionClient {
439
439
  agent?: string;
440
440
  signal?: AbortSignal;
441
441
  }): Promise<SessionItemsResponse>;
442
+ /**
443
+ * The session's events from `cursor` on, reconnecting from the last one when the connection
444
+ * drops or sends nothing, keepalives included, for `idleTimeoutMs` (default 45 s; `0` never).
445
+ */
442
446
  observe(options?: {
443
447
  cursor?: string;
444
448
  signal?: AbortSignal;
445
449
  follow?: boolean;
450
+ idleTimeoutMs?: number;
446
451
  }): AsyncGenerator<{
447
452
  schema: "nylorun.event/2";
448
453
  eventId: string;
@@ -279,6 +279,10 @@ export class SessionClient {
279
279
  ...("tail" in page ? { tail: page.tail } : {}),
280
280
  };
281
281
  }
282
+ /**
283
+ * The session's events from `cursor` on, reconnecting from the last one when the connection
284
+ * drops or sends nothing, keepalives included, for `idleTimeoutMs` (default 45 s; `0` never).
285
+ */
282
286
  async *observe(options = {}) {
283
287
  if (!options.follow) {
284
288
  yield* this.observeOne(this.id, options);
@@ -318,7 +322,11 @@ export class SessionClient {
318
322
  };
319
323
  const pump = async (sessionId, cursor) => {
320
324
  try {
321
- for await (const event of this.observeOne(sessionId, { cursor, signal })) {
325
+ for await (const event of this.observeOne(sessionId, {
326
+ cursor,
327
+ signal,
328
+ ...(options.idleTimeoutMs !== undefined ? { idleTimeoutMs: options.idleTimeoutMs } : {}),
329
+ })) {
322
330
  push(event);
323
331
  const link = linkedSessionId(event);
324
332
  if (link && link !== this.id && !linked.has(link)) {
package/dist/sse.d.ts CHANGED
@@ -4,9 +4,26 @@ export interface SSEEvent {
4
4
  data: string;
5
5
  id?: string;
6
6
  }
7
- /** Fetch-based SSE supports Authorization headers and works in browsers and Node. */
8
- export declare function readSSE(response: Response, signal: AbortSignal): AsyncGenerator<SSEEvent>;
7
+ /**
8
+ * How long a stream may send nothing before the client takes it for dead: the Runtime sends a
9
+ * `: keepalive` comment every 15 s, so three missed ones.
10
+ */
11
+ export declare const SSE_IDLE_TIMEOUT_MS = 45000;
12
+ /** A stream sent no byte, not even a keepalive, for `idleTimeoutMs`. */
13
+ export declare class SSEIdleTimeoutError extends Error {
14
+ readonly idleTimeoutMs: number;
15
+ constructor(idleTimeoutMs: number);
16
+ }
17
+ /**
18
+ * Fetch-based SSE supports Authorization headers and works in browsers and Node. Any byte,
19
+ * keepalive comments included, counts as liveness; after `idleTimeoutMs` without one the read
20
+ * stops with `SSEIdleTimeoutError` (`0` waits forever).
21
+ */
22
+ export declare function readSSE(response: Response, signal: AbortSignal, options?: {
23
+ idleTimeoutMs?: number;
24
+ }): AsyncGenerator<SSEEvent>;
9
25
  export declare function observeSSE(transport: Transport, path: string, options?: {
10
26
  signal?: AbortSignal;
11
27
  cursor?: string;
28
+ idleTimeoutMs?: number;
12
29
  }): AsyncGenerator<SSEEvent>;
package/dist/sse.js CHANGED
@@ -1,6 +1,24 @@
1
1
  import { delay, RuntimeError } from "./http.js";
2
- /** Fetch-based SSE supports Authorization headers and works in browsers and Node. */
3
- export async function* readSSE(response, signal) {
2
+ /**
3
+ * How long a stream may send nothing before the client takes it for dead: the Runtime sends a
4
+ * `: keepalive` comment every 15 s, so three missed ones.
5
+ */
6
+ export const SSE_IDLE_TIMEOUT_MS = 45_000;
7
+ /** A stream sent no byte, not even a keepalive, for `idleTimeoutMs`. */
8
+ export class SSEIdleTimeoutError extends Error {
9
+ idleTimeoutMs;
10
+ constructor(idleTimeoutMs) {
11
+ super(`The event stream sent nothing for ${idleTimeoutMs} ms`);
12
+ this.idleTimeoutMs = idleTimeoutMs;
13
+ this.name = "SSEIdleTimeoutError";
14
+ }
15
+ }
16
+ /**
17
+ * Fetch-based SSE supports Authorization headers and works in browsers and Node. Any byte,
18
+ * keepalive comments included, counts as liveness; after `idleTimeoutMs` without one the read
19
+ * stops with `SSEIdleTimeoutError` (`0` waits forever).
20
+ */
21
+ export async function* readSSE(response, signal, options = {}) {
4
22
  if (!response.headers.get("content-type")?.includes("text/event-stream"))
5
23
  throw new Error("Expected an SSE response");
6
24
  if (!response.body)
@@ -15,9 +33,22 @@ export async function* readSSE(response, signal) {
15
33
  void reader.cancel().catch(() => { });
16
34
  };
17
35
  signal.addEventListener("abort", abort, { once: true });
36
+ const idleTimeoutMs = options.idleTimeoutMs ?? SSE_IDLE_TIMEOUT_MS;
37
+ let idle = false;
38
+ let timer;
18
39
  try {
19
40
  while (!signal.aborted) {
41
+ // Only the wait for bytes counts, never the time the consumer holds an event. Cancelling
42
+ // the reader ends the pending read, as an abort does.
43
+ if (idleTimeoutMs > 0)
44
+ timer = setTimeout(() => {
45
+ idle = true;
46
+ abort();
47
+ }, idleTimeoutMs);
20
48
  const result = await reader.read();
49
+ clearTimeout(timer);
50
+ if (idle)
51
+ throw new SSEIdleTimeoutError(idleTimeoutMs);
21
52
  if (result.done)
22
53
  break;
23
54
  buffer += decoder.decode(result.value, { stream: true });
@@ -55,6 +86,7 @@ export async function* readSSE(response, signal) {
55
86
  }
56
87
  }
57
88
  finally {
89
+ clearTimeout(timer);
58
90
  signal.removeEventListener("abort", abort);
59
91
  await reader.cancel().catch(() => { });
60
92
  reader.releaseLock();
@@ -78,7 +110,10 @@ export async function* observeSSE(transport, path, options = {}) {
78
110
  ...(cursor ? { "Last-Event-ID": cursor } : {}),
79
111
  },
80
112
  });
81
- for await (const event of readSSE(response, signal)) {
113
+ const read = readSSE(response, signal, {
114
+ ...(options.idleTimeoutMs !== undefined ? { idleTimeoutMs: options.idleTimeoutMs } : {}),
115
+ });
116
+ for await (const event of read) {
82
117
  if (event.event === "nylorun.closed") {
83
118
  closedByRuntime += 1;
84
119
  reconnectNow = closedByRuntime === 1;
@@ -94,6 +129,9 @@ export async function* observeSSE(transport, path, options = {}) {
94
129
  catch (error) {
95
130
  if (signal.aborted)
96
131
  return;
132
+ // A silent stream is a dead connection: open a new one from the last event at once.
133
+ if (error instanceof SSEIdleTimeoutError)
134
+ continue;
97
135
  if (error instanceof RuntimeError &&
98
136
  error.status >= 400 &&
99
137
  error.status < 500 &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/agents",
3
- "version": "0.29.1-beta",
3
+ "version": "0.31.0-beta",
4
4
  "description": "Nylorun agent definitions and session client",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -43,7 +43,7 @@
43
43
  "test": "vitest run --passWithNoTests"
44
44
  },
45
45
  "dependencies": {
46
- "@nylorun/core": "0.30.0-beta",
46
+ "@nylorun/core": "0.32.0-beta",
47
47
  "gray-matter": "^4.0.3"
48
48
  },
49
49
  "peerDependencies": {