@getorrery/client 0.0.0-stage → 0.2.2

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
@@ -1,3 +1,39 @@
1
- # Temporary Holding Version
1
+ # `@getorrery/client`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The TypeScript half of the client SDK: `AguiSession` (decode, `seq`, re-attach `since`,
4
+ control RPC) and `SurfaceStore`. **Pure data, no drawing** — exactly the same two things
5
+ [`../../core/crates/orrery-client`](../../core/crates/orrery-client) gives Rust, built on
6
+ stock `@ag-ui/client`'s `HttpAgent` over SSE.
7
+
8
+ Types come from [`@getorrery/protocol`](../../protocol), which is generated by
9
+ `cargo xtask typegen` and must not be hand-edited.
10
+
11
+ ```bash
12
+ pnpm -C harness/clients/sdk-ts test # vitest, over ../conformance
13
+ ```
14
+
15
+ It passes the shared fixtures in [`../conformance`](../conformance) — the same files
16
+ `orrery-client` runs in Rust, asserted the same way. If the two ever disagree, one of the
17
+ five clients is drawing something the others are not.
18
+
19
+ ## The session is stock `@ag-ui/client`
20
+
21
+ `AguiSession` wraps `HttpAgent` and hands it back unmodified through `httpAgent()`. That
22
+ *is* the "existing AG-UI clients work unmodified" proof: if this package had to patch the
23
+ agent, the claim would be false. What it adds is the half AG-UI has no vocabulary for —
24
+ `session.attach(since)`, `turn.cancel`, `intent`, `consent.answer`, `query` — over a small
25
+ `fetch` client against `POST /control`.
26
+
27
+ `run()` parses the SSE stream itself rather than going through the agent's subscriber API,
28
+ for one reason: our frames carry a `seq` the stock agent drops, and `seq` is the whole of
29
+ the resumption story.
30
+
31
+ ## A client never writes state
32
+
33
+ AG-UI's shared state is bidirectional by design; ours is not. `StateSnapshot` and
34
+ `StateDelta` flow outward only, and an edit a person makes arrives back as an `intent` the
35
+ kernel validates. Anyone porting a component that expects to write state directly needs to
36
+ know that.
37
+
38
+ **Status.** Implemented and `private` until there is something to publish.
39
+ Implementation plan: [`harness/docs/plans/08-protocol-transport.md`](../../docs/plans/08-protocol-transport.md).
package/dist/anim.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Animation definitions (decision 0043, plan 38).
3
+ *
4
+ * The host sends each one once, at attach, as the AG-UI `CUSTOM` event
5
+ * `orrery.ui.anim` at `seq` 0 (outside the session's order, which starts at 1).
6
+ * `SurfaceStore` keeps them by id; a drawer (`@getorrery/anim`) draws any of them.
7
+ * The type is generated from `orrery-proto`; this file adds the name and a
8
+ * parser that fails loudly, naming the field.
9
+ */
10
+ import type { AnimDef } from "@getorrery/protocol";
11
+ export type { AnimDef, AnimGlyphs, AnimPixel, AnimPixelState, AnimSize, AnimState, AnimTheme, } from "@getorrery/protocol";
12
+ /** The `Custom` name an animation definition rides under. */
13
+ export declare const ANIM = "orrery.ui.anim";
14
+ /** The `seq` of a frame outside the session's order: the definition, at attach. */
15
+ export declare const OUT_OF_ORDER = 0;
16
+ /**
17
+ * A definition from the wire.
18
+ *
19
+ * Throws an `Error` naming the field that is wrong and what it must match:
20
+ * every state has its frames in every theme, each as wide as `slots`, and the
21
+ * template names no slot it lacks. A drawer catches it and uses its glyphs.
22
+ */
23
+ export declare function parseAnim(value: unknown): AnimDef;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * `orrery-client/1` from TypeScript (plan 36 task 10, decision 0038).
3
+ *
4
+ * A client the host started reads `ORRERY_ENDPOINT` (`pipe:<name>` with the
5
+ * `tty` grant — a named pipe on Windows, a Unix socket elsewhere, decision
6
+ * 0066 §4 — `stdio` without) and `ORRERY_TOKEN`, attaches, and then gets
7
+ * `client/frames` and sends `control/*`. The channel itself is `@getorrery/ext`'s
8
+ * `connect` — one implementation of the wire for every Node client — and this
9
+ * file gives it the AG-UI [`Frame`] type.
10
+ */
11
+ import { CLIENT_PROTOCOL, type Client, type ConnectOptions } from "@getorrery/ext";
12
+ import { type Frame } from "./events.js";
13
+ export { CLIENT_PROTOCOL };
14
+ /** The channel to the host, with frames typed as this package's [`Frame`]. */
15
+ export interface ClientChannel extends Omit<Client, "frames"> {
16
+ frames(): AsyncIterable<Frame>;
17
+ }
18
+ /**
19
+ * The socket a `pipe:<name>` endpoint names: `\\.\pipe\<name>` on Windows,
20
+ * the socket's path elsewhere; `undefined` for any other endpoint.
21
+ */
22
+ export declare function pipePath(endpoint: string, platform?: string): string | undefined;
23
+ /** Attach over `ORRERY_ENDPOINT` + `ORRERY_TOKEN`, or the `input` / `output` given. */
24
+ export declare function connectClient(options?: ConnectOptions): Promise<ClientChannel>;
@@ -0,0 +1,135 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // src/cloud-events.ts
8
+ var SseParser = class {
9
+ /** The last `id:` seen: what `Last-Event-ID` sends back. */
10
+ lastEventId;
11
+ buffer = "";
12
+ event = "";
13
+ data = [];
14
+ id;
15
+ /** Feed text; get the events it completed. */
16
+ push(text) {
17
+ this.buffer += text;
18
+ const out = [];
19
+ let start = 0;
20
+ for (let i = 0; i < this.buffer.length; i += 1) {
21
+ const c = this.buffer[i];
22
+ if (c !== "\n" && c !== "\r") continue;
23
+ if (c === "\r" && i === this.buffer.length - 1) break;
24
+ this.line(this.buffer.slice(start, i), out);
25
+ if (c === "\r" && this.buffer[i + 1] === "\n") i += 1;
26
+ start = i + 1;
27
+ }
28
+ this.buffer = this.buffer.slice(start);
29
+ return out;
30
+ }
31
+ line(line, out) {
32
+ if (line === "") {
33
+ if (this.data.length > 0 || this.event) {
34
+ out.push({ ...this.id !== void 0 ? { id: this.id } : {}, event: this.event || "message", data: this.data.join("\n") });
35
+ }
36
+ this.event = "";
37
+ this.data = [];
38
+ this.id = void 0;
39
+ return;
40
+ }
41
+ if (line.startsWith(":")) return;
42
+ const at = line.indexOf(":");
43
+ const field = at < 0 ? line : line.slice(0, at);
44
+ let value = at < 0 ? "" : line.slice(at + 1);
45
+ if (value.startsWith(" ")) value = value.slice(1);
46
+ switch (field) {
47
+ case "event":
48
+ this.event = value;
49
+ break;
50
+ case "data":
51
+ this.data.push(value);
52
+ break;
53
+ case "id":
54
+ if (!value.includes("\0")) {
55
+ this.id = value;
56
+ this.lastEventId = value;
57
+ }
58
+ break;
59
+ default:
60
+ break;
61
+ }
62
+ }
63
+ };
64
+ function cloudEventOf(e) {
65
+ let v = {};
66
+ try {
67
+ const parsed = e.data ? JSON.parse(e.data) : {};
68
+ if (parsed && typeof parsed === "object") v = parsed;
69
+ } catch {
70
+ if (e.event !== "reset") return null;
71
+ }
72
+ const str = (k) => typeof v[k] === "string" ? v[k] : void 0;
73
+ switch (e.event) {
74
+ case "reset":
75
+ return { kind: "reset" };
76
+ case "machine":
77
+ return str("machine_id") ? { ...v, kind: "machine" } : null;
78
+ case "host":
79
+ return str("host_id") ? { ...v, kind: "host" } : null;
80
+ case "session":
81
+ return str("session_id") && str("machine_id") ? { ...v, kind: "session" } : null;
82
+ case "lease":
83
+ return str("session_id") && typeof v["epoch"] === "number" ? { ...v, kind: "lease" } : null;
84
+ case "config":
85
+ return Array.isArray(v["keys"]) ? { ...v, kind: "config" } : null;
86
+ default:
87
+ return null;
88
+ }
89
+ }
90
+ function backoffMs(attempt, firstMs = 1e3, maxMs = 3e4) {
91
+ return Math.min(maxMs, firstMs * 2 ** Math.max(0, Math.min(attempt, 30)));
92
+ }
93
+ var CloudEventsError = class extends Error {
94
+ constructor(message, status) {
95
+ super(message);
96
+ this.status = status;
97
+ this.name = "CloudEventsError";
98
+ }
99
+ };
100
+ async function* cloudEvents(options) {
101
+ const f = options.fetch ?? fetch;
102
+ const response = await f(options.url, {
103
+ headers: {
104
+ accept: "text/event-stream",
105
+ Authorization: `Bearer ${options.token}`,
106
+ ...options.lastEventId ? { "Last-Event-ID": options.lastEventId } : {}
107
+ },
108
+ ...options.signal ? { signal: options.signal } : {}
109
+ });
110
+ if (!response.ok || !response.body) {
111
+ const why = await response.text().catch(() => "");
112
+ throw new CloudEventsError(`GET /events: ${response.status}${why ? ` ${why}` : ""}`, response.status);
113
+ }
114
+ const reader = response.body.getReader();
115
+ const decoder = new TextDecoder();
116
+ const parser = new SseParser();
117
+ try {
118
+ for (; ; ) {
119
+ const { done, value } = await reader.read();
120
+ if (done) return;
121
+ for (const e of parser.push(decoder.decode(value, { stream: true }))) yield e;
122
+ }
123
+ } finally {
124
+ reader.releaseLock();
125
+ }
126
+ }
127
+
128
+ export {
129
+ __export,
130
+ SseParser,
131
+ cloudEventOf,
132
+ backoffMs,
133
+ CloudEventsError,
134
+ cloudEvents
135
+ };