@nylorun/agents 0.30.0-beta → 0.31.1-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/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.30.0-beta",
3
+ "version": "0.31.1-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.31.0-beta",
46
+ "@nylorun/core": "0.33.0-beta",
47
47
  "gray-matter": "^4.0.3"
48
48
  },
49
49
  "peerDependencies": {