@paneui/core 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lalit Singh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # @paneui/core
2
+
3
+ Typed client for the Pane relay HTTP + WebSocket API. Framework-free: no argv,
4
+ no MCP, no server dependencies — just the relay protocol expressed as typed
5
+ operations.
6
+
7
+ ## Runtime requirement: Node.js >= 20
8
+
9
+ `@paneui/core` targets the **Node.js** runtime (>= 20, as declared in
10
+ `package.json`'s `engines`). It is *framework*-free, not *runtime*-free.
11
+
12
+ The WebSocket transport (`openStream`) uses the [`ws`](https://www.npmjs.com/package/ws)
13
+ package rather than the global `WebSocket`. `ws` exposes a Node-style event API
14
+ (`socket.on("message", ...)`, custom upgrade headers such as `Authorization`)
15
+ that the browser `WebSocket` does not, and the relay protocol relies on it.
16
+ Because of this, `@paneui/core` is **not** intended to run in a browser or other
17
+ non-Node runtime as-is.
18
+
19
+ The HTTP surface (`PaneClient`, `registerAgent`) uses the standard `fetch` API
20
+ and is runtime-agnostic; only `openStream` carries the Node constraint.
21
+
22
+ If you need a browser client, treat that as separate future work — it would
23
+ need a `ws`-vs-global-`WebSocket` abstraction rather than the unconditional
24
+ `import { WebSocket } from "ws"` used today.
25
+
26
+ ## Exports
27
+
28
+ - `PaneClient` / `PaneApiError` — typed HTTP operations against a relay.
29
+ - `openStream` — WebSocket stream (replay-on-connect, then live). **Node only.**
30
+ - `registerAgent` — agent registration helper.
31
+ - `artifactSchema`, `callbackSchema`, `createSessionSchema` — Zod schemas.
@@ -0,0 +1,161 @@
1
+ import type { ArtifactRecord, ArtifactSummary, ArtifactType, ArtifactVersion, CreateArtifactResponse, CreateSessionRequest, CreateSessionResponse, EventsPage, KeyInfo, PaneEvent, SessionState } from "./types.js";
2
+ export interface ClientOptions {
3
+ /** Relay base URL, e.g. https://pane.example.com. Trailing slash is trimmed. */
4
+ url: string;
5
+ /** Agent API key (bearer token). */
6
+ apiKey: string;
7
+ /** Optional fetch override (defaults to global fetch). */
8
+ fetch?: typeof fetch;
9
+ }
10
+ /** Low-level relay response: ok flag, HTTP status, parsed JSON body. */
11
+ export interface RelayResponse {
12
+ ok: boolean;
13
+ status: number;
14
+ data: unknown;
15
+ }
16
+ /**
17
+ * Request body for POST /v1/artifacts — create a named, reusable artifact plus
18
+ * its v1 content. Mirrors `createArtifactSchema` from ./schemas.js.
19
+ */
20
+ export interface CreateArtifactRequest {
21
+ name: string;
22
+ slug?: string;
23
+ description?: string;
24
+ tags?: string[];
25
+ source: string;
26
+ type: ArtifactType;
27
+ event_schema?: unknown;
28
+ input_schema?: Record<string, unknown>;
29
+ }
30
+ /**
31
+ * Request body for POST /v1/artifacts/:id/versions — append a new immutable
32
+ * version (content only). Mirrors `createArtifactVersionSchema`.
33
+ */
34
+ export interface CreateArtifactVersionRequest {
35
+ source: string;
36
+ type: ArtifactType;
37
+ event_schema?: unknown;
38
+ input_schema?: Record<string, unknown>;
39
+ }
40
+ /**
41
+ * Request body for PATCH /v1/artifacts/:id — head metadata only (never
42
+ * content). Mirrors `patchArtifactMetadataSchema`.
43
+ */
44
+ export interface PatchArtifactMetadataRequest {
45
+ name?: string;
46
+ slug?: string;
47
+ description?: string;
48
+ tags?: string[];
49
+ }
50
+ /**
51
+ * An error thrown by the typed operations when the relay returns a non-2xx
52
+ * response (or the request fails outright). Carries the HTTP status and the
53
+ * relay error envelope so callers can branch on `code`.
54
+ */
55
+ export declare class PaneApiError extends Error {
56
+ readonly status: number;
57
+ readonly code: string;
58
+ readonly details: unknown;
59
+ /** Agent-friendly remediation hint, when the relay supplies one. */
60
+ readonly hint?: string;
61
+ /** Whether retrying the same request may succeed (e.g. 429). */
62
+ readonly retryable?: boolean;
63
+ /** Documentation URL for this error class (mapped from the wire's `docs_url`). */
64
+ readonly docsUrl?: string;
65
+ constructor(status: number, code: string, message: string, details?: unknown, extra?: {
66
+ hint?: string;
67
+ retryable?: boolean;
68
+ docsUrl?: string;
69
+ });
70
+ }
71
+ export declare class PaneClient {
72
+ private readonly base;
73
+ private readonly apiKey;
74
+ private readonly fetchImpl;
75
+ constructor(opts: ClientOptions);
76
+ /** Relay base URL (trailing slash trimmed). */
77
+ get baseUrl(): string;
78
+ /** WebSocket base URL derived from the relay base URL (http→ws, https→wss). */
79
+ get wsBaseUrl(): string;
80
+ /**
81
+ * Low-level HTTP helper. Mirrors the relay API contract: Bearer auth,
82
+ * JSON bodies, 204 handled. Never throws on non-2xx — returns `ok: false`.
83
+ * Network failures return `{ ok: false, status: 0, ... }`.
84
+ */
85
+ call(method: string, path: string, body?: object): Promise<RelayResponse>;
86
+ /** Assert a 2xx body is a non-null object before treating it as typed JSON. */
87
+ private asObject;
88
+ /** Throw a PaneApiError from a failed RelayResponse. */
89
+ private fail;
90
+ /** POST /v1/sessions — create a session. */
91
+ createSession(req: CreateSessionRequest): Promise<CreateSessionResponse>;
92
+ /** GET /v1/sessions/:id — non-blocking session metadata. */
93
+ getSession(sessionId: string): Promise<SessionState>;
94
+ /**
95
+ * GET /v1/sessions/:id/events — fetch the event log.
96
+ * `since` is an opaque cursor; `waitSeconds` enables the relay long-poll
97
+ * (0 = non-blocking, capped at 30 by the relay).
98
+ */
99
+ getEvents(sessionId: string, opts?: {
100
+ since?: string | null;
101
+ waitSeconds?: number;
102
+ }): Promise<EventsPage>;
103
+ /** POST /v1/sessions/:id/events — append an agent event. */
104
+ sendEvent(sessionId: string, ev: {
105
+ type: string;
106
+ data: unknown;
107
+ causationId?: string;
108
+ idempotencyKey?: string;
109
+ }): Promise<{
110
+ event: PaneEvent;
111
+ deduped: boolean;
112
+ }>;
113
+ /**
114
+ * POST /v1/artifacts — create a named, reusable artifact and its v1 content.
115
+ * Returns the new `artifact_id` and `version` (1).
116
+ */
117
+ createArtifact(req: CreateArtifactRequest): Promise<CreateArtifactResponse>;
118
+ /**
119
+ * POST /v1/artifacts/:id/versions — append a new immutable version to an
120
+ * existing artifact. `idOrSlug` accepts the artifact id or its slug.
121
+ * Returns the new `version` number.
122
+ */
123
+ createArtifactVersion(idOrSlug: string, req: CreateArtifactVersionRequest): Promise<CreateArtifactResponse>;
124
+ /**
125
+ * PATCH /v1/artifacts/:id — update head metadata (name / slug / description /
126
+ * tags); never the content. Returns the updated lean summary.
127
+ */
128
+ updateArtifact(idOrSlug: string, metadata: PatchArtifactMetadataRequest): Promise<ArtifactSummary>;
129
+ /**
130
+ * GET /v1/artifacts?q=... — search/list the agent's named artifacts. The
131
+ * response is lean (no `source` blob), ranked by `last_used_at`. Omit `query`
132
+ * to list every named artifact.
133
+ */
134
+ searchArtifacts(query?: string): Promise<ArtifactSummary[]>;
135
+ /**
136
+ * GET /v1/artifacts/:id — fetch a full artifact (head metadata + version
137
+ * list). `idOrSlug` accepts the artifact id or its slug.
138
+ */
139
+ getArtifact(idOrSlug: string): Promise<ArtifactRecord>;
140
+ /**
141
+ * GET /v1/artifacts/:id/versions/:version — fetch one version's full
142
+ * content (HTML, event schema, input schema).
143
+ */
144
+ getArtifactVersion(idOrSlug: string, version: number): Promise<ArtifactVersion>;
145
+ /**
146
+ * GET /v1/keys — the calling agent's own key info. The relay scopes this to
147
+ * the authenticated agent: it returns one key (the caller's), not a list.
148
+ */
149
+ listKeys(): Promise<KeyInfo>;
150
+ /**
151
+ * DELETE /v1/keys/:id — revoke an API key. The relay only permits revoking
152
+ * the caller's OWN key (any other id is rejected 403): this is a
153
+ * self-destruct. Returns 204 with no body on success.
154
+ */
155
+ revokeKey(id: string): Promise<void>;
156
+ /**
157
+ * DELETE /v1/sessions/:id — close/delete a session. Idempotent on the relay
158
+ * side (an already-closed session still returns 204 with no body).
159
+ */
160
+ deleteSession(id: string): Promise<void>;
161
+ }
package/dist/client.js ADDED
@@ -0,0 +1,281 @@
1
+ // Pane relay HTTP client. Pure: no argv, no process.env reads, no MCP.
2
+ // The caller supplies the relay base URL + API key explicitly.
3
+ import { MAX_RESPONSE_SNIPPET_LENGTH } from "./limits.js";
4
+ /**
5
+ * An error thrown by the typed operations when the relay returns a non-2xx
6
+ * response (or the request fails outright). Carries the HTTP status and the
7
+ * relay error envelope so callers can branch on `code`.
8
+ */
9
+ export class PaneApiError extends Error {
10
+ status;
11
+ code;
12
+ details;
13
+ /** Agent-friendly remediation hint, when the relay supplies one. */
14
+ hint;
15
+ /** Whether retrying the same request may succeed (e.g. 429). */
16
+ retryable;
17
+ /** Documentation URL for this error class (mapped from the wire's `docs_url`). */
18
+ docsUrl;
19
+ constructor(status, code, message, details, extra) {
20
+ super(message);
21
+ this.name = "PaneApiError";
22
+ this.status = status;
23
+ this.code = code;
24
+ this.details = details;
25
+ this.hint = extra?.hint;
26
+ this.retryable = extra?.retryable;
27
+ this.docsUrl = extra?.docsUrl;
28
+ }
29
+ }
30
+ export class PaneClient {
31
+ base;
32
+ apiKey;
33
+ fetchImpl;
34
+ constructor(opts) {
35
+ this.base = opts.url.replace(/\/$/, "");
36
+ this.apiKey = opts.apiKey;
37
+ this.fetchImpl = opts.fetch ?? fetch;
38
+ }
39
+ /** Relay base URL (trailing slash trimmed). */
40
+ get baseUrl() {
41
+ return this.base;
42
+ }
43
+ /** WebSocket base URL derived from the relay base URL (http→ws, https→wss). */
44
+ get wsBaseUrl() {
45
+ const u = new URL(this.base);
46
+ u.protocol = u.protocol === "https:" ? "wss:" : "ws:";
47
+ return u.toString().replace(/\/$/, "");
48
+ }
49
+ /**
50
+ * Low-level HTTP helper. Mirrors the relay API contract: Bearer auth,
51
+ * JSON bodies, 204 handled. Never throws on non-2xx — returns `ok: false`.
52
+ * Network failures return `{ ok: false, status: 0, ... }`.
53
+ */
54
+ async call(method, path, body) {
55
+ const url = this.base + path;
56
+ let res;
57
+ try {
58
+ res = await this.fetchImpl(url, {
59
+ method,
60
+ headers: {
61
+ authorization: "Bearer " + this.apiKey,
62
+ ...(body ? { "content-type": "application/json" } : {}),
63
+ },
64
+ body: body ? JSON.stringify(body) : undefined,
65
+ });
66
+ }
67
+ catch (e) {
68
+ const msg = e instanceof Error ? e.message : String(e);
69
+ return {
70
+ ok: false,
71
+ status: 0,
72
+ data: { error: { code: "fetch_error", message: msg } },
73
+ };
74
+ }
75
+ let data = null;
76
+ if (res.status !== 204) {
77
+ const text = await res.text().catch(() => "");
78
+ if (text !== "") {
79
+ try {
80
+ data = JSON.parse(text);
81
+ }
82
+ catch {
83
+ // Body was not JSON (HTML error page, plain-text proxy error, …).
84
+ // Don't discard it — surface the raw text so callers can diagnose.
85
+ const snippet = text.length > MAX_RESPONSE_SNIPPET_LENGTH
86
+ ? text.slice(0, MAX_RESPONSE_SNIPPET_LENGTH) + "…"
87
+ : text;
88
+ data = {
89
+ error: {
90
+ code: "non_json_response",
91
+ message: `relay returned a non-JSON body (status ${res.status})`,
92
+ details: { body: snippet },
93
+ },
94
+ };
95
+ }
96
+ }
97
+ }
98
+ return { ok: res.ok, status: res.status, data };
99
+ }
100
+ /** Assert a 2xx body is a non-null object before treating it as typed JSON. */
101
+ asObject(r) {
102
+ if (r.data === null ||
103
+ typeof r.data !== "object" ||
104
+ Array.isArray(r.data)) {
105
+ throw new PaneApiError(r.status, "invalid_response", `relay returned a ${r.status} with a non-object body`, { body: r.data });
106
+ }
107
+ return r.data;
108
+ }
109
+ /** Throw a PaneApiError from a failed RelayResponse. */
110
+ fail(r) {
111
+ const err = r.data?.error;
112
+ throw new PaneApiError(r.status, err?.code ?? "relay_error", err?.message ?? `relay returned ${r.status}`, err?.details, {
113
+ hint: err?.hint,
114
+ retryable: err?.retryable,
115
+ docsUrl: err?.docs_url,
116
+ });
117
+ }
118
+ /** POST /v1/sessions — create a session. */
119
+ async createSession(req) {
120
+ const r = await this.call("POST", "/v1/sessions", {
121
+ artifact: req.artifact,
122
+ input_data: req.input_data,
123
+ participants: req.participants,
124
+ ttl: req.ttl,
125
+ metadata: req.metadata,
126
+ callback: req.callback,
127
+ });
128
+ if (!r.ok)
129
+ this.fail(r);
130
+ return this.asObject(r);
131
+ }
132
+ /** GET /v1/sessions/:id — non-blocking session metadata. */
133
+ async getSession(sessionId) {
134
+ const r = await this.call("GET", `/v1/sessions/${encodeURIComponent(sessionId)}`);
135
+ if (!r.ok)
136
+ this.fail(r);
137
+ return this.asObject(r);
138
+ }
139
+ /**
140
+ * GET /v1/sessions/:id/events — fetch the event log.
141
+ * `since` is an opaque cursor; `waitSeconds` enables the relay long-poll
142
+ * (0 = non-blocking, capped at 30 by the relay).
143
+ */
144
+ async getEvents(sessionId, opts = {}) {
145
+ const q = new URLSearchParams();
146
+ if (opts.since != null && opts.since !== "")
147
+ q.set("since", opts.since);
148
+ if (opts.waitSeconds != null && opts.waitSeconds > 0) {
149
+ q.set("wait", String(Math.floor(opts.waitSeconds)));
150
+ }
151
+ const qs = q.toString();
152
+ const r = await this.call("GET", `/v1/sessions/${encodeURIComponent(sessionId)}/events${qs ? "?" + qs : ""}`);
153
+ if (!r.ok)
154
+ this.fail(r);
155
+ return this.asObject(r);
156
+ }
157
+ /** POST /v1/sessions/:id/events — append an agent event. */
158
+ async sendEvent(sessionId, ev) {
159
+ const r = await this.call("POST", `/v1/sessions/${encodeURIComponent(sessionId)}/events`, {
160
+ type: ev.type,
161
+ data: ev.data,
162
+ causation_id: ev.causationId,
163
+ idempotency_key: ev.idempotencyKey,
164
+ });
165
+ if (!r.ok)
166
+ this.fail(r);
167
+ const body = this.asObject(r);
168
+ return { event: body.event, deduped: body.deduped ?? false };
169
+ }
170
+ /**
171
+ * POST /v1/artifacts — create a named, reusable artifact and its v1 content.
172
+ * Returns the new `artifact_id` and `version` (1).
173
+ */
174
+ async createArtifact(req) {
175
+ const r = await this.call("POST", "/v1/artifacts", {
176
+ name: req.name,
177
+ slug: req.slug,
178
+ description: req.description,
179
+ tags: req.tags,
180
+ source: req.source,
181
+ type: req.type,
182
+ event_schema: req.event_schema,
183
+ input_schema: req.input_schema,
184
+ });
185
+ if (!r.ok)
186
+ this.fail(r);
187
+ return this.asObject(r);
188
+ }
189
+ /**
190
+ * POST /v1/artifacts/:id/versions — append a new immutable version to an
191
+ * existing artifact. `idOrSlug` accepts the artifact id or its slug.
192
+ * Returns the new `version` number.
193
+ */
194
+ async createArtifactVersion(idOrSlug, req) {
195
+ const r = await this.call("POST", `/v1/artifacts/${encodeURIComponent(idOrSlug)}/versions`, {
196
+ source: req.source,
197
+ type: req.type,
198
+ event_schema: req.event_schema,
199
+ input_schema: req.input_schema,
200
+ });
201
+ if (!r.ok)
202
+ this.fail(r);
203
+ return this.asObject(r);
204
+ }
205
+ /**
206
+ * PATCH /v1/artifacts/:id — update head metadata (name / slug / description /
207
+ * tags); never the content. Returns the updated lean summary.
208
+ */
209
+ async updateArtifact(idOrSlug, metadata) {
210
+ const r = await this.call("PATCH", `/v1/artifacts/${encodeURIComponent(idOrSlug)}`, {
211
+ name: metadata.name,
212
+ slug: metadata.slug,
213
+ description: metadata.description,
214
+ tags: metadata.tags,
215
+ });
216
+ if (!r.ok)
217
+ this.fail(r);
218
+ return this.asObject(r);
219
+ }
220
+ /**
221
+ * GET /v1/artifacts?q=... — search/list the agent's named artifacts. The
222
+ * response is lean (no `source` blob), ranked by `last_used_at`. Omit `query`
223
+ * to list every named artifact.
224
+ */
225
+ async searchArtifacts(query) {
226
+ const qs = query != null && query !== "" ? "?q=" + encodeURIComponent(query) : "";
227
+ const r = await this.call("GET", `/v1/artifacts${qs}`);
228
+ if (!r.ok)
229
+ this.fail(r);
230
+ return this.asObject(r).artifacts;
231
+ }
232
+ /**
233
+ * GET /v1/artifacts/:id — fetch a full artifact (head metadata + version
234
+ * list). `idOrSlug` accepts the artifact id or its slug.
235
+ */
236
+ async getArtifact(idOrSlug) {
237
+ const r = await this.call("GET", `/v1/artifacts/${encodeURIComponent(idOrSlug)}`);
238
+ if (!r.ok)
239
+ this.fail(r);
240
+ return this.asObject(r);
241
+ }
242
+ /**
243
+ * GET /v1/artifacts/:id/versions/:version — fetch one version's full
244
+ * content (HTML, event schema, input schema).
245
+ */
246
+ async getArtifactVersion(idOrSlug, version) {
247
+ const r = await this.call("GET", `/v1/artifacts/${encodeURIComponent(idOrSlug)}/versions/${encodeURIComponent(String(version))}`);
248
+ if (!r.ok)
249
+ this.fail(r);
250
+ return this.asObject(r);
251
+ }
252
+ /**
253
+ * GET /v1/keys — the calling agent's own key info. The relay scopes this to
254
+ * the authenticated agent: it returns one key (the caller's), not a list.
255
+ */
256
+ async listKeys() {
257
+ const r = await this.call("GET", "/v1/keys");
258
+ if (!r.ok)
259
+ this.fail(r);
260
+ return this.asObject(r);
261
+ }
262
+ /**
263
+ * DELETE /v1/keys/:id — revoke an API key. The relay only permits revoking
264
+ * the caller's OWN key (any other id is rejected 403): this is a
265
+ * self-destruct. Returns 204 with no body on success.
266
+ */
267
+ async revokeKey(id) {
268
+ const r = await this.call("DELETE", `/v1/keys/${encodeURIComponent(id)}`);
269
+ if (!r.ok)
270
+ this.fail(r);
271
+ }
272
+ /**
273
+ * DELETE /v1/sessions/:id — close/delete a session. Idempotent on the relay
274
+ * side (an already-closed session still returns 204 with no body).
275
+ */
276
+ async deleteSession(id) {
277
+ const r = await this.call("DELETE", `/v1/sessions/${encodeURIComponent(id)}`);
278
+ if (!r.ok)
279
+ this.fail(r);
280
+ }
281
+ }
@@ -0,0 +1,10 @@
1
+ export { PaneClient, PaneApiError } from "./client.js";
2
+ export type { ClientOptions, RelayResponse, CreateArtifactRequest, CreateArtifactVersionRequest, PatchArtifactMetadataRequest, } from "./client.js";
3
+ export { openStream } from "./stream.js";
4
+ export type { OpenStreamOptions, StreamHandlers, StreamHandle, } from "./stream.js";
5
+ export { registerAgent } from "./register.js";
6
+ export type { RegisterAgentOptions, RegisterAgentResult } from "./register.js";
7
+ export { artifactSchema, callbackSchema, createSessionSchema, artifactTypeSchema, createArtifactSchema, createArtifactVersionSchema, patchArtifactMetadataSchema, } from "./schemas.js";
8
+ export type { CreateSessionInput } from "./schemas.js";
9
+ export { MAX_EVENT_TYPE_LENGTH, MAX_IDEMPOTENCY_KEY_LENGTH, MAX_RESPONSE_SNIPPET_LENGTH, MAX_FRAME_SNIPPET_LENGTH, } from "./limits.js";
10
+ export type { AuthorKind, PaneEvent, Artifact, ArtifactType, ArtifactVersion, ArtifactRecord, ArtifactSummary, CreateArtifactResponse, KeyInfo, Callback, CreateSessionRequest, CreateSessionResponse, SessionState, EventsPage, RelayError, } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // @paneui/core — typed client for the Pane relay HTTP + WebSocket API.
2
+ // Pure and framework-free: no argv, no MCP, no server deps.
3
+ export { PaneClient, PaneApiError } from "./client.js";
4
+ export { openStream } from "./stream.js";
5
+ export { registerAgent } from "./register.js";
6
+ export { artifactSchema, callbackSchema, createSessionSchema, artifactTypeSchema, createArtifactSchema, createArtifactVersionSchema, patchArtifactMetadataSchema, } from "./schemas.js";
7
+ export { MAX_EVENT_TYPE_LENGTH, MAX_IDEMPOTENCY_KEY_LENGTH, MAX_RESPONSE_SNIPPET_LENGTH, MAX_FRAME_SNIPPET_LENGTH, } from "./limits.js";
@@ -0,0 +1,8 @@
1
+ /** Maximum length of an event type string, in characters. */
2
+ export declare const MAX_EVENT_TYPE_LENGTH = 64;
3
+ /** Maximum length of an idempotency key string, in characters. */
4
+ export declare const MAX_IDEMPOTENCY_KEY_LENGTH = 128;
5
+ /** Maximum number of characters from a raw response body to include in error details. */
6
+ export declare const MAX_RESPONSE_SNIPPET_LENGTH = 500;
7
+ /** Maximum number of characters from a raw stream frame to include in error messages. */
8
+ export declare const MAX_FRAME_SNIPPET_LENGTH = 200;
package/dist/limits.js ADDED
@@ -0,0 +1,10 @@
1
+ // Shared protocol limits used across transports (HTTP and WebSocket).
2
+ // Defined once here so both relay and client code import the same constants.
3
+ /** Maximum length of an event type string, in characters. */
4
+ export const MAX_EVENT_TYPE_LENGTH = 64;
5
+ /** Maximum length of an idempotency key string, in characters. */
6
+ export const MAX_IDEMPOTENCY_KEY_LENGTH = 128;
7
+ /** Maximum number of characters from a raw response body to include in error details. */
8
+ export const MAX_RESPONSE_SNIPPET_LENGTH = 500;
9
+ /** Maximum number of characters from a raw stream frame to include in error messages. */
10
+ export const MAX_FRAME_SNIPPET_LENGTH = 200;
@@ -0,0 +1,25 @@
1
+ export interface RegisterAgentOptions {
2
+ /** Relay base URL, e.g. https://pane.example.com. Trailing slash is trimmed. */
3
+ url: string;
4
+ /** Optional agent display name; the relay defaults it if omitted. */
5
+ name?: string;
6
+ /**
7
+ * Shared registration secret. Sent as `Authorization: Bearer <secret>`.
8
+ * Only needed when the relay runs REGISTRATION_MODE=secret; ignored by
9
+ * relays in open mode and rejected (404) by relays in closed mode.
10
+ */
11
+ secret?: string;
12
+ /** Optional fetch override (defaults to global fetch). */
13
+ fetch?: typeof fetch;
14
+ }
15
+ export interface RegisterAgentResult {
16
+ agent_id: string;
17
+ api_key: string;
18
+ key_prefix: string;
19
+ }
20
+ /**
21
+ * Provision a fresh agent + API key from the relay. Mirrors PaneClient.call's
22
+ * never-throw-raw style: network/parse failures and non-2xx responses are
23
+ * surfaced as PaneApiError.
24
+ */
25
+ export declare function registerAgent(opts: RegisterAgentOptions): Promise<RegisterAgentResult>;
@@ -0,0 +1,62 @@
1
+ // Standalone agent self-registration: POST /v1/register.
2
+ //
3
+ // Unlike PaneClient operations this needs no bearer API key — registration is
4
+ // the call that *obtains* one. Whether the relay endpoint is reachable depends
5
+ // on its REGISTRATION_MODE: a `secret`-mode relay requires the shared
6
+ // registration secret to be passed as a Bearer token (see the `secret` option
7
+ // below). Abuse is bounded server-side by a per-IP rate limit (a 429 surfaces
8
+ // here as a PaneApiError with status 429).
9
+ import { PaneApiError } from "./client.js";
10
+ import { MAX_RESPONSE_SNIPPET_LENGTH } from "./limits.js";
11
+ /**
12
+ * Provision a fresh agent + API key from the relay. Mirrors PaneClient.call's
13
+ * never-throw-raw style: network/parse failures and non-2xx responses are
14
+ * surfaced as PaneApiError.
15
+ */
16
+ export async function registerAgent(opts) {
17
+ const base = opts.url.replace(/\/$/, "");
18
+ const fetchImpl = opts.fetch ?? fetch;
19
+ const body = {};
20
+ if (opts.name !== undefined)
21
+ body["name"] = opts.name;
22
+ const headers = {
23
+ "content-type": "application/json",
24
+ };
25
+ // Sent only when the relay runs REGISTRATION_MODE=secret; harmless otherwise.
26
+ if (opts.secret !== undefined && opts.secret !== "") {
27
+ headers["authorization"] = `Bearer ${opts.secret}`;
28
+ }
29
+ let res;
30
+ try {
31
+ res = await fetchImpl(base + "/v1/register", {
32
+ method: "POST",
33
+ headers,
34
+ body: JSON.stringify(body),
35
+ });
36
+ }
37
+ catch (e) {
38
+ const msg = e instanceof Error ? e.message : String(e);
39
+ throw new PaneApiError(0, "fetch_error", msg);
40
+ }
41
+ let data = null;
42
+ const text = await res.text().catch(() => "");
43
+ if (text !== "") {
44
+ try {
45
+ data = JSON.parse(text);
46
+ }
47
+ catch {
48
+ const snippet = text.length > MAX_RESPONSE_SNIPPET_LENGTH
49
+ ? text.slice(0, MAX_RESPONSE_SNIPPET_LENGTH) + "…"
50
+ : text;
51
+ throw new PaneApiError(res.status, "non_json_response", `relay returned a non-JSON body (status ${res.status})`, { body: snippet });
52
+ }
53
+ }
54
+ if (!res.ok) {
55
+ const err = data?.error;
56
+ throw new PaneApiError(res.status, err?.code ?? "relay_error", err?.message ?? `relay returned ${res.status}`, err?.details);
57
+ }
58
+ if (data === null || typeof data !== "object" || Array.isArray(data)) {
59
+ throw new PaneApiError(res.status, "invalid_response", `relay returned a ${res.status} with a non-object body`, { body: data });
60
+ }
61
+ return data;
62
+ }
@@ -0,0 +1,197 @@
1
+ import { z } from "zod";
2
+ export declare const artifactTypeSchema: z.ZodEnum<["html-inline", "html-ref"]>;
3
+ export declare const artifactSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
4
+ type: z.ZodLiteral<"html-inline">;
5
+ source: z.ZodString;
6
+ }, "strip", z.ZodTypeAny, {
7
+ type: "html-inline";
8
+ source: string;
9
+ }, {
10
+ type: "html-inline";
11
+ source: string;
12
+ }>, z.ZodObject<{
13
+ type: z.ZodLiteral<"html-ref">;
14
+ source: z.ZodString;
15
+ }, "strip", z.ZodTypeAny, {
16
+ type: "html-ref";
17
+ source: string;
18
+ }, {
19
+ type: "html-ref";
20
+ source: string;
21
+ }>]>;
22
+ export declare const callbackSchema: z.ZodObject<{
23
+ url: z.ZodString;
24
+ events: z.ZodArray<z.ZodString, "many">;
25
+ secret: z.ZodString;
26
+ }, "strip", z.ZodTypeAny, {
27
+ url: string;
28
+ events: string[];
29
+ secret: string;
30
+ }, {
31
+ url: string;
32
+ events: string[];
33
+ secret: string;
34
+ }>;
35
+ export declare const createSessionSchema: z.ZodObject<{
36
+ artifact: z.ZodEffects<z.ZodUnion<[z.ZodObject<{
37
+ id: z.ZodString;
38
+ version: z.ZodOptional<z.ZodNumber>;
39
+ }, "strip", z.ZodTypeAny, {
40
+ id: string;
41
+ version?: number | undefined;
42
+ }, {
43
+ id: string;
44
+ version?: number | undefined;
45
+ }>, z.ZodObject<{
46
+ source: z.ZodString;
47
+ type: z.ZodEnum<["html-inline", "html-ref"]>;
48
+ event_schema: z.ZodOptional<z.ZodUnknown>;
49
+ }, "strip", z.ZodTypeAny, {
50
+ type: "html-inline" | "html-ref";
51
+ source: string;
52
+ event_schema?: unknown;
53
+ }, {
54
+ type: "html-inline" | "html-ref";
55
+ source: string;
56
+ event_schema?: unknown;
57
+ }>]>, {
58
+ type: "html-inline" | "html-ref";
59
+ source: string;
60
+ event_schema?: unknown;
61
+ } | {
62
+ id: string;
63
+ version?: number | undefined;
64
+ }, {
65
+ type: "html-inline" | "html-ref";
66
+ source: string;
67
+ event_schema?: unknown;
68
+ } | {
69
+ id: string;
70
+ version?: number | undefined;
71
+ }>;
72
+ input_data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
73
+ participants: z.ZodOptional<z.ZodObject<{
74
+ humans: z.ZodNumber;
75
+ }, "strip", z.ZodTypeAny, {
76
+ humans: number;
77
+ }, {
78
+ humans: number;
79
+ }>>;
80
+ ttl: z.ZodOptional<z.ZodNumber>;
81
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
82
+ callback: z.ZodOptional<z.ZodObject<{
83
+ url: z.ZodString;
84
+ events: z.ZodArray<z.ZodString, "many">;
85
+ secret: z.ZodString;
86
+ }, "strip", z.ZodTypeAny, {
87
+ url: string;
88
+ events: string[];
89
+ secret: string;
90
+ }, {
91
+ url: string;
92
+ events: string[];
93
+ secret: string;
94
+ }>>;
95
+ }, "strip", z.ZodTypeAny, {
96
+ artifact: {
97
+ type: "html-inline" | "html-ref";
98
+ source: string;
99
+ event_schema?: unknown;
100
+ } | {
101
+ id: string;
102
+ version?: number | undefined;
103
+ };
104
+ input_data?: Record<string, unknown> | undefined;
105
+ participants?: {
106
+ humans: number;
107
+ } | undefined;
108
+ ttl?: number | undefined;
109
+ metadata?: Record<string, unknown> | undefined;
110
+ callback?: {
111
+ url: string;
112
+ events: string[];
113
+ secret: string;
114
+ } | undefined;
115
+ }, {
116
+ artifact: {
117
+ type: "html-inline" | "html-ref";
118
+ source: string;
119
+ event_schema?: unknown;
120
+ } | {
121
+ id: string;
122
+ version?: number | undefined;
123
+ };
124
+ input_data?: Record<string, unknown> | undefined;
125
+ participants?: {
126
+ humans: number;
127
+ } | undefined;
128
+ ttl?: number | undefined;
129
+ metadata?: Record<string, unknown> | undefined;
130
+ callback?: {
131
+ url: string;
132
+ events: string[];
133
+ secret: string;
134
+ } | undefined;
135
+ }>;
136
+ export declare const createArtifactSchema: z.ZodObject<{
137
+ name: z.ZodString;
138
+ slug: z.ZodOptional<z.ZodString>;
139
+ description: z.ZodOptional<z.ZodString>;
140
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
141
+ source: z.ZodString;
142
+ type: z.ZodEnum<["html-inline", "html-ref"]>;
143
+ event_schema: z.ZodOptional<z.ZodUnknown>;
144
+ input_schema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
145
+ }, "strip", z.ZodTypeAny, {
146
+ type: "html-inline" | "html-ref";
147
+ source: string;
148
+ name: string;
149
+ event_schema?: unknown;
150
+ slug?: string | undefined;
151
+ description?: string | undefined;
152
+ tags?: string[] | undefined;
153
+ input_schema?: Record<string, unknown> | undefined;
154
+ }, {
155
+ type: "html-inline" | "html-ref";
156
+ source: string;
157
+ name: string;
158
+ event_schema?: unknown;
159
+ slug?: string | undefined;
160
+ description?: string | undefined;
161
+ tags?: string[] | undefined;
162
+ input_schema?: Record<string, unknown> | undefined;
163
+ }>;
164
+ export declare const createArtifactVersionSchema: z.ZodObject<{
165
+ source: z.ZodString;
166
+ type: z.ZodEnum<["html-inline", "html-ref"]>;
167
+ event_schema: z.ZodOptional<z.ZodUnknown>;
168
+ input_schema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
169
+ }, "strip", z.ZodTypeAny, {
170
+ type: "html-inline" | "html-ref";
171
+ source: string;
172
+ event_schema?: unknown;
173
+ input_schema?: Record<string, unknown> | undefined;
174
+ }, {
175
+ type: "html-inline" | "html-ref";
176
+ source: string;
177
+ event_schema?: unknown;
178
+ input_schema?: Record<string, unknown> | undefined;
179
+ }>;
180
+ export declare const patchArtifactMetadataSchema: z.ZodObject<{
181
+ name: z.ZodOptional<z.ZodString>;
182
+ slug: z.ZodOptional<z.ZodString>;
183
+ description: z.ZodOptional<z.ZodString>;
184
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
185
+ }, "strip", z.ZodTypeAny, {
186
+ name?: string | undefined;
187
+ slug?: string | undefined;
188
+ description?: string | undefined;
189
+ tags?: string[] | undefined;
190
+ }, {
191
+ name?: string | undefined;
192
+ slug?: string | undefined;
193
+ description?: string | undefined;
194
+ tags?: string[] | undefined;
195
+ }>;
196
+ /** @deprecated use `CreateSessionRequest` from ./types.js (same type). */
197
+ export type CreateSessionInput = z.infer<typeof createSessionSchema>;
@@ -0,0 +1,82 @@
1
+ // Zod schemas for the Pane relay request shapes. These let callers (the CLI,
2
+ // other clients) validate user-supplied input — e.g. an inline JSON artifact
3
+ // or callback config — before it hits the relay, producing clear errors.
4
+ import { z } from "zod";
5
+ // The artifact `type` discriminant. `html-inline` carries raw HTML in `source`;
6
+ // `html-ref` carries a URL. The relay rejects `html-ref` in this release.
7
+ export const artifactTypeSchema = z.enum(["html-inline", "html-ref"]);
8
+ // Discriminated on `type`: both require a non-empty `source`. Kept for callers
9
+ // that want to validate a bare artifact (no event schema attached).
10
+ export const artifactSchema = z.discriminatedUnion("type", [
11
+ z.object({ type: z.literal("html-inline"), source: z.string().min(1) }),
12
+ z.object({ type: z.literal("html-ref"), source: z.string().min(1) }),
13
+ ]);
14
+ export const callbackSchema = z.object({
15
+ url: z.string().url(),
16
+ events: z.array(z.string().min(1)).min(1),
17
+ secret: z.string().min(8),
18
+ });
19
+ // The inline artifact form for POST /v1/sessions — carries the event schema
20
+ // INSIDE the artifact object (one-off, no registered artifact). The relay
21
+ // transparently creates an anonymous artifact behind it.
22
+ const inlineArtifactSchema = z.object({
23
+ source: z.string().min(1),
24
+ type: artifactTypeSchema,
25
+ // Optional: omit for a view-only one-off (a report/dashboard the human only
26
+ // views — the session then accepts no page/agent events).
27
+ event_schema: z.unknown().optional(),
28
+ });
29
+ // The reference form for POST /v1/sessions — instances an existing named
30
+ // artifact. `id` accepts the artifact id or its slug; `version` is optional
31
+ // and defaults to the artifact's latest version.
32
+ const refArtifactSchema = z.object({
33
+ id: z.string().min(1),
34
+ version: z.number().int().positive().optional(),
35
+ });
36
+ // The session-create `artifact` field: exactly one of the two forms. A union
37
+ // (not a discriminated union — the two forms share no discriminant key) with a
38
+ // refine enforcing exactly-one-of `id` / `source`.
39
+ const sessionArtifactSchema = z
40
+ .union([refArtifactSchema, inlineArtifactSchema])
41
+ .refine((a) => {
42
+ const hasId = "id" in a && a.id !== undefined;
43
+ const hasSource = "source" in a && a.source !== undefined;
44
+ return hasId !== hasSource;
45
+ }, {
46
+ message: "artifact must carry exactly one of `id` (reference an existing artifact) or `source` (inline a one-off artifact)",
47
+ });
48
+ export const createSessionSchema = z.object({
49
+ artifact: sessionArtifactSchema,
50
+ input_data: z.record(z.unknown()).optional(),
51
+ participants: z.object({ humans: z.number().int().positive() }).optional(),
52
+ ttl: z.number().int().positive().optional(),
53
+ metadata: z.record(z.unknown()).optional(),
54
+ callback: callbackSchema.optional(),
55
+ });
56
+ // POST /v1/artifacts — create a named, reusable artifact plus its v1 content.
57
+ export const createArtifactSchema = z.object({
58
+ name: z.string().min(1),
59
+ slug: z.string().min(1).optional(),
60
+ description: z.string().optional(),
61
+ tags: z.array(z.string().min(1)).optional(),
62
+ source: z.string().min(1),
63
+ type: artifactTypeSchema,
64
+ // Optional: omit for a view-only artifact (no event vocabulary).
65
+ event_schema: z.unknown().optional(),
66
+ input_schema: z.record(z.unknown()).optional(),
67
+ });
68
+ // POST /v1/artifacts/:id/versions — append a new version (content only).
69
+ export const createArtifactVersionSchema = z.object({
70
+ source: z.string().min(1),
71
+ type: artifactTypeSchema,
72
+ // Optional: omit for a view-only artifact (no event vocabulary).
73
+ event_schema: z.unknown().optional(),
74
+ input_schema: z.record(z.unknown()).optional(),
75
+ });
76
+ // PATCH /v1/artifacts/:id — update head metadata only (never content).
77
+ export const patchArtifactMetadataSchema = z.object({
78
+ name: z.string().min(1).optional(),
79
+ slug: z.string().min(1).optional(),
80
+ description: z.string().optional(),
81
+ tags: z.array(z.string().min(1)).optional(),
82
+ });
@@ -0,0 +1,51 @@
1
+ import { WebSocket } from "ws";
2
+ import type { PaneEvent } from "./types.js";
3
+ export interface OpenStreamOptions {
4
+ /** WebSocket base URL, e.g. wss://pane.example.com (no trailing slash). */
5
+ wsBaseUrl: string;
6
+ /** Session id. */
7
+ sessionId: string;
8
+ /** Agent (or participant) bearer token. */
9
+ token: string;
10
+ /** Opaque cursor: replay only events strictly after this id. */
11
+ since?: string | null;
12
+ }
13
+ /** Callbacks for a live stream. */
14
+ export interface StreamHandlers {
15
+ /** Fired for every event envelope (replayed and live). */
16
+ onEvent?: (event: PaneEvent) => void;
17
+ /** Fired once when the initial replay finishes. */
18
+ onReplayComplete?: () => void;
19
+ /** Fired on a relay error frame. */
20
+ onRelayError?: (error: {
21
+ code?: string;
22
+ message?: string;
23
+ details?: unknown;
24
+ }) => void;
25
+ /** Fired when the socket closes (cleanly or otherwise). */
26
+ onClose?: (info: {
27
+ code: number;
28
+ reason: string;
29
+ }) => void;
30
+ /** Fired on a transport-level error. */
31
+ onError?: (err: Error) => void;
32
+ }
33
+ /** A live handle to an open stream. */
34
+ export interface StreamHandle {
35
+ /** Send an event frame into the session. */
36
+ send(frame: {
37
+ type: string;
38
+ data?: unknown;
39
+ causation_id?: string;
40
+ idempotency_key?: string;
41
+ }): void;
42
+ /** Close the stream. */
43
+ close(): void;
44
+ /** The underlying ws socket (escape hatch). */
45
+ readonly socket: WebSocket;
46
+ }
47
+ /**
48
+ * Open a WebSocket stream to a Pane session. Replays on connect, then streams
49
+ * live. Returns a handle for sending frames and closing.
50
+ */
51
+ export declare function openStream(opts: OpenStreamOptions, handlers: StreamHandlers): StreamHandle;
package/dist/stream.js ADDED
@@ -0,0 +1,97 @@
1
+ // WebSocket client for WS /v1/sessions/:id/stream.
2
+ //
3
+ // The relay protocol (see the relay's src/ws/handler.ts):
4
+ // - on connect, the relay replays every event since `?since=` (or from the
5
+ // start), then sends a `{ kind: "system.replay.complete" }` marker;
6
+ // - thereafter it pushes live events as they land;
7
+ // - each frame is a JSON object: either a PaneEvent envelope, the replay
8
+ // marker, an `{ ack, deduped }` for frames we sent, or an `{ error }`.
9
+ //
10
+ // Note: `system.participant.joined` / `system.participant.left` (and other
11
+ // `system.*` events) arrive as ordinary `PaneEvent` envelopes — they are not a
12
+ // distinct frame kind, and may be interleaved with the initial replay stream
13
+ // just like any other event.
14
+ //
15
+ // `openStream` exposes this as a typed event emitter over the `ws` package.
16
+ import { WebSocket } from "ws";
17
+ import { MAX_FRAME_SNIPPET_LENGTH } from "./limits.js";
18
+ /**
19
+ * Open a WebSocket stream to a Pane session. Replays on connect, then streams
20
+ * live. Returns a handle for sending frames and closing.
21
+ */
22
+ export function openStream(opts, handlers) {
23
+ const base = opts.wsBaseUrl.replace(/\/$/, "");
24
+ const u = new URL(`${base}/v1/sessions/${encodeURIComponent(opts.sessionId)}/stream`);
25
+ if (opts.since != null && opts.since !== "") {
26
+ u.searchParams.set("since", opts.since);
27
+ }
28
+ // Token via Authorization header (Node ws supports it); the relay also
29
+ // accepts ?token= but the header keeps it out of any URL access log.
30
+ const socket = new WebSocket(u.toString(), {
31
+ headers: { authorization: "Bearer " + opts.token },
32
+ });
33
+ socket.on("message", (raw) => {
34
+ const text = raw.toString();
35
+ let msg;
36
+ try {
37
+ msg = JSON.parse(text);
38
+ }
39
+ catch (e) {
40
+ // A malformed frame must never be silently dropped — a dropped event
41
+ // makes `watch --type X` hang forever. Surface it as a transport error.
42
+ const snippet = text.length > MAX_FRAME_SNIPPET_LENGTH
43
+ ? text.slice(0, MAX_FRAME_SNIPPET_LENGTH) + "…"
44
+ : text;
45
+ handlers.onError?.(new Error(`failed to parse stream frame as JSON (${e instanceof Error ? e.message : String(e)}): ${snippet}`));
46
+ return;
47
+ }
48
+ if (!msg || typeof msg !== "object") {
49
+ handlers.onError?.(new Error(`unexpected non-object stream frame: ${text.slice(0, MAX_FRAME_SNIPPET_LENGTH)}`));
50
+ return;
51
+ }
52
+ const obj = msg;
53
+ if (obj["kind"] === "system.replay.complete") {
54
+ handlers.onReplayComplete?.();
55
+ return;
56
+ }
57
+ if ("error" in obj) {
58
+ handlers.onRelayError?.(obj["error"]);
59
+ return;
60
+ }
61
+ if ("ack" in obj) {
62
+ // Ack for a frame we sent; nothing to surface by default.
63
+ return;
64
+ }
65
+ if (typeof obj["id"] === "string" && typeof obj["type"] === "string") {
66
+ handlers.onEvent?.(obj);
67
+ return;
68
+ }
69
+ // Unrecognized frame shape — route to onError rather than dropping it.
70
+ handlers.onError?.(new Error(`unrecognized stream frame: ${JSON.stringify(obj).slice(0, MAX_FRAME_SNIPPET_LENGTH)}`));
71
+ });
72
+ socket.on("close", (code, reason) => {
73
+ handlers.onClose?.({ code, reason: reason.toString() });
74
+ });
75
+ socket.on("error", (err) => {
76
+ handlers.onError?.(err instanceof Error ? err : new Error(String(err)));
77
+ });
78
+ return {
79
+ send(frame) {
80
+ if (socket.readyState !== WebSocket.OPEN) {
81
+ throw new Error(`cannot send frame: stream socket is not open (readyState=${socket.readyState})`);
82
+ }
83
+ socket.send(JSON.stringify(frame));
84
+ },
85
+ close() {
86
+ try {
87
+ socket.close();
88
+ }
89
+ catch (e) {
90
+ console.debug("[pane] stream close error:", e instanceof Error ? e.message : String(e));
91
+ }
92
+ },
93
+ get socket() {
94
+ return socket;
95
+ },
96
+ };
97
+ }
@@ -0,0 +1,144 @@
1
+ import type { z } from "zod";
2
+ import type { createSessionSchema } from "./schemas.js";
3
+ export type AuthorKind = "human" | "agent" | "system";
4
+ /** A single event envelope as emitted by the relay. */
5
+ export interface PaneEvent {
6
+ id: string;
7
+ session_id: string;
8
+ author: {
9
+ kind: AuthorKind;
10
+ id: string;
11
+ };
12
+ ts: string;
13
+ type: string;
14
+ data: unknown;
15
+ causation_id: string | null;
16
+ idempotency_key: string | null;
17
+ }
18
+ /** The artifact content type. `html-ref` is rejected by the relay for now. */
19
+ export type ArtifactType = "html-inline" | "html-ref";
20
+ /**
21
+ * An artifact: discriminated on `type`. `html-inline` carries raw HTML in
22
+ * `source`; `html-ref` carries a URL the relay/shell fetches on the human's
23
+ * behalf. The discriminant keeps the type↔source coupling explicit.
24
+ */
25
+ export type Artifact = {
26
+ type: "html-inline";
27
+ source: string;
28
+ } | {
29
+ type: "html-ref";
30
+ source: string;
31
+ };
32
+ /** Optional webhook callback config. */
33
+ export interface Callback {
34
+ url: string;
35
+ events: string[];
36
+ secret: string;
37
+ }
38
+ /**
39
+ * Request body for POST /v1/sessions. Derived from `createSessionSchema` so the
40
+ * runtime validator and the static type cannot drift.
41
+ */
42
+ export type CreateSessionRequest = z.infer<typeof createSessionSchema>;
43
+ /** Response from POST /v1/sessions. */
44
+ export interface CreateSessionResponse {
45
+ session_id: string;
46
+ tokens: {
47
+ humans: string[];
48
+ agent: string;
49
+ };
50
+ urls: {
51
+ humans: string[];
52
+ agent_stream: string;
53
+ };
54
+ expires_at: string;
55
+ }
56
+ /** Response from GET /v1/sessions/:id. */
57
+ export interface SessionState {
58
+ session_id: string;
59
+ status: string;
60
+ /** The artifact version this session is pinned to. */
61
+ artifact_id: string;
62
+ artifact_version_id: string;
63
+ artifact_version: number;
64
+ metadata: Record<string, unknown> | null;
65
+ input_data: Record<string, unknown> | null;
66
+ created_at: string;
67
+ expires_at: string;
68
+ }
69
+ /** Response from GET /v1/sessions/:id/events. */
70
+ export interface EventsPage {
71
+ events: PaneEvent[];
72
+ next_cursor: string | null;
73
+ }
74
+ /** One immutable version of an artifact's content. */
75
+ export interface ArtifactVersion {
76
+ id: string;
77
+ version: number;
78
+ type: ArtifactType;
79
+ source: string;
80
+ event_schema: unknown;
81
+ input_schema: Record<string, unknown> | null;
82
+ created_at: string;
83
+ }
84
+ /** A full artifact — head metadata plus its version list. */
85
+ export interface Artifact_ {
86
+ id: string;
87
+ slug: string | null;
88
+ name: string | null;
89
+ description: string | null;
90
+ tags: string[] | null;
91
+ latest_version: number;
92
+ last_used_at: string | null;
93
+ created_at: string;
94
+ updated_at: string;
95
+ versions: ArtifactVersion[];
96
+ }
97
+ /**
98
+ * A full artifact — head metadata plus its version list. (`ArtifactRecord` is
99
+ * the public name; `Artifact` is kept as the older inline-artifact union.)
100
+ */
101
+ export type ArtifactRecord = Artifact_;
102
+ /**
103
+ * A lean artifact summary for list/search responses — head metadata only, no
104
+ * `source` blob. See GET /v1/artifacts.
105
+ */
106
+ export interface ArtifactSummary {
107
+ id: string;
108
+ slug: string | null;
109
+ name: string | null;
110
+ description: string | null;
111
+ tags: string[] | null;
112
+ latest_version: number;
113
+ last_used_at: string | null;
114
+ }
115
+ /** Response from POST /v1/artifacts and POST /v1/artifacts/:id/versions. */
116
+ export interface CreateArtifactResponse {
117
+ artifact_id: string;
118
+ version: number;
119
+ }
120
+ /**
121
+ * Response from GET /v1/keys — the calling agent's own key info. The relay
122
+ * scopes this to the authenticated agent: it returns ONE key (the caller's),
123
+ * not a list.
124
+ */
125
+ export interface KeyInfo {
126
+ agent_id: string;
127
+ name: string | null;
128
+ key_prefix: string;
129
+ created_at: string;
130
+ last_used_at: string | null;
131
+ revoked_at: string | null;
132
+ }
133
+ /** A relay error envelope. */
134
+ export interface RelayError {
135
+ code: string;
136
+ message?: string;
137
+ details?: unknown;
138
+ /** Agent-friendly remediation hint. */
139
+ hint?: string;
140
+ /** Whether retrying the same request may succeed. */
141
+ retryable?: boolean;
142
+ /** Documentation URL for this error class (snake_case on the wire). */
143
+ docs_url?: string;
144
+ }
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ // Wire types for the Pane relay HTTP + WebSocket API.
2
+ //
3
+ // These mirror the relay's public response shapes (see the relay's
4
+ // src/types.ts, src/http/serialize.ts and src/http/routes/*). They are
5
+ // re-declared here rather than imported from @paneui/relay so that @paneui/core
6
+ // stays pure and framework-free — no Prisma, no Hono, no server deps.
7
+ export {};
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@paneui/core",
3
+ "version": "0.0.1",
4
+ "description": "Pane relay client: typed HTTP + WebSocket operations against a Pane relay. Framework-free.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "keywords": [
8
+ "pane",
9
+ "agent",
10
+ "websocket",
11
+ "relay",
12
+ "human-in-the-loop"
13
+ ],
14
+ "homepage": "https://github.com/aerolalit/paneui#readme",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/aerolalit/paneui.git",
18
+ "directory": "packages/core"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/aerolalit/paneui/issues"
22
+ },
23
+ "engines": {
24
+ "node": ">=20"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "main": "dist/index.js",
30
+ "types": "dist/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "default": "./dist/index.js"
35
+ }
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "LICENSE",
40
+ "README.md"
41
+ ],
42
+ "scripts": {
43
+ "build": "tsc",
44
+ "typecheck": "tsc --noEmit",
45
+ "test": "vitest run",
46
+ "test:unit": "vitest run"
47
+ },
48
+ "dependencies": {
49
+ "ws": "^8.20.1",
50
+ "zod": "^3.23.0"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^22.7.0",
54
+ "@types/ws": "^8.18.1",
55
+ "typescript": "^5.6.0",
56
+ "vitest": "^4.1.6"
57
+ }
58
+ }