@getorrery/client 0.0.0-stage → 0.2.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/README.md +38 -2
- package/dist/anim.d.ts +23 -0
- package/dist/channel.d.ts +24 -0
- package/dist/chunk-P3UGNLD6.js +135 -0
- package/dist/chunk-RQUTHT7N.js +1551 -0
- package/dist/cloud-events.d.ts +94 -0
- package/dist/cloud-events.js +14 -0
- package/dist/conformance-node.d.ts +11 -0
- package/dist/conformance.d.ts +58 -0
- package/dist/events.d.ts +173 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +112 -0
- package/dist/kept.d.ts +82 -0
- package/dist/node.d.ts +11 -0
- package/dist/node.js +175 -0
- package/dist/session.d.ts +210 -0
- package/dist/store.d.ts +277 -0
- package/dist/transport-http.d.ts +37 -0
- package/dist/transport-ws.d.ts +68 -0
- package/dist/transport.d.ts +138 -0
- package/dist/workspace.d.ts +66 -0
- package/package.json +56 -3
package/README.md
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@getorrery/client`
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
};
|