@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 +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/dist/node.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AGUI_PROTOCOL_VERSION,
|
|
3
|
+
ANIM,
|
|
4
|
+
AguiSession,
|
|
5
|
+
CONSENT_REQUEST,
|
|
6
|
+
CONSENT_RESOLVED,
|
|
7
|
+
ControlError,
|
|
8
|
+
DETACHED_TURN,
|
|
9
|
+
FrameHub,
|
|
10
|
+
HttpSseTransport,
|
|
11
|
+
MAX_FILE_BYTES,
|
|
12
|
+
MAX_MESSAGE_BYTES,
|
|
13
|
+
MOVED,
|
|
14
|
+
NOTICES,
|
|
15
|
+
OUT_OF_ORDER,
|
|
16
|
+
PLACED,
|
|
17
|
+
SCENARIO_COUNT,
|
|
18
|
+
SESSION_CHANGES,
|
|
19
|
+
STATE_CHANGED,
|
|
20
|
+
SurfaceStore,
|
|
21
|
+
TURN_CANCELLED,
|
|
22
|
+
UNPLACED,
|
|
23
|
+
USER_MESSAGE,
|
|
24
|
+
WORKSPACE_COMMIT,
|
|
25
|
+
WORKSPACE_PUSH,
|
|
26
|
+
WORKSPACE_READ,
|
|
27
|
+
WORKSPACE_READ_CAP_BYTES,
|
|
28
|
+
WebSocketTransport,
|
|
29
|
+
afterOf,
|
|
30
|
+
checkpoints,
|
|
31
|
+
choosingLine,
|
|
32
|
+
filePart,
|
|
33
|
+
firstSeq,
|
|
34
|
+
formatAddress,
|
|
35
|
+
frameOf,
|
|
36
|
+
framesOf,
|
|
37
|
+
framesOf2,
|
|
38
|
+
keptTranscript,
|
|
39
|
+
movedOf,
|
|
40
|
+
parse,
|
|
41
|
+
parseAddress,
|
|
42
|
+
parseAnim,
|
|
43
|
+
parseEndpoint,
|
|
44
|
+
readKept,
|
|
45
|
+
replay,
|
|
46
|
+
resumeFrom,
|
|
47
|
+
sse,
|
|
48
|
+
thoughtMs,
|
|
49
|
+
workspaceCommit,
|
|
50
|
+
workspaceErrorCode,
|
|
51
|
+
workspacePush,
|
|
52
|
+
workspaceRead,
|
|
53
|
+
workspaceRequest
|
|
54
|
+
} from "./chunk-RQUTHT7N.js";
|
|
55
|
+
import {
|
|
56
|
+
CloudEventsError,
|
|
57
|
+
SseParser,
|
|
58
|
+
__export,
|
|
59
|
+
backoffMs,
|
|
60
|
+
cloudEventOf,
|
|
61
|
+
cloudEvents
|
|
62
|
+
} from "./chunk-P3UGNLD6.js";
|
|
63
|
+
|
|
64
|
+
// src/channel.ts
|
|
65
|
+
import net from "node:net";
|
|
66
|
+
import { CLIENT_PROTOCOL, connect } from "@getorrery/ext";
|
|
67
|
+
function pipePath(endpoint, platform = process.platform) {
|
|
68
|
+
if (!endpoint.startsWith("pipe:")) return void 0;
|
|
69
|
+
const name = endpoint.slice("pipe:".length);
|
|
70
|
+
return platform === "win32" && !name.startsWith("\\\\") ? `\\\\.\\pipe\\${name}` : name;
|
|
71
|
+
}
|
|
72
|
+
async function connectClient(options = {}) {
|
|
73
|
+
const endpoint = (options.env ?? process.env)["ORRERY_ENDPOINT"] ?? "";
|
|
74
|
+
const path = options.input && options.output ? void 0 : pipePath(endpoint);
|
|
75
|
+
if (path !== void 0) {
|
|
76
|
+
const socket = net.createConnection(path);
|
|
77
|
+
await new Promise((resolve, reject) => {
|
|
78
|
+
socket.once("connect", resolve);
|
|
79
|
+
socket.once("error", (e) => reject(new Error(`cannot reach the host at ${endpoint} (${e.message})`)));
|
|
80
|
+
});
|
|
81
|
+
options = { ...options, input: socket, output: socket };
|
|
82
|
+
}
|
|
83
|
+
const channel = await connect(options);
|
|
84
|
+
const raw = channel.frames.bind(channel);
|
|
85
|
+
channel.frames = async function* () {
|
|
86
|
+
for await (const f of raw()) {
|
|
87
|
+
const frame = frameOf(f);
|
|
88
|
+
if (frame) yield frame;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
return channel;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/conformance-node.ts
|
|
95
|
+
var conformance_node_exports = {};
|
|
96
|
+
__export(conformance_node_exports, {
|
|
97
|
+
SCENARIO_COUNT: () => SCENARIO_COUNT,
|
|
98
|
+
checkpoints: () => checkpoints,
|
|
99
|
+
fixturesDir: () => fixturesDir,
|
|
100
|
+
framesOf: () => framesOf2,
|
|
101
|
+
load: () => load,
|
|
102
|
+
loadAll: () => loadAll,
|
|
103
|
+
parse: () => parse,
|
|
104
|
+
replay: () => replay
|
|
105
|
+
});
|
|
106
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
107
|
+
import { basename, dirname, join } from "node:path";
|
|
108
|
+
import { fileURLToPath } from "node:url";
|
|
109
|
+
function fixturesDir() {
|
|
110
|
+
return join(dirname(fileURLToPath(import.meta.url)), "../../conformance");
|
|
111
|
+
}
|
|
112
|
+
function load(path) {
|
|
113
|
+
return parse(readFileSync(path, "utf8"), basename(path).replace(/\.jsonl$/, ""), path);
|
|
114
|
+
}
|
|
115
|
+
function loadAll(dir = fixturesDir()) {
|
|
116
|
+
return readdirSync(dir).filter((f) => f.endsWith(".jsonl")).sort().map((f) => load(join(dir, f)));
|
|
117
|
+
}
|
|
118
|
+
export {
|
|
119
|
+
AGUI_PROTOCOL_VERSION,
|
|
120
|
+
ANIM,
|
|
121
|
+
AguiSession,
|
|
122
|
+
CLIENT_PROTOCOL,
|
|
123
|
+
CONSENT_REQUEST,
|
|
124
|
+
CONSENT_RESOLVED,
|
|
125
|
+
CloudEventsError,
|
|
126
|
+
ControlError,
|
|
127
|
+
DETACHED_TURN,
|
|
128
|
+
FrameHub,
|
|
129
|
+
HttpSseTransport,
|
|
130
|
+
MAX_FILE_BYTES,
|
|
131
|
+
MAX_MESSAGE_BYTES,
|
|
132
|
+
MOVED,
|
|
133
|
+
NOTICES,
|
|
134
|
+
OUT_OF_ORDER,
|
|
135
|
+
PLACED,
|
|
136
|
+
SESSION_CHANGES,
|
|
137
|
+
STATE_CHANGED,
|
|
138
|
+
SseParser,
|
|
139
|
+
SurfaceStore,
|
|
140
|
+
TURN_CANCELLED,
|
|
141
|
+
UNPLACED,
|
|
142
|
+
USER_MESSAGE,
|
|
143
|
+
WORKSPACE_COMMIT,
|
|
144
|
+
WORKSPACE_PUSH,
|
|
145
|
+
WORKSPACE_READ,
|
|
146
|
+
WORKSPACE_READ_CAP_BYTES,
|
|
147
|
+
WebSocketTransport,
|
|
148
|
+
afterOf,
|
|
149
|
+
backoffMs,
|
|
150
|
+
choosingLine,
|
|
151
|
+
cloudEventOf,
|
|
152
|
+
cloudEvents,
|
|
153
|
+
conformance_node_exports as conformance,
|
|
154
|
+
connectClient,
|
|
155
|
+
filePart,
|
|
156
|
+
firstSeq,
|
|
157
|
+
formatAddress,
|
|
158
|
+
frameOf,
|
|
159
|
+
framesOf,
|
|
160
|
+
keptTranscript,
|
|
161
|
+
movedOf,
|
|
162
|
+
parseAddress,
|
|
163
|
+
parseAnim,
|
|
164
|
+
parseEndpoint,
|
|
165
|
+
pipePath,
|
|
166
|
+
readKept,
|
|
167
|
+
resumeFrom,
|
|
168
|
+
sse,
|
|
169
|
+
thoughtMs,
|
|
170
|
+
workspaceCommit,
|
|
171
|
+
workspaceErrorCode,
|
|
172
|
+
workspacePush,
|
|
173
|
+
workspaceRead,
|
|
174
|
+
workspaceRequest
|
|
175
|
+
};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `AguiSession`, the TypeScript half.
|
|
3
|
+
*
|
|
4
|
+
* The subscribe half is stock `@ag-ui/client`: `HttpAgent` runs the agent and
|
|
5
|
+
* emits AG-UI events, exactly as it would against any AG-UI server. That *is*
|
|
6
|
+
* the "existing AG-UI clients work unmodified" proof — if this file had to patch
|
|
7
|
+
* the agent, the claim would be false.
|
|
8
|
+
*
|
|
9
|
+
* The control half is a small `fetch` client, because AG-UI's input path is a
|
|
10
|
+
* run invocation and has no vocabulary for `session.attach(since)`,
|
|
11
|
+
* `turn.cancel`, `intent` or `query`.
|
|
12
|
+
*/
|
|
13
|
+
import { HttpAgent } from "@ag-ui/client";
|
|
14
|
+
import type { ContentPart } from "@getorrery/protocol";
|
|
15
|
+
import type { Frame } from "./events.js";
|
|
16
|
+
import { type Transport } from "./transport.js";
|
|
17
|
+
import { type WebSocketCtor } from "./transport-ws.js";
|
|
18
|
+
export type { Request } from "./transport.js";
|
|
19
|
+
export type { ContentPart } from "@getorrery/protocol";
|
|
20
|
+
/**
|
|
21
|
+
* A file as an AG-UI content part (harness decision 0050): an `image` part
|
|
22
|
+
* for `image/*`, a `document` otherwise, its bytes inline as base64 and its
|
|
23
|
+
* name in `metadata.filename`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function filePart(name: string, mimeType: string, base64: string): ContentPart;
|
|
26
|
+
/** The most one file may be, decoded (the kernel's `attach::MAX_FILE_BYTES`). */
|
|
27
|
+
export declare const MAX_FILE_BYTES: number;
|
|
28
|
+
/** The most one message's files may be together, decoded. */
|
|
29
|
+
export declare const MAX_MESSAGE_BYTES: number;
|
|
30
|
+
/** One of the four ways to reach a kernel. */
|
|
31
|
+
export type Endpoint = {
|
|
32
|
+
kind: "inproc";
|
|
33
|
+
} | {
|
|
34
|
+
kind: "pipe";
|
|
35
|
+
name: string;
|
|
36
|
+
} | {
|
|
37
|
+
kind: "http";
|
|
38
|
+
url: string;
|
|
39
|
+
token?: string;
|
|
40
|
+
} | {
|
|
41
|
+
kind: "ws";
|
|
42
|
+
url: string;
|
|
43
|
+
token?: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Parse an endpoint string.
|
|
47
|
+
*
|
|
48
|
+
* The token rides the authority rather than a query parameter because a query
|
|
49
|
+
* string ends up in logs, in shell history and in `ps` output, and a bearer
|
|
50
|
+
* token is the whole of phase 1's auth story.
|
|
51
|
+
*/
|
|
52
|
+
export declare function parseEndpoint(raw: string): Endpoint;
|
|
53
|
+
/** The CUSTOM frame a watched key's change arrives as (decision 0016). */
|
|
54
|
+
export declare const STATE_CHANGED = "orrery.state.changed";
|
|
55
|
+
/**
|
|
56
|
+
* The CUSTOM frame a node closes its clients with when their session moved
|
|
57
|
+
* to another machine (platform decision 0014 §8). Re-attach to `session` on
|
|
58
|
+
* `machine`; the epoch is the new one (decision 0013 §6).
|
|
59
|
+
*/
|
|
60
|
+
export declare const MOVED = "orrery.moved";
|
|
61
|
+
/** What an `orrery.moved` frame says. */
|
|
62
|
+
export interface Moved {
|
|
63
|
+
session: string;
|
|
64
|
+
/** The machine it moved to: its id on the account. */
|
|
65
|
+
machine: string;
|
|
66
|
+
/** Its name, for a line. */
|
|
67
|
+
machine_name?: string;
|
|
68
|
+
epoch?: number;
|
|
69
|
+
/** The client that asked. */
|
|
70
|
+
by?: string;
|
|
71
|
+
/** Unix seconds. */
|
|
72
|
+
at?: number;
|
|
73
|
+
}
|
|
74
|
+
/** The move a frame announces, or `null` when it is not `orrery.moved`. */
|
|
75
|
+
export declare function movedOf(frame: Frame): Moved | null;
|
|
76
|
+
/** `session.handoff`'s options: the untracked files, and a running turn. */
|
|
77
|
+
export interface HandoffOptions {
|
|
78
|
+
untracked?: "include" | "skip";
|
|
79
|
+
turn?: "wait" | "cancel";
|
|
80
|
+
}
|
|
81
|
+
/** Options for [`AguiSession.connect`] beyond the endpoint. */
|
|
82
|
+
export interface SessionConnectOptions {
|
|
83
|
+
/** The `WebSocket` implementation for a `ws://` endpoint (the global one when absent). */
|
|
84
|
+
WebSocket?: WebSocketCtor;
|
|
85
|
+
/** Bearer token for a `ws://` endpoint with none in its authority (sent in the Hello). */
|
|
86
|
+
token?: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A connected session, over any [`Transport`].
|
|
90
|
+
*
|
|
91
|
+
* `AguiSession.connect("http://…")` is the HTTP + SSE transport it always was;
|
|
92
|
+
* `ws://…` is the WebSocket transport; [`AguiSession.over`] takes any other.
|
|
93
|
+
* Drawing code holds an `AguiSession` and never learns which.
|
|
94
|
+
*/
|
|
95
|
+
export declare class AguiSession {
|
|
96
|
+
readonly transport: Transport;
|
|
97
|
+
private session;
|
|
98
|
+
private agent;
|
|
99
|
+
private lastSeq;
|
|
100
|
+
private declared;
|
|
101
|
+
private machine;
|
|
102
|
+
private ownConn;
|
|
103
|
+
/** Watch id → callback, for `orrery.state.changed` frames (decision 0016). */
|
|
104
|
+
private readonly watchers;
|
|
105
|
+
private constructor();
|
|
106
|
+
/** Connect to an endpoint: `http(s)://` or `ws(s)://`. */
|
|
107
|
+
static connect(endpoint: Endpoint | string, options?: SessionConnectOptions): AguiSession;
|
|
108
|
+
/** A session over a transport this SDK did not build. */
|
|
109
|
+
static over(transport: Transport): AguiSession;
|
|
110
|
+
/** The stock `@ag-ui/client` agent, unmodified, for anyone who wants it. HTTP only. */
|
|
111
|
+
httpAgent(): HttpAgent;
|
|
112
|
+
/**
|
|
113
|
+
* This client's connection (harness decision 0058), from the last `events()`
|
|
114
|
+
* ack: a surface whose `requester.conn` is another is somebody else's
|
|
115
|
+
* question ([`choosingLine`]). `0` until then, and for a server too old to say.
|
|
116
|
+
*/
|
|
117
|
+
get conn(): number;
|
|
118
|
+
/** The last `seq` this session has seen. */
|
|
119
|
+
seq(): number | null;
|
|
120
|
+
/** The session attached to, as `[machine/]session`. */
|
|
121
|
+
address(): string | null;
|
|
122
|
+
/**
|
|
123
|
+
* Say what this client is and what it draws, so the host knows whether it
|
|
124
|
+
* can answer consent (decision 0041 §2). An HTTP client has no manifest;
|
|
125
|
+
* this is its `client/attach`, sent with `events()`. Without it the host
|
|
126
|
+
* counts the client as unable to answer.
|
|
127
|
+
*/
|
|
128
|
+
declare(client: string, draws: readonly string[]): void;
|
|
129
|
+
/**
|
|
130
|
+
* Attach to a session, `[machine/]session`, optionally replaying from where
|
|
131
|
+
* this client left off.
|
|
132
|
+
*
|
|
133
|
+
* Over HTTP this is the `session.attach` request it always was. Over a
|
|
134
|
+
* transport whose `Hello` names the session (the WebSocket), the address is
|
|
135
|
+
* remembered and travels in the next `events()`'s `Hello`, as on the pipe.
|
|
136
|
+
*/
|
|
137
|
+
attach(address: string, since?: number): Promise<void>;
|
|
138
|
+
/**
|
|
139
|
+
* Submit a turn. Resolves with the turn's id from the server's answer
|
|
140
|
+
* (the HTTP body, or the WebSocket `reply` value), and `null` when the
|
|
141
|
+
* answer carries none.
|
|
142
|
+
*
|
|
143
|
+
* `parts` are files, as AG-UI 1.0 content parts (harness decision 0050):
|
|
144
|
+
* the kernel puts each into the turn's context, or the turn says why not.
|
|
145
|
+
* [`filePart`] makes one from a file's bytes.
|
|
146
|
+
*/
|
|
147
|
+
submit(text: string, parts?: readonly ContentPart[]): Promise<string | null>;
|
|
148
|
+
/** Cancel a turn in flight. */
|
|
149
|
+
cancel(turn: string): Promise<void>;
|
|
150
|
+
/** Answer a consent prompt. */
|
|
151
|
+
answer(prompt: string, answer: string): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* Send what a surface produced.
|
|
154
|
+
*
|
|
155
|
+
* The whole of a client's write path. A client edit arrives here, as an intent
|
|
156
|
+
* the kernel validates, never as a state mutation.
|
|
157
|
+
*/
|
|
158
|
+
intent(surface: string, value: unknown): Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* An action on a node of the UI document: a list item chosen, a button
|
|
161
|
+
* pressed (decision 0002).
|
|
162
|
+
*/
|
|
163
|
+
uiIntent(node: string, action: string, value: unknown): Promise<void>;
|
|
164
|
+
/**
|
|
165
|
+
* The context API's `watch(ext, key, onChange)` (decision 0016), the same
|
|
166
|
+
* call an extension makes in any runtime: `onChange` gets the key's value
|
|
167
|
+
* now, then every change, picked out of the `events()` stream. Resolves to
|
|
168
|
+
* `stop()`. Rejects, naming the key, when it does not exist or is private.
|
|
169
|
+
* Works over HTTP and WebSocket: both answer requests, and changes arrive
|
|
170
|
+
* as `orrery.state.changed` frames.
|
|
171
|
+
*/
|
|
172
|
+
watch(ext: string, key: string, onChange: (value: unknown) => void): Promise<() => Promise<void>>;
|
|
173
|
+
/** Hand a frame's `orrery.state.changed` to the watch it names. */
|
|
174
|
+
private dispatch;
|
|
175
|
+
/**
|
|
176
|
+
* Move this session to another machine of the account (platform decision
|
|
177
|
+
* 0014): `to` is its name or id. The move runs as the session's `/handoff`
|
|
178
|
+
* flow, so its steps arrive as frames; the node then sends `orrery.moved`
|
|
179
|
+
* ([`movedOf`]). Needs the `handoff` client grant.
|
|
180
|
+
*/
|
|
181
|
+
handoff(to: string, options?: HandoffOptions): Promise<unknown>;
|
|
182
|
+
/** Run a command the host owns: `/model`, `/providers`, `/help`. */
|
|
183
|
+
command(name: string, args?: unknown): Promise<void>;
|
|
184
|
+
/**
|
|
185
|
+
* Every frame of the session, from now (or from `since`) on: the transport's
|
|
186
|
+
* `Hello`, then its frames.
|
|
187
|
+
*
|
|
188
|
+
* The passive half: a client that subscribes here sees every run — its own
|
|
189
|
+
* and anybody else's — and the screen the host declares, which the kernel
|
|
190
|
+
* re-sends on every subscription. `run` only ever sees the run it started.
|
|
191
|
+
*/
|
|
192
|
+
events(since?: number): AsyncGenerator<Frame>;
|
|
193
|
+
/** `events()` from where this session left off: what a renderer's `frames()` wants. */
|
|
194
|
+
frames(): AsyncIterable<Frame>;
|
|
195
|
+
/**
|
|
196
|
+
* Start a run and yield its frames. HTTP only.
|
|
197
|
+
*
|
|
198
|
+
* `POST /run` with a `RunAgentInput`, parsed as SSE. `@ag-ui/client` would
|
|
199
|
+
* give the same events with its own subscriber API; this form exists because
|
|
200
|
+
* ours carry a `seq` the stock agent drops, and `seq` is the whole of the
|
|
201
|
+
* resumption story.
|
|
202
|
+
*/
|
|
203
|
+
run(text: string): AsyncGenerator<Frame>;
|
|
204
|
+
/** Stop the stream. */
|
|
205
|
+
close(): Promise<void>;
|
|
206
|
+
private seen;
|
|
207
|
+
private http;
|
|
208
|
+
private requireSession;
|
|
209
|
+
private control;
|
|
210
|
+
}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SurfaceStore`, the TypeScript half.
|
|
3
|
+
*
|
|
4
|
+
* The same projection as `clients/sdk-rs`, asserted against the same fixtures.
|
|
5
|
+
* Text deltas become a markdown surface and tool calls become a tool stack
|
|
6
|
+
* **here**, so no Ink component and no ratatui widget reimplements that — and
|
|
7
|
+
* if these two files ever disagree, the conformance suite says so before a user
|
|
8
|
+
* does.
|
|
9
|
+
*
|
|
10
|
+
* The store is a projection, never an authority. AG-UI's shared state is
|
|
11
|
+
* bidirectional by design and ours is not: an edit a person makes travels back
|
|
12
|
+
* as an `intent` the kernel validates.
|
|
13
|
+
*/
|
|
14
|
+
import type { AnimDef, SessionChanges, Status, SurfaceKind, UiDoc } from "@getorrery/protocol";
|
|
15
|
+
import { type Frame } from "./events.js";
|
|
16
|
+
/** The `Custom` name live file changes ride under (platform plan 07). */
|
|
17
|
+
export declare const SESSION_CHANGES = "orrery.session.changes";
|
|
18
|
+
/**
|
|
19
|
+
* The `Custom` name the person's message rides under (harness decision 0053):
|
|
20
|
+
* `{ id, role: "user", content, run_id, attachments, steer }`, one per prompt
|
|
21
|
+
* or steer any client sent, in history and on resume too.
|
|
22
|
+
*/
|
|
23
|
+
export declare const USER_MESSAGE = "orrery.user.message";
|
|
24
|
+
/** The turn surfaces land in when replay picks up mid-turn. */
|
|
25
|
+
export declare const DETACHED_TURN = "(detached)";
|
|
26
|
+
/** A hole in the sequence. */
|
|
27
|
+
export interface Gap {
|
|
28
|
+
expected: number;
|
|
29
|
+
got: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Who a flow's question belongs to (harness decision 0058 §5): the connection
|
|
33
|
+
* that sent the `command`, and what that client calls itself.
|
|
34
|
+
*/
|
|
35
|
+
export interface Requester {
|
|
36
|
+
conn: number;
|
|
37
|
+
client?: string;
|
|
38
|
+
}
|
|
39
|
+
/** One surface, as a client holds it. */
|
|
40
|
+
export interface SurfaceView {
|
|
41
|
+
id: string;
|
|
42
|
+
status: Status | null;
|
|
43
|
+
kind: SurfaceKind;
|
|
44
|
+
/** Set on a flow's Question / Form: another connection's draws read-only. */
|
|
45
|
+
requester?: Requester;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The read-only line for a question another connection asked (decision 0058
|
|
49
|
+
* §5): `"<client> is choosing…"`, or `null` when it is this client's own (or
|
|
50
|
+
* nobody's). Takes a surface's `requester` or an overlay node's
|
|
51
|
+
* `props.requester` as it came off the wire.
|
|
52
|
+
*/
|
|
53
|
+
export declare function choosingLine(requester: unknown, ownConn: number): string | null;
|
|
54
|
+
/** How a consent prompt was settled. */
|
|
55
|
+
export interface Resolution {
|
|
56
|
+
answer: string;
|
|
57
|
+
by: string;
|
|
58
|
+
}
|
|
59
|
+
/** A consent prompt, and how it stands. */
|
|
60
|
+
export interface PromptView {
|
|
61
|
+
id: string;
|
|
62
|
+
reason: string;
|
|
63
|
+
deadline_ms: number;
|
|
64
|
+
resolved: Resolution | null;
|
|
65
|
+
}
|
|
66
|
+
/** What went wrong in a turn. */
|
|
67
|
+
export interface TurnError {
|
|
68
|
+
message: string;
|
|
69
|
+
code: string | null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* A turn's reasoning (decision 0005): drawn folded, as one line, until a
|
|
73
|
+
* person opens it. `ended_ms - started_ms` is the time spent thinking: the
|
|
74
|
+
* start moves forward past any gap between blocks, so a tool's time between
|
|
75
|
+
* two blocks is not counted.
|
|
76
|
+
*/
|
|
77
|
+
export interface ReasoningView {
|
|
78
|
+
text: string;
|
|
79
|
+
started_ms: number | null;
|
|
80
|
+
ended_ms: number | null;
|
|
81
|
+
/** `false` while it streams. */
|
|
82
|
+
complete: boolean;
|
|
83
|
+
}
|
|
84
|
+
/** How long it thought for, when the host said. */
|
|
85
|
+
export declare function thoughtMs(r: ReasoningView): number | null;
|
|
86
|
+
/** One turn: everything the kernel did about one input. */
|
|
87
|
+
export interface TurnView {
|
|
88
|
+
id: string;
|
|
89
|
+
settled: boolean;
|
|
90
|
+
cancelled: boolean;
|
|
91
|
+
error: TurnError | null;
|
|
92
|
+
/** In insertion order, not sorted. */
|
|
93
|
+
surfaces: SurfaceView[];
|
|
94
|
+
prompts: PromptView[];
|
|
95
|
+
/** What the model thought before answering, when it said. */
|
|
96
|
+
reasoning?: ReasoningView;
|
|
97
|
+
}
|
|
98
|
+
/** The whole store, in the shape the conformance fixtures assert. */
|
|
99
|
+
export interface StoreState {
|
|
100
|
+
last_seq: number | null;
|
|
101
|
+
live: string | null;
|
|
102
|
+
gaps: Gap[];
|
|
103
|
+
turns: TurnView[];
|
|
104
|
+
/** The UI document the host declared (decision 0002), once it has. */
|
|
105
|
+
ui?: UiDoc;
|
|
106
|
+
/** The animation definitions the host sent, by id (decision 0043). */
|
|
107
|
+
anims?: Record<string, AnimDef>;
|
|
108
|
+
/** The latest live-changes set (platform plan 07): `files` replaced, `clean` dropped. */
|
|
109
|
+
changes?: SessionChanges;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* An extension's model call or agent, as its own segment (decision 0076 §3):
|
|
113
|
+
* opened by `SUBAGENT_STARTED`, closed by `SUBAGENT_FINISHED` / `_ERROR`. Its
|
|
114
|
+
* messages stay among the turn's surfaces; `surfaces` names them, so a drawing
|
|
115
|
+
* client groups them under `<ext> · <what>`.
|
|
116
|
+
*/
|
|
117
|
+
export interface SegmentView {
|
|
118
|
+
id: string;
|
|
119
|
+
/** The extension: `git` of `git.model`. */
|
|
120
|
+
ext: string;
|
|
121
|
+
/** `model` or `agent`: the rest of the name. */
|
|
122
|
+
what: string;
|
|
123
|
+
/** The prompt, shortened by the host. */
|
|
124
|
+
description: string;
|
|
125
|
+
/** The tool call that caused it, when one did. */
|
|
126
|
+
parentToolCallId: string | null;
|
|
127
|
+
/** The turn it ran in. */
|
|
128
|
+
turn: string;
|
|
129
|
+
/** Its messages' and calls' surface ids, in order. */
|
|
130
|
+
surfaces: string[];
|
|
131
|
+
status: "running" | "done" | "failed";
|
|
132
|
+
/** What `SUBAGENT_FINISHED` reported: the segment's usage. */
|
|
133
|
+
usage: unknown;
|
|
134
|
+
error: TurnError | null;
|
|
135
|
+
}
|
|
136
|
+
/** One extension notice (decision 0083 §2), as the `notices` toast node holds it. */
|
|
137
|
+
export interface Notice {
|
|
138
|
+
id: string;
|
|
139
|
+
level: "info" | "warn" | "error";
|
|
140
|
+
text: string;
|
|
141
|
+
source: string;
|
|
142
|
+
}
|
|
143
|
+
/** One extension's status-line segment (decision 0083 §3). */
|
|
144
|
+
export interface ExtStatus {
|
|
145
|
+
ext: string;
|
|
146
|
+
key: string;
|
|
147
|
+
text: string;
|
|
148
|
+
}
|
|
149
|
+
/** A key an extension bound to a command (decision 0083 §4): `ctrl+g` → `git.status`. */
|
|
150
|
+
export interface KeyBinding {
|
|
151
|
+
key: string;
|
|
152
|
+
command: string;
|
|
153
|
+
}
|
|
154
|
+
/** The node the host's notices live in. */
|
|
155
|
+
export declare const NOTICES = "notices";
|
|
156
|
+
/** What changed, so a renderer can redraw a part rather than everything. */
|
|
157
|
+
export type StoreChange = {
|
|
158
|
+
kind: "turn-started";
|
|
159
|
+
turn: string;
|
|
160
|
+
} | {
|
|
161
|
+
kind: "turn-settled";
|
|
162
|
+
turn: string;
|
|
163
|
+
} | {
|
|
164
|
+
kind: "surface-changed";
|
|
165
|
+
turn: string;
|
|
166
|
+
surface: string;
|
|
167
|
+
} | {
|
|
168
|
+
kind: "surface-removed";
|
|
169
|
+
turn: string;
|
|
170
|
+
surface: string;
|
|
171
|
+
}
|
|
172
|
+
/** The turn's reasoning started, grew or ended. */
|
|
173
|
+
| {
|
|
174
|
+
kind: "reasoning-changed";
|
|
175
|
+
turn: string;
|
|
176
|
+
} | {
|
|
177
|
+
kind: "prompt-raised";
|
|
178
|
+
prompt: string;
|
|
179
|
+
} | {
|
|
180
|
+
kind: "prompt-resolved";
|
|
181
|
+
prompt: string;
|
|
182
|
+
}
|
|
183
|
+
/** The UI document changed; `path` is relative to `/ui`, `""` for all of it. */
|
|
184
|
+
| {
|
|
185
|
+
kind: "ui-changed";
|
|
186
|
+
path: string;
|
|
187
|
+
}
|
|
188
|
+
/** An animation definition arrived (or was replaced): draw by its id. */
|
|
189
|
+
| {
|
|
190
|
+
kind: "anim-changed";
|
|
191
|
+
id: string;
|
|
192
|
+
}
|
|
193
|
+
/** An extension segment opened, grew or closed. */
|
|
194
|
+
| {
|
|
195
|
+
kind: "segment-changed";
|
|
196
|
+
segment: string;
|
|
197
|
+
}
|
|
198
|
+
/** The session's live changes arrived: redraw the change list. */
|
|
199
|
+
| {
|
|
200
|
+
kind: "changes-updated";
|
|
201
|
+
}
|
|
202
|
+
/** A renderer's cue to re-attach with `since = <the last seq it saw>`. */
|
|
203
|
+
| {
|
|
204
|
+
kind: "gap-detected";
|
|
205
|
+
expected: number;
|
|
206
|
+
got: number;
|
|
207
|
+
}
|
|
208
|
+
/** An event this client has no handler for. Ignored, not rejected. */
|
|
209
|
+
| {
|
|
210
|
+
kind: "ignored";
|
|
211
|
+
what: string;
|
|
212
|
+
};
|
|
213
|
+
/** The projection itself. */
|
|
214
|
+
export declare class SurfaceStore {
|
|
215
|
+
private data;
|
|
216
|
+
/** Kept beside the state, not in it: the fixtures assert the surfaces alone. */
|
|
217
|
+
private segs;
|
|
218
|
+
/** The whole state, as the conformance fixtures spell it. */
|
|
219
|
+
state(): StoreState;
|
|
220
|
+
/** One turn by run id. */
|
|
221
|
+
turn(id: string): TurnView | undefined;
|
|
222
|
+
/** Every settled turn, oldest first. What scrollback shows. */
|
|
223
|
+
settled(): TurnView[];
|
|
224
|
+
/** The turn in flight. What the live region shows. */
|
|
225
|
+
live(): TurnView | undefined;
|
|
226
|
+
/** The UI document, as the host last declared it. */
|
|
227
|
+
ui(): UiDoc | undefined;
|
|
228
|
+
/** Every extension segment, oldest first. */
|
|
229
|
+
segments(): SegmentView[];
|
|
230
|
+
/** The segment surface `id` belongs to, if any. */
|
|
231
|
+
segmentOf(id: string): SegmentView | undefined;
|
|
232
|
+
/** The host's notices, newest first (decision 0083 §2). */
|
|
233
|
+
notices(): Notice[];
|
|
234
|
+
/** The footer's extension segments, in the order first set (decision 0083 §3). */
|
|
235
|
+
extStatus(): ExtStatus[];
|
|
236
|
+
/** The keys bound on the input (decision 0083 §4). */
|
|
237
|
+
keys(): KeyBinding[];
|
|
238
|
+
/** Every gap detected so far. */
|
|
239
|
+
gaps(): Gap[];
|
|
240
|
+
/**
|
|
241
|
+
* Apply one frame. Never rejects: an unknown event is reported as `ignored`
|
|
242
|
+
* and still consumes its sequence number.
|
|
243
|
+
*/
|
|
244
|
+
apply(frame: Frame): StoreChange[];
|
|
245
|
+
/**
|
|
246
|
+
* Account for a frame this build could not even parse.
|
|
247
|
+
*
|
|
248
|
+
* An event type from a newer kernel still occupies a sequence number, so
|
|
249
|
+
* skipping it silently would turn every forward-compatible addition into a
|
|
250
|
+
* phantom gap.
|
|
251
|
+
*/
|
|
252
|
+
applyUnknown(seq: number, what: string): StoreChange[];
|
|
253
|
+
private note;
|
|
254
|
+
private applyEvent;
|
|
255
|
+
/** Note that surface `id` is part of segment `seg`. */
|
|
256
|
+
private join;
|
|
257
|
+
/** The turn new surfaces land in. */
|
|
258
|
+
private target;
|
|
259
|
+
/**
|
|
260
|
+
* The turn holding surface `id`: the one new surfaces land in when it is
|
|
261
|
+
* there, else the newest that has it, else the one it would land in. A
|
|
262
|
+
* surface id names one block in the thread (decision 0025): a later publish
|
|
263
|
+
* of it changes that block where it was drawn, whichever turn.
|
|
264
|
+
*/
|
|
265
|
+
private holding;
|
|
266
|
+
private upsert;
|
|
267
|
+
/** Change the live turn's reasoning, opening it if this is the first. */
|
|
268
|
+
private reasoning;
|
|
269
|
+
private settle;
|
|
270
|
+
/** A snapshot's `ui` replaces the document whole. It touches no turn. */
|
|
271
|
+
private uiSnapshot;
|
|
272
|
+
/** One op under `/ui`, with the RFC 6902 rules. Session-wide. */
|
|
273
|
+
private uiPatch;
|
|
274
|
+
private snapshot;
|
|
275
|
+
private patch;
|
|
276
|
+
private custom;
|
|
277
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HTTP + SSE transport: what `orrery serve`'s HTTP listener speaks
|
|
3
|
+
* (`orrery-transport/src/listener/http.rs`).
|
|
4
|
+
*
|
|
5
|
+
* - The [`Hello`] becomes `GET /events?since=…&client=…&draws=…`, parsed as
|
|
6
|
+
* SSE. HTTP has no first frame, so the [`HelloAck`] is the response status:
|
|
7
|
+
* a 200 is an ack, anything else is the host's refusal, in its own words.
|
|
8
|
+
* - A [`Request`] is `POST /control`, and its JSON body is the answer.
|
|
9
|
+
*
|
|
10
|
+
* Browser-safe: `fetch`, `ReadableStream` and `TextDecoder` only.
|
|
11
|
+
*/
|
|
12
|
+
import { type Frame } from "./events.js";
|
|
13
|
+
import { type CloseListener, type FrameListener, type Hello, type HelloAck, type Request, type Transport, type Unsubscribe } from "./transport.js";
|
|
14
|
+
/** `http(s)://host:port`, plus an optional bearer token. */
|
|
15
|
+
export interface HttpTransportOptions {
|
|
16
|
+
url: string;
|
|
17
|
+
token?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class HttpSseTransport implements Transport {
|
|
20
|
+
readonly kind = "http";
|
|
21
|
+
readonly url: string;
|
|
22
|
+
private readonly token;
|
|
23
|
+
private hub;
|
|
24
|
+
private abort;
|
|
25
|
+
/** The subscription's number from `GET /events` (decision 0058), sent back on every `POST /control`. */
|
|
26
|
+
private conn;
|
|
27
|
+
constructor(options: HttpTransportOptions);
|
|
28
|
+
/** The headers every request carries. */
|
|
29
|
+
headers(): Record<string, string>;
|
|
30
|
+
connect(hello: Hello): Promise<HelloAck>;
|
|
31
|
+
subscribe(onFrame: FrameListener, onClose?: CloseListener): Unsubscribe;
|
|
32
|
+
send(request: Request): Promise<unknown>;
|
|
33
|
+
close(): Promise<void>;
|
|
34
|
+
private pump;
|
|
35
|
+
}
|
|
36
|
+
/** An SSE body, as frames: every `data: ` line of every event block. */
|
|
37
|
+
export declare function sse(body: ReadableStream<Uint8Array>): AsyncGenerator<Frame>;
|