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 +52 -26
- package/dist/_shared/config.d.ts +17 -1
- package/dist/_shared/config.js +19 -2
- package/dist/_shared/index.d.ts +2 -0
- package/dist/_shared/index.js +1 -0
- package/dist/_shared/operations.d.ts +48 -0
- package/dist/_shared/operations.js +149 -0
- package/dist/_shared/proxy-protocol.d.ts +15 -0
- package/dist/_shared/proxy-protocol.js +15 -0
- package/dist/_shared/runtime-types.d.ts +8 -2
- package/dist/_shared/sse.d.ts +74 -0
- package/dist/_shared/sse.js +0 -0
- package/dist/cli.mjs +370 -7
- package/dist/cli.mjs.sha256 +1 -1
- package/dist/client.d.ts +19 -3
- package/dist/client.js +54 -6
- package/dist/client.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/events.md +7 -6
- package/docs/quickstart.md +26 -16
- package/docs/templates.md +45 -16
- package/package.json +1 -1
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
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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 ./
|
|
20
|
-
|
|
21
|
-
antpath status
|
|
22
|
-
antpath events
|
|
23
|
-
antpath outputs
|
|
24
|
-
antpath download <run-id>
|
|
25
|
-
antpath cancel
|
|
26
|
-
antpath delete
|
|
27
|
-
antpath whoami
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
--
|
|
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
|
-
- [
|
|
133
|
+
- [Blueprints](docs/templates.md)
|
|
108
134
|
- [Credentials](docs/credentials.md)
|
|
109
135
|
- [MCP](docs/mcp.md)
|
|
110
136
|
- [Skills](docs/skills.md)
|
package/dist/_shared/config.d.ts
CHANGED
|
@@ -18,7 +18,16 @@ export interface PlatformProxyConfig {
|
|
|
18
18
|
readonly sweeperIntervalMs: number;
|
|
19
19
|
}
|
|
20
20
|
export interface PlatformCaps {
|
|
21
|
-
|
|
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 {};
|
package/dist/_shared/config.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const DEFAULT_CAPS = {
|
|
2
|
-
maxRunDurationMs:
|
|
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:
|
|
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 === "") {
|
package/dist/_shared/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/_shared/index.js
CHANGED
|
@@ -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
|
-
/**
|
|
91
|
-
|
|
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
|