antpath 0.10.13 → 0.10.15

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/README.md CHANGED
@@ -8,23 +8,28 @@ antpath is a TypeScript-first SDK + CLI for running autonomous Claude Managed Ag
8
8
 
9
9
  ```ts
10
10
  import {
11
- AntpathClient, // the only public class — submits durable runs to a dashboard
12
- defineTemplate,
13
- string,
14
- validateProxyAuth // helper for per-run proxy endpoint auth
11
+ AntpathClient, // the only client class — submits durable runs to a dashboard
12
+ Skill, // workspace / provider / inline skill bundles
13
+ McpServer, // MCP server declarations (headers split into secrets server-side)
14
+ ProxyEndpoint, // per-run managed HTTP proxy endpoint
15
+ AgentsMd, // AGENTS.md / CLAUDE.md uploads
16
+ File, // arbitrary workspace files mounted into the session
17
+ defineRun, // type-safe wrapper around a credential-free `Blueprint`
18
+ validateProxyAuth // helper that fails fast on policy/auth mismatch
15
19
  } from "antpath";
16
20
  ```
17
21
 
18
22
  ```bash
19
- antpath run ./template.json --api-token ant_… \
20
- --anthropic-api-key sk-ant-… --follow
21
- antpath status <run-id> --api-token …
22
- antpath events <run-id> --api-token … --follow
23
- antpath outputs <run-id> --api-token …
24
- antpath download <run-id> <output-id> --out ./local --api-token …
25
- antpath cancel <run-id> --api-token …
26
- antpath delete <run-id> --api-token …
27
- antpath whoami --api-token …
23
+ antpath run --config ./blueprint.json --api-token ant_… \
24
+ --anthropic-api-key sk-ant-… --follow
25
+ antpath status <run-id> --api-token …
26
+ antpath events <run-id> [--follow] --api-token …
27
+ antpath outputs <run-id> --api-token …
28
+ antpath download <run-id> [--out path] --api-token …
29
+ antpath cancel <run-id> --api-token …
30
+ antpath delete <run-id> --api-token …
31
+ antpath whoami --api-token …
32
+ antpath skills <upload|list|get|delete> [flags] --api-token …
28
33
  ```
29
34
 
30
35
  The SDK class and the CLI are backed by the same `@antpath/shared` operations module — any read or write you can do through one, you can do through the other, against the same durable run records. The same npm package also ships the in-container `antpath` CLI as its `bin` entry; the worker mounts that CLI inside every run at `/mnt/session/uploads/antpath/antpath` (Anthropic Managed Agents rebases every session-resource mount under `/mnt/session/uploads/`, and mounted files have no execute permission so they are invoked through `node`), so skills can call `node /mnt/session/uploads/antpath/antpath proxy …` against the per-run manifest. See [Agent-first surface design](../../references/development-principles.md#agent-first-surface-design).
@@ -42,23 +47,17 @@ The dashboard URL defaults to `https://www.antpath.ai`. Self-hosted deployments
42
47
  ## Quickstart (SDK)
43
48
 
44
49
  ```ts
45
- import { AntpathClient, defineTemplate, string } from "antpath";
50
+ import { AntpathClient } from "antpath";
46
51
 
47
52
  const client = new AntpathClient({
48
53
  apiToken: process.env.ANTPATH_API_TOKEN!
49
54
  // baseUrl defaults to https://www.antpath.ai — set it for self-hosted deployments.
50
55
  });
51
56
 
52
- const template = defineTemplate({
53
- name: "hello",
57
+ const ref = await client.submitRun({
54
58
  model: "claude-haiku-4-5",
55
59
  system: "You are a concise automation agent.",
56
- messages: ["Write a short answer about {{topic}}."],
57
- variables: { topic: string() }
58
- });
59
-
60
- const ref = await client.submitRun(template, {
61
- variables: { topic: "agent-first SDK design" },
60
+ prompt: "Write a short answer about agent-first SDK design.",
62
61
  secrets: { anthropic: { apiKey: process.env.ANTHROPIC_API_KEY! } }
63
62
  });
64
63
 
@@ -70,6 +69,23 @@ for (const output of await ref.outputs()) {
70
69
  }
71
70
  ```
72
71
 
72
+ Reusable, credential-free configs use `defineRun`:
73
+
74
+ ```ts
75
+ import { defineRun } from "antpath";
76
+
77
+ const summarise = defineRun((topic: string) => ({
78
+ model: "claude-haiku-4-5",
79
+ system: "You are a concise automation agent.",
80
+ prompt: `Write a short answer about ${topic}.`
81
+ }));
82
+
83
+ const ref = await client.submitRun({
84
+ ...summarise("agent-first SDK design"),
85
+ secrets: { anthropic: { apiKey: process.env.ANTHROPIC_API_KEY! } }
86
+ });
87
+ ```
88
+
73
89
  Stream events live with `ref.stream()`:
74
90
 
75
91
  ```ts
@@ -80,16 +96,26 @@ for await (const event of ref.stream()) {
80
96
  }
81
97
  ```
82
98
 
83
- The same flow from the CLI:
99
+ The same flow from the CLI (two equivalent forms):
84
100
 
85
101
  ```bash
86
- antpath run ./template.json \
102
+ antpath run \
103
+ --api-token "$ANTPATH_API_TOKEN" \
104
+ --anthropic-api-key "$ANTHROPIC_API_KEY" \
105
+ --model claude-haiku-4-5 \
106
+ --system "You are a concise automation agent." \
107
+ --prompt "Write a short answer about agent-first SDK design." \
108
+ --follow
109
+
110
+ antpath run \
87
111
  --api-token "$ANTPATH_API_TOKEN" \
88
112
  --anthropic-api-key "$ANTHROPIC_API_KEY" \
89
- --var topic="agent-first SDK design" \
113
+ --config ./blueprint.json \
90
114
  --follow
91
115
  ```
92
116
 
117
+ `--config` accepts a JSON file matching the `Blueprint` shape: `{ model, system?, prompt, skills?, mcpServers?, environment?, cleanup?, proxyEndpoints?, metadata? }`. There is no template wrapper and no `{{var}}` DSL — interpolate at the call site.
118
+
93
119
  ## Test commands
94
120
 
95
121
  ```text
@@ -104,7 +130,7 @@ pnpm test:user:live # live; same but hits the real platform
104
130
  ## Guides
105
131
 
106
132
  - [Quickstart](docs/quickstart.md)
107
- - [Templates](docs/templates.md)
133
+ - [Blueprints](docs/templates.md)
108
134
  - [Credentials](docs/credentials.md)
109
135
  - [MCP](docs/mcp.md)
110
136
  - [Skills](docs/skills.md)
@@ -18,7 +18,16 @@ export interface PlatformProxyConfig {
18
18
  readonly sweeperIntervalMs: number;
19
19
  }
20
20
  export interface PlatformCaps {
21
- readonly maxRunDurationMs: number;
21
+ /**
22
+ * Wall-clock ceiling on a single run before forced termination.
23
+ * `null` means no antpath-imposed cap, but this is **not unlimited
24
+ * overall**: the upstream provider (e.g. Anthropic Managed Agents)
25
+ * still enforces its own session-lifetime ceiling, and a run that
26
+ * exceeds it terminates regardless. Override via env var
27
+ * `ANTPATH_MAX_RUN_DURATION_MS` to layer an antpath-side cap below
28
+ * the provider's.
29
+ */
30
+ readonly maxRunDurationMs: number | null;
22
31
  readonly maxActiveRunsPerWorkspace: number;
23
32
  readonly maxActiveRunsPerUserOrToken: number;
24
33
  readonly pollingBaseIntervalMs: number;
@@ -44,4 +53,11 @@ export interface PlatformConfig extends PlatformRequiredConfig {
44
53
  type Env = Record<string, string | undefined>;
45
54
  export declare function parsePlatformConfig(env: Env): PlatformConfig;
46
55
  export declare function snapshotRunCaps(config: PlatformConfig): PlatformCaps;
56
+ /**
57
+ * Single source of truth for the `ANTPATH_MAX_RUN_DURATION_MS` env override.
58
+ * Returns `null` when unset (= no antpath-imposed wall-clock cap). Used by
59
+ * `parsePlatformConfig` and by dashboard routes that mint proxy bearers and
60
+ * snapshot caps onto each run.
61
+ */
62
+ export declare function parseMaxRunDurationMs(env: Env): number | null;
47
63
  export {};
@@ -1,5 +1,5 @@
1
1
  const DEFAULT_CAPS = {
2
- maxRunDurationMs: 5 * 60 * 1000,
2
+ maxRunDurationMs: null,
3
3
  maxActiveRunsPerWorkspace: 1,
4
4
  maxActiveRunsPerUserOrToken: 1,
5
5
  pollingBaseIntervalMs: 5_000,
@@ -41,7 +41,7 @@ export function parsePlatformConfig(env) {
41
41
  throw new Error(`Missing required environment variables: ${missing.join(", ")}`);
42
42
  }
43
43
  const caps = {
44
- maxRunDurationMs: optionalPositiveInt(env, "ANTPATH_MAX_RUN_DURATION_MS", DEFAULT_CAPS.maxRunDurationMs),
44
+ maxRunDurationMs: parseMaxRunDurationMs(env),
45
45
  maxActiveRunsPerWorkspace: optionalPositiveInt(env, "ANTPATH_MAX_ACTIVE_RUNS_PER_WORKSPACE", DEFAULT_CAPS.maxActiveRunsPerWorkspace),
46
46
  maxActiveRunsPerUserOrToken: optionalPositiveInt(env, "ANTPATH_MAX_ACTIVE_RUNS_PER_USER_OR_TOKEN", DEFAULT_CAPS.maxActiveRunsPerUserOrToken),
47
47
  pollingBaseIntervalMs: optionalPositiveInt(env, "ANTPATH_POLLING_BASE_INTERVAL_MS", DEFAULT_CAPS.pollingBaseIntervalMs),
@@ -100,6 +100,23 @@ function optionalPositiveInt(env, name, fallback) {
100
100
  function optionalNonNegativeInt(env, name, fallback) {
101
101
  return optionalInt(env, name, fallback, 0);
102
102
  }
103
+ /**
104
+ * Single source of truth for the `ANTPATH_MAX_RUN_DURATION_MS` env override.
105
+ * Returns `null` when unset (= no antpath-imposed wall-clock cap). Used by
106
+ * `parsePlatformConfig` and by dashboard routes that mint proxy bearers and
107
+ * snapshot caps onto each run.
108
+ */
109
+ export function parseMaxRunDurationMs(env) {
110
+ const raw = env.ANTPATH_MAX_RUN_DURATION_MS;
111
+ if (raw === undefined || raw === "") {
112
+ return DEFAULT_CAPS.maxRunDurationMs;
113
+ }
114
+ const value = Number(raw);
115
+ if (!Number.isSafeInteger(value) || value < 1) {
116
+ throw new Error("ANTPATH_MAX_RUN_DURATION_MS must be a safe integer greater than or equal to 1");
117
+ }
118
+ return value;
119
+ }
103
120
  function optionalInt(env, name, fallback, minimum) {
104
121
  const raw = env[name];
105
122
  if (raw === undefined || raw === "") {
@@ -16,5 +16,7 @@ export * from "./runtime-types.js";
16
16
  export * from "./known-events.js";
17
17
  export * from "./http.js";
18
18
  export * as operations from "./operations.js";
19
+ export type { SseStreamOptions, SseStreamOutcome } from "./operations.js";
19
20
  export * from "./proxy-validation.js";
21
+ export * from "./sse.js";
20
22
  export * from "./telemetry.js";
@@ -19,5 +19,6 @@ export * from "./known-events.js";
19
19
  export * from "./http.js";
20
20
  export * as operations from "./operations.js";
21
21
  export * from "./proxy-validation.js";
22
+ export * from "./sse.js";
22
23
  export * from "./telemetry.js";
23
24
  //# sourceMappingURL=index.js.map
@@ -1,6 +1,7 @@
1
1
  import type { HttpClient } from "./http.js";
2
2
  import type { RunUnit } from "./run-unit.js";
3
3
  import type { AgentsMdRecord, FileRecord, Output, Run, RunEvent, SignedOutputLink, Skill, WhoAmI } from "./runtime-types.js";
4
+ import { type SseFrame } from "./sse.js";
4
5
  import type { PlatformFlatRunSubmissionInput, PlatformRunSubmissionInput } from "./submission.js";
5
6
  /**
6
7
  * The single source of truth for SDK<->BFF transport. The SDK class
@@ -31,6 +32,53 @@ export declare function getRun(http: HttpClient, runId: string): Promise<Run>;
31
32
  */
32
33
  export declare function getRunUnit(http: HttpClient, runId: string): Promise<RunUnit>;
33
34
  export declare function listRunEvents(http: HttpClient, runId: string): Promise<readonly RunEvent[]>;
35
+ /**
36
+ * Outcome of an SSE round — caller decides whether to reconnect.
37
+ *
38
+ * - `terminal` — the server emitted `event: terminal` and closed.
39
+ * The run reached a final state; the iterator drained any
40
+ * events that landed alongside the transition.
41
+ * - `disconnected` — the connection closed for any other reason
42
+ * (network error, server-side error, server-side close without
43
+ * a terminal frame). Callers may reconnect with the cursor.
44
+ * - `aborted` — the caller's AbortSignal fired.
45
+ *
46
+ * `nextCursor` is the most recent cursor observed during the round
47
+ * — pass it back as `cursor` to resume.
48
+ */
49
+ export interface SseStreamOutcome {
50
+ readonly kind: "terminal" | "disconnected" | "aborted";
51
+ readonly nextCursor: string | undefined;
52
+ readonly terminalStatus?: string;
53
+ readonly error?: string;
54
+ }
55
+ export interface SseStreamOptions {
56
+ readonly cursor?: string;
57
+ readonly signal?: AbortSignal;
58
+ /**
59
+ * Sink for non-event frames (`ready`, `ping`, `terminal`, `gone`,
60
+ * `error`). Optional — callers that only care about run events can
61
+ * leave this off and just read the iterator.
62
+ */
63
+ readonly onFrame?: (frame: SseFrame) => void;
64
+ }
65
+ /**
66
+ * Open the `GET /api/runs/:runId/events/stream` SSE endpoint and yield
67
+ * each `event: run_event` frame as a parsed `RunEvent`. The iterator
68
+ * completes when the server emits `event: terminal`, when the caller's
69
+ * AbortSignal fires, or when the underlying stream closes.
70
+ *
71
+ * The caller controls reconnection — this function deliberately does
72
+ * NOT auto-reconnect. The SDK wraps it with a backoff loop and falls
73
+ * back to polling when SSE is unavailable; the CLI tails events with
74
+ * the same reconnect contract.
75
+ *
76
+ * Cursor semantics: the dashboard sends each event frame with `id: <cursor>`
77
+ * which natively maps to `Last-Event-ID` on reconnect, AND embeds the
78
+ * cursor inside the JSON payload as `.cursor` so non-EventSource
79
+ * consumers can read it. We surface both.
80
+ */
81
+ export declare function streamRunEventsSse(http: HttpClient, runId: string, options?: SseStreamOptions): AsyncGenerator<RunEvent, SseStreamOutcome, void>;
34
82
  export declare function listOutputs(http: HttpClient, runId: string): Promise<readonly Output[]>;
35
83
  export declare function createOutputLink(http: HttpClient, runId: string, outputId: string): Promise<SignedOutputLink>;
36
84
  export declare function cancelRun(http: HttpClient, runId: string): Promise<void>;
@@ -1,3 +1,4 @@
1
+ import { iterateSse } from "./sse.js";
1
2
  /**
2
3
  * The single source of truth for SDK<->BFF transport. The SDK class
3
4
  * AND the CLI subcommands both call these functions; neither
@@ -40,6 +41,154 @@ export async function listRunEvents(http, runId) {
40
41
  const result = await http.request(`/api/runs/${encodeURIComponent(runId)}/events`);
41
42
  return result.events;
42
43
  }
44
+ /**
45
+ * Open the `GET /api/runs/:runId/events/stream` SSE endpoint and yield
46
+ * each `event: run_event` frame as a parsed `RunEvent`. The iterator
47
+ * completes when the server emits `event: terminal`, when the caller's
48
+ * AbortSignal fires, or when the underlying stream closes.
49
+ *
50
+ * The caller controls reconnection — this function deliberately does
51
+ * NOT auto-reconnect. The SDK wraps it with a backoff loop and falls
52
+ * back to polling when SSE is unavailable; the CLI tails events with
53
+ * the same reconnect contract.
54
+ *
55
+ * Cursor semantics: the dashboard sends each event frame with `id: <cursor>`
56
+ * which natively maps to `Last-Event-ID` on reconnect, AND embeds the
57
+ * cursor inside the JSON payload as `.cursor` so non-EventSource
58
+ * consumers can read it. We surface both.
59
+ */
60
+ export async function* streamRunEventsSse(http, runId, options = {}) {
61
+ const headers = {
62
+ accept: "text/event-stream"
63
+ };
64
+ if (options.cursor) {
65
+ // Both the header and the query-string carry the cursor — the
66
+ // header wins server-side, but some intermediaries strip
67
+ // `Last-Event-ID`, so we belt-and-braces it.
68
+ headers["last-event-id"] = options.cursor;
69
+ }
70
+ const query = {};
71
+ if (options.cursor)
72
+ query.cursor = options.cursor;
73
+ let outcome = {
74
+ kind: "disconnected",
75
+ nextCursor: options.cursor
76
+ };
77
+ let download;
78
+ try {
79
+ const init = { method: "GET", headers };
80
+ if (options.signal)
81
+ init.signal = options.signal;
82
+ download = await http.download(`/api/runs/${encodeURIComponent(runId)}/events/stream`, init, query);
83
+ }
84
+ catch (err) {
85
+ return {
86
+ kind: options.signal?.aborted ? "aborted" : "disconnected",
87
+ nextCursor: options.cursor,
88
+ error: err instanceof Error ? err.message : String(err)
89
+ };
90
+ }
91
+ const body = download.response.body;
92
+ if (!body) {
93
+ return {
94
+ kind: "disconnected",
95
+ nextCursor: options.cursor,
96
+ error: "SSE response body was empty"
97
+ };
98
+ }
99
+ let cursor = options.cursor;
100
+ try {
101
+ for await (const frame of iterateSse(body, options.signal)) {
102
+ options.onFrame?.(frame);
103
+ if (frame.id)
104
+ cursor = frame.id;
105
+ switch (frame.event) {
106
+ case "run_event": {
107
+ const event = parseRunEventFrame(frame.data);
108
+ if (event) {
109
+ // The dashboard duplicates the cursor inside the data
110
+ // payload — prefer it if present, since it survives proxies
111
+ // that strip the `id:` line.
112
+ const payloadCursor = readStringField(event, "cursor");
113
+ if (payloadCursor)
114
+ cursor = payloadCursor;
115
+ yield event;
116
+ }
117
+ break;
118
+ }
119
+ case "terminal": {
120
+ const status = readStringField(parseJsonObject(frame.data) ?? {}, "status");
121
+ outcome = {
122
+ kind: "terminal",
123
+ nextCursor: cursor,
124
+ ...(status ? { terminalStatus: status } : {})
125
+ };
126
+ return outcome;
127
+ }
128
+ case "gone": {
129
+ outcome = {
130
+ kind: "disconnected",
131
+ nextCursor: cursor,
132
+ error: "run no longer exists"
133
+ };
134
+ return outcome;
135
+ }
136
+ case "error": {
137
+ const message = readStringField(parseJsonObject(frame.data) ?? {}, "message") ?? "SSE stream reported an error";
138
+ outcome = {
139
+ kind: "disconnected",
140
+ nextCursor: cursor,
141
+ error: message
142
+ };
143
+ return outcome;
144
+ }
145
+ // `ready` and `ping` are no-ops — they keep proxies alive and
146
+ // surface the initial cursor; consumers see them via `onFrame`.
147
+ }
148
+ }
149
+ }
150
+ catch (err) {
151
+ return {
152
+ kind: options.signal?.aborted ? "aborted" : "disconnected",
153
+ nextCursor: cursor,
154
+ error: err instanceof Error ? err.message : String(err)
155
+ };
156
+ }
157
+ // Stream ended without a terminal frame (server closed unexpectedly).
158
+ return {
159
+ kind: options.signal?.aborted ? "aborted" : "disconnected",
160
+ nextCursor: cursor
161
+ };
162
+ }
163
+ function parseRunEventFrame(data) {
164
+ const parsed = parseJsonObject(data);
165
+ if (!parsed)
166
+ return null;
167
+ // Run events always carry id + type; reject malformed frames.
168
+ const id = readStringField(parsed, "id");
169
+ const type = readStringField(parsed, "type");
170
+ if (!id || !type)
171
+ return null;
172
+ return parsed;
173
+ }
174
+ function parseJsonObject(data) {
175
+ if (data.length === 0)
176
+ return null;
177
+ try {
178
+ const value = JSON.parse(data);
179
+ if (value && typeof value === "object" && !Array.isArray(value)) {
180
+ return value;
181
+ }
182
+ return null;
183
+ }
184
+ catch {
185
+ return null;
186
+ }
187
+ }
188
+ function readStringField(obj, field) {
189
+ const value = obj[field];
190
+ return typeof value === "string" ? value : undefined;
191
+ }
43
192
  export async function listOutputs(http, runId) {
44
193
  const result = await http.request(`/api/runs/${encodeURIComponent(runId)}/outputs`);
45
194
  return result.outputs;
@@ -10,6 +10,21 @@
10
10
  */
11
11
  export declare const PROXY_PROTOCOL_VERSION: "1";
12
12
  export declare const PROXY_PROTOCOL_HEADER = "x-antpath-proxy-protocol";
13
+ /**
14
+ * Default `User-Agent` the proxy attaches to every outbound request when
15
+ * the caller did not supply one via `allowHeaders`. Some upstreams reject
16
+ * requests that arrive without a meaningful UA — notably the Wikimedia
17
+ * family (Wikidata, Wikipedia, Wikimedia Commons), whose policy requires
18
+ * a contactable identifier and otherwise returns HTTP 403 with a
19
+ * `Please identify your user agent` body.
20
+ *
21
+ * Callers can override per request by listing `user-agent` in their
22
+ * endpoint's `allowHeaders` and setting it on the proxy call; the
23
+ * default only fires when nothing was forwarded.
24
+ *
25
+ * See <https://meta.wikimedia.org/wiki/User-Agent_policy>.
26
+ */
27
+ export declare const PROXY_DEFAULT_USER_AGENT = "antpath-proxy/1.0 (+https://antpath.ai/contact)";
13
28
  export declare const PROXY_METHOD_HEADER = "x-antpath-method";
14
29
  export declare const PROXY_PATH_HEADER = "x-antpath-path";
15
30
  export declare const PROXY_QUERY_HEADER = "x-antpath-query";
@@ -22,6 +22,21 @@
22
22
  */
23
23
  export const PROXY_PROTOCOL_VERSION = "1";
24
24
  export const PROXY_PROTOCOL_HEADER = "x-antpath-proxy-protocol";
25
+ /**
26
+ * Default `User-Agent` the proxy attaches to every outbound request when
27
+ * the caller did not supply one via `allowHeaders`. Some upstreams reject
28
+ * requests that arrive without a meaningful UA — notably the Wikimedia
29
+ * family (Wikidata, Wikipedia, Wikimedia Commons), whose policy requires
30
+ * a contactable identifier and otherwise returns HTTP 403 with a
31
+ * `Please identify your user agent` body.
32
+ *
33
+ * Callers can override per request by listing `user-agent` in their
34
+ * endpoint's `allowHeaders` and setting it on the proxy call; the
35
+ * default only fires when nothing was forwarded.
36
+ *
37
+ * See <https://meta.wikimedia.org/wiki/User-Agent_policy>.
38
+ */
39
+ export const PROXY_DEFAULT_USER_AGENT = "antpath-proxy/1.0 (+https://antpath.ai/contact)";
25
40
  export const PROXY_METHOD_HEADER = "x-antpath-method";
26
41
  export const PROXY_PATH_HEADER = "x-antpath-path";
27
42
  export const PROXY_QUERY_HEADER = "x-antpath-query";
@@ -87,8 +87,14 @@ export interface WhoAmI {
87
87
  readonly storageCapBytes?: number;
88
88
  /** Current captured-output usage in bytes. */
89
89
  readonly storageUsedBytes?: number;
90
- /** Wall-clock ceiling on a single run before forced termination. */
91
- readonly maxRunDurationMs?: number;
90
+ /**
91
+ * Wall-clock ceiling on a single run before forced termination.
92
+ * `null` means no antpath-imposed cap, but this is **not unlimited
93
+ * overall**: the upstream provider (e.g. Anthropic Managed Agents)
94
+ * still enforces its own session-lifetime ceiling, and a run that
95
+ * exceeds it terminates regardless.
96
+ */
97
+ readonly maxRunDurationMs?: number | null;
92
98
  };
93
99
  readonly [key: string]: unknown;
94
100
  }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Tiny SSE (text/event-stream) parser used by the SDK / CLI to consume
3
+ * the dashboard's `/api/runs/:id/events/stream` endpoint without
4
+ * pulling in a DOM `EventSource` dependency. Node's `fetch` returns a
5
+ * web `ReadableStream<Uint8Array>` body; this parser turns that into
6
+ * an async iterable of `SseFrame`s.
7
+ *
8
+ * The wire grammar follows the WHATWG HTML spec § Server-sent events:
9
+ *
10
+ * - Lines are separated by `\n`, `\r`, or `\r\n`.
11
+ * - A `:` prefix marks a comment — ignored.
12
+ * - `field: value` (or `field:value`, with no space) — leading space
13
+ * after the colon is stripped.
14
+ * - Recognised fields: `event`, `data`, `id`, `retry`.
15
+ * - Multiple `data:` lines join with `\n`.
16
+ * - A blank line dispatches the current frame and resets the buffer.
17
+ * - `event:` defaults to `"message"` when absent.
18
+ *
19
+ * The parser is deliberately a free function so it can be unit-tested
20
+ * against synthesized chunk boundaries (one byte at a time, mid-frame,
21
+ * across UTF-8 multi-byte sequences) without spinning up a real
22
+ * connection.
23
+ */
24
+ export interface SseFrame {
25
+ readonly id?: string;
26
+ readonly event: string;
27
+ readonly data: string;
28
+ readonly retry?: number;
29
+ }
30
+ /**
31
+ * Stateful parser: feed in chunks (in any size), pull out completed
32
+ * frames. Designed for `for await` consumption — see `iterateSse`.
33
+ */
34
+ export declare class SseParser {
35
+ #private;
36
+ /**
37
+ * Push a chunk of raw bytes through the parser. Returns any frames
38
+ * that completed during this chunk. Chunks may contain zero, one,
39
+ * or many frame boundaries — the parser handles partial trailing
40
+ * data internally.
41
+ */
42
+ pushBytes(chunk: Uint8Array): readonly SseFrame[];
43
+ /**
44
+ * Push a text chunk directly. Used by the parser tests so they can
45
+ * exercise byte-boundary edge cases without manufacturing a
46
+ * `TextDecoder` round-trip.
47
+ */
48
+ pushText(text: string): readonly SseFrame[];
49
+ /**
50
+ * Indicate end-of-stream. Returns any final frame that the dispatcher
51
+ * was holding when the stream closed mid-frame (no trailing blank
52
+ * line). The dispatcher pattern is: blank line dispatches; if the
53
+ * remote closes after a partial frame, that frame is dropped per the
54
+ * spec.
55
+ */
56
+ flush(): void;
57
+ /**
58
+ * Current "last event id" — the cursor the SSE spec instructs the
59
+ * client to replay on automatic reconnection. Callers use this to
60
+ * resume after a disconnect.
61
+ */
62
+ get lastEventId(): string | undefined;
63
+ }
64
+ /**
65
+ * Convert a web `ReadableStream<Uint8Array>` (the body of a fetch
66
+ * Response) into an async iterable of SSE frames. The stream is
67
+ * consumed exactly once; the caller is responsible for cancelling
68
+ * the underlying reader on abort.
69
+ *
70
+ * The reader is raced against the abort signal so a long-blocked
71
+ * server (no chunks for many seconds) still releases the loop
72
+ * promptly when the consumer aborts.
73
+ */
74
+ export declare function iterateSse(body: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncIterable<SseFrame>;
Binary file