@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.
@@ -0,0 +1,1551 @@
1
+ import {
2
+ __export
3
+ } from "./chunk-P3UGNLD6.js";
4
+
5
+ // src/events.ts
6
+ var CONSENT_REQUEST = "orrery.consent.request";
7
+ var CONSENT_RESOLVED = "orrery.consent.resolved";
8
+ var TURN_CANCELLED = "orrery.turn.cancelled";
9
+ var PLACED = "orrery.placed";
10
+ var UNPLACED = "orrery.unplaced";
11
+ function frameOf(raw) {
12
+ if (typeof raw !== "object" || raw === null) return null;
13
+ const { metadata, ...event } = raw;
14
+ const out = { ...metadata?.orrery ?? {}, ...event };
15
+ return typeof out["seq"] === "number" ? out : null;
16
+ }
17
+ function firstSeq(frame) {
18
+ return frame.merged_from ?? frame.seq;
19
+ }
20
+
21
+ // src/anim.ts
22
+ var ANIM = "orrery.ui.anim";
23
+ var OUT_OF_ORDER = 0;
24
+ function parseAnim(value) {
25
+ const def = value;
26
+ if (def === null || typeof def !== "object") throw new Error("anim: not an object");
27
+ const need = (field, ok) => {
28
+ if (!ok) throw new Error(`anim \`${String(def.id ?? "?")}\`: \`${field}\` is missing or the wrong type`);
29
+ };
30
+ need("id", typeof def.id === "string" && def.id !== "");
31
+ need("viewBox", Array.isArray(def.viewBox) && def.viewBox.length === 4);
32
+ need("template", typeof def.template === "string");
33
+ need("slots", Array.isArray(def.slots));
34
+ need("themes", typeof def.themes === "object" && def.themes !== null);
35
+ need("states", typeof def.states === "object" && def.states !== null);
36
+ const d = def;
37
+ const id = d.id;
38
+ if (Object.keys(d.themes).length === 0) {
39
+ throw new Error(`anim \`${id}\`: \`themes\` is empty; give it at least \`graphite\``);
40
+ }
41
+ for (const [state, spec] of Object.entries(d.states)) {
42
+ for (const [theme, t] of Object.entries(d.themes)) {
43
+ const frames = t.frames?.[state];
44
+ if (!frames) {
45
+ throw new Error(
46
+ `anim \`${id}\`: \`themes.${theme}.frames.${state}\` is missing; \`states.${state}\` needs its frames in every theme`
47
+ );
48
+ }
49
+ if (frames.length !== spec.frames) {
50
+ throw new Error(
51
+ `anim \`${id}\`: \`themes.${theme}.frames.${state}\` has ${frames.length} frames but \`states.${state}.frames\` says ${spec.frames}`
52
+ );
53
+ }
54
+ const at = frames.findIndex((f) => f.length !== d.slots.length);
55
+ if (at >= 0) {
56
+ throw new Error(
57
+ `anim \`${id}\`: \`themes.${theme}.frames.${state}[${at}]\` has ${frames[at].length} values but \`slots\` has ${d.slots.length}`
58
+ );
59
+ }
60
+ }
61
+ if (spec.loopFrom != null && spec.loopFrom >= spec.frames) {
62
+ throw new Error(`anim \`${id}\`: \`states.${state}.loopFrom\` is past its last frame`);
63
+ }
64
+ }
65
+ for (const m of d.template.matchAll(/\{\{([^}]*)\}\}/g)) {
66
+ const name = m[1];
67
+ const known = d.slots.includes(name) || Object.values(d.themes).every((t) => name in (t.statics ?? {})) || (d.sizes?.length ?? 0) > 0 && d.sizes.every((s) => name in s.statics);
68
+ if (!known) {
69
+ throw new Error(
70
+ `anim \`${id}\`: \`template\` names \`{{${name}}}\`, which is in no \`slots\`, theme \`statics\` or size \`statics\``
71
+ );
72
+ }
73
+ }
74
+ return d;
75
+ }
76
+
77
+ // src/store.ts
78
+ import { EventType } from "@ag-ui/core";
79
+ var SESSION_CHANGES = "orrery.session.changes";
80
+ var USER_MESSAGE = "orrery.user.message";
81
+ var DETACHED_TURN = "(detached)";
82
+ var BLOCK_GAP = "\n\n";
83
+ function choosingLine(requester, ownConn) {
84
+ const r = requester;
85
+ if (!r || typeof r.conn !== "number" || r.conn === ownConn) return null;
86
+ return `${typeof r.client === "string" && r.client ? r.client : "another client"} is choosing\u2026`;
87
+ }
88
+ function thoughtMs(r) {
89
+ if (r.started_ms === null || r.ended_ms === null) return null;
90
+ return Math.max(0, r.ended_ms - r.started_ms);
91
+ }
92
+ var NOTICES = "notices";
93
+ function emptyState() {
94
+ return { last_seq: null, live: null, gaps: [], turns: [] };
95
+ }
96
+ function newTurn(id) {
97
+ return {
98
+ id,
99
+ settled: false,
100
+ cancelled: false,
101
+ error: null,
102
+ surfaces: [],
103
+ prompts: []
104
+ };
105
+ }
106
+ function unescape(token) {
107
+ return token.replace(/~1/g, "/").replace(/~0/g, "~");
108
+ }
109
+ function splitPointer(path) {
110
+ if (!path.startsWith("/surfaces/")) return null;
111
+ const rest = path.slice("/surfaces/".length);
112
+ const parts = rest.split("/");
113
+ const id = unescape(parts[0] ?? "");
114
+ if (!id) return null;
115
+ return { id, rest: parts.slice(1).map(unescape) };
116
+ }
117
+ function outcomeStatus(outcome) {
118
+ const t = outcome?.t;
119
+ if (t === "ok" || t === "truncated") return "done";
120
+ if (t === "cancelled") return "cancelled";
121
+ if (t === void 0) return "done";
122
+ return "failed";
123
+ }
124
+ function outcomeSurface(outcome) {
125
+ const o = outcome;
126
+ return o?.surface;
127
+ }
128
+ var SurfaceStore = class {
129
+ data = emptyState();
130
+ /** Kept beside the state, not in it: the fixtures assert the surfaces alone. */
131
+ segs = /* @__PURE__ */ new Map();
132
+ /** The whole state, as the conformance fixtures spell it. */
133
+ state() {
134
+ return this.data;
135
+ }
136
+ /** One turn by run id. */
137
+ turn(id) {
138
+ return this.data.turns.find((t) => t.id === id);
139
+ }
140
+ /** Every settled turn, oldest first. What scrollback shows. */
141
+ settled() {
142
+ return this.data.turns.filter((t) => t.settled);
143
+ }
144
+ /** The turn in flight. What the live region shows. */
145
+ live() {
146
+ const id = this.data.live;
147
+ return id === null ? void 0 : this.turn(id);
148
+ }
149
+ /** The UI document, as the host last declared it. */
150
+ ui() {
151
+ return this.data.ui;
152
+ }
153
+ /** Every extension segment, oldest first. */
154
+ segments() {
155
+ return [...this.segs.values()];
156
+ }
157
+ /** The segment surface `id` belongs to, if any. */
158
+ segmentOf(id) {
159
+ for (const seg of this.segs.values()) if (seg.surfaces.includes(id)) return seg;
160
+ return void 0;
161
+ }
162
+ /** The host's notices, newest first (decision 0083 §2). */
163
+ notices() {
164
+ const n = this.data.ui?.nodes[NOTICES];
165
+ return Array.isArray(n?.props?.toasts) ? n.props.toasts : [];
166
+ }
167
+ /** The footer's extension segments, in the order first set (decision 0083 §3). */
168
+ extStatus() {
169
+ const f = Object.values(this.data.ui?.nodes ?? {}).find((n) => n.type === "footer");
170
+ return Array.isArray(f?.ext_status) ? f.ext_status : [];
171
+ }
172
+ /** The keys bound on the input (decision 0083 §4). */
173
+ keys() {
174
+ const i = Object.values(this.data.ui?.nodes ?? {}).find((n) => n.type === "input");
175
+ return Array.isArray(i?.keys) ? i.keys : [];
176
+ }
177
+ /** Every gap detected so far. */
178
+ gaps() {
179
+ return this.data.gaps;
180
+ }
181
+ /**
182
+ * Apply one frame. Never rejects: an unknown event is reported as `ignored`
183
+ * and still consumes its sequence number.
184
+ */
185
+ apply(frame) {
186
+ const changes = frame.seq === OUT_OF_ORDER ? [] : this.note(firstSeq(frame), frame.seq);
187
+ return changes.concat(this.applyEvent(frame));
188
+ }
189
+ /**
190
+ * Account for a frame this build could not even parse.
191
+ *
192
+ * An event type from a newer kernel still occupies a sequence number, so
193
+ * skipping it silently would turn every forward-compatible addition into a
194
+ * phantom gap.
195
+ */
196
+ applyUnknown(seq, what) {
197
+ const changes = this.note(seq, seq);
198
+ changes.push({ kind: "ignored", what });
199
+ return changes;
200
+ }
201
+ note(first, seq) {
202
+ const changes = [];
203
+ const prev = this.data.last_seq;
204
+ if (prev !== null && first !== prev + 1) {
205
+ const gap = { expected: prev + 1, got: first };
206
+ this.data.gaps.push(gap);
207
+ changes.push({ kind: "gap-detected", ...gap });
208
+ }
209
+ this.data.last_seq = seq;
210
+ return changes;
211
+ }
212
+ applyEvent(event) {
213
+ switch (event.type) {
214
+ case EventType.RUN_STARTED: {
215
+ this.data.turns.push(newTurn(event.runId));
216
+ this.data.live = event.runId;
217
+ return [{ kind: "turn-started", turn: event.runId }];
218
+ }
219
+ case EventType.RUN_FINISHED:
220
+ return this.settle(null);
221
+ case EventType.RUN_ERROR:
222
+ return this.settle({
223
+ message: event.message,
224
+ code: event.code ?? null
225
+ });
226
+ case EventType.TEXT_MESSAGE_START:
227
+ this.join(event.subagentRunId, event.messageId);
228
+ return this.upsert(event.messageId, (s) => {
229
+ s.status = "running";
230
+ s.kind = { t: "markdown", value: "", complete: false };
231
+ });
232
+ case EventType.TEXT_MESSAGE_CONTENT:
233
+ return this.upsert(event.messageId, (s) => {
234
+ if (s.kind.t === "markdown") {
235
+ s.kind.value += event.delta;
236
+ } else {
237
+ s.kind = { t: "markdown", value: event.delta, complete: false };
238
+ s.status = "running";
239
+ }
240
+ });
241
+ case EventType.TEXT_MESSAGE_END:
242
+ return this.upsert(event.messageId, (s) => {
243
+ if (s.kind.t === "markdown") s.kind.complete = true;
244
+ s.status = "done";
245
+ });
246
+ case EventType.TOOL_CALL_START:
247
+ this.join(event.subagentRunId, event.toolCallId);
248
+ return this.upsert(event.toolCallId, (s) => {
249
+ s.status = "running";
250
+ s.kind = {
251
+ t: "stack",
252
+ dir: "column",
253
+ title: event.toolCallName,
254
+ collapsed: false,
255
+ children: [{ kind: { t: "text", value: "", style: "code" } }]
256
+ };
257
+ });
258
+ case EventType.TOOL_CALL_ARGS:
259
+ return this.upsert(event.toolCallId, (s) => {
260
+ if (s.kind.t !== "stack") return;
261
+ const first = s.kind.children?.[0];
262
+ if (first && first.kind.t === "text") first.kind.value += event.delta;
263
+ });
264
+ // The arguments are complete; the call is not. Nothing to draw yet.
265
+ case EventType.TOOL_CALL_END:
266
+ return [{ kind: "ignored", what: `TOOL_CALL_END:${event.toolCallId}` }];
267
+ case EventType.TOOL_CALL_RESULT: {
268
+ const status = outcomeStatus(event.outcome);
269
+ const result = outcomeSurface(event.outcome) ?? {
270
+ kind: { t: "text", value: event.content }
271
+ };
272
+ if (result.id !== void 0 && result.id !== null) {
273
+ const turn = this.holding(result.id);
274
+ turn.surfaces = turn.surfaces.filter((s) => s.id !== result.id);
275
+ }
276
+ return this.upsert(event.toolCallId, (s) => {
277
+ s.status = status;
278
+ if (s.kind.t === "stack") {
279
+ s.kind.children = [...s.kind.children ?? [], result];
280
+ }
281
+ });
282
+ }
283
+ case EventType.REASONING_START:
284
+ return this.reasoning((r) => {
285
+ const at = event.timestamp ?? null;
286
+ if (at !== null && r.started_ms !== null && r.ended_ms !== null) {
287
+ r.started_ms = Math.max(0, at - Math.max(0, r.ended_ms - r.started_ms));
288
+ } else if (r.started_ms === null) {
289
+ r.started_ms = at;
290
+ }
291
+ if (r.text !== "" && !r.text.endsWith(BLOCK_GAP)) r.text += BLOCK_GAP;
292
+ r.complete = false;
293
+ });
294
+ case EventType.REASONING_MESSAGE_START:
295
+ case EventType.REASONING_MESSAGE_END:
296
+ return [{ kind: "ignored", what: event.type }];
297
+ case EventType.REASONING_MESSAGE_CONTENT:
298
+ return this.reasoning((r) => {
299
+ r.text += event.delta;
300
+ });
301
+ case EventType.REASONING_END:
302
+ return this.reasoning((r) => {
303
+ r.ended_ms = event.timestamp ?? r.ended_ms ?? r.started_ms;
304
+ r.complete = true;
305
+ });
306
+ case EventType.STATE_SNAPSHOT: {
307
+ const changes = this.uiSnapshot(event.snapshot);
308
+ const hasSurfaces = event.snapshot?.surfaces !== void 0;
309
+ return hasSurfaces || changes.length === 0 ? [...changes, ...this.snapshot(event.snapshot)] : changes;
310
+ }
311
+ case EventType.STATE_DELTA:
312
+ return event.delta.flatMap(
313
+ (op) => uiRelative(op.path) !== void 0 ? this.uiPatch(op) : this.patch(op)
314
+ );
315
+ case EventType.CUSTOM:
316
+ return this.custom(event.name, event.value);
317
+ case EventType.SUBAGENT_STARTED: {
318
+ const dot = event.name.indexOf(".");
319
+ this.segs.set(event.subagentRunId, {
320
+ id: event.subagentRunId,
321
+ ext: dot >= 0 ? event.name.slice(0, dot) : event.name,
322
+ what: dot >= 0 ? event.name.slice(dot + 1) : "model",
323
+ description: event.description ?? "",
324
+ parentToolCallId: event.parentToolCallId ?? null,
325
+ turn: this.target().id,
326
+ surfaces: [],
327
+ status: "running",
328
+ usage: null,
329
+ error: null
330
+ });
331
+ return [{ kind: "segment-changed", segment: event.subagentRunId }];
332
+ }
333
+ case EventType.SUBAGENT_FINISHED:
334
+ case EventType.SUBAGENT_ERROR: {
335
+ const seg = this.segs.get(event.subagentRunId);
336
+ if (!seg) return [{ kind: "ignored", what: `${event.type}:${event.subagentRunId}` }];
337
+ if (event.type === EventType.SUBAGENT_ERROR) {
338
+ seg.status = "failed";
339
+ seg.error = { message: event.message, code: event.code ?? null };
340
+ } else {
341
+ seg.status = "done";
342
+ seg.usage = event.result ?? null;
343
+ }
344
+ return [{ kind: "segment-changed", segment: seg.id }];
345
+ }
346
+ default:
347
+ return [{ kind: "ignored", what: event.type }];
348
+ }
349
+ }
350
+ /** Note that surface `id` is part of segment `seg`. */
351
+ join(seg, id) {
352
+ const s = seg === void 0 ? void 0 : this.segs.get(seg);
353
+ if (s && !s.surfaces.includes(id)) s.surfaces.push(id);
354
+ }
355
+ /** The turn new surfaces land in. */
356
+ target() {
357
+ if (this.data.live !== null) {
358
+ const live = this.turn(this.data.live);
359
+ if (live) return live;
360
+ }
361
+ for (let i = this.data.turns.length - 1; i >= 0; i -= 1) {
362
+ const turn = this.data.turns[i];
363
+ if (!turn.settled) return turn;
364
+ }
365
+ const detached = newTurn(DETACHED_TURN);
366
+ this.data.turns.push(detached);
367
+ return detached;
368
+ }
369
+ /**
370
+ * The turn holding surface `id`: the one new surfaces land in when it is
371
+ * there, else the newest that has it, else the one it would land in. A
372
+ * surface id names one block in the thread (decision 0025): a later publish
373
+ * of it changes that block where it was drawn, whichever turn.
374
+ */
375
+ holding(id) {
376
+ const target = this.target();
377
+ if (target.surfaces.some((s) => s.id === id)) return target;
378
+ for (let i = this.data.turns.length - 1; i >= 0; i -= 1) {
379
+ const turn = this.data.turns[i];
380
+ if (turn.surfaces.some((s) => s.id === id)) return turn;
381
+ }
382
+ return target;
383
+ }
384
+ upsert(id, edit) {
385
+ const turn = this.target();
386
+ let surface = turn.surfaces.find((s) => s.id === id);
387
+ if (!surface) {
388
+ surface = { id, status: null, kind: { t: "text", value: "" } };
389
+ turn.surfaces.push(surface);
390
+ }
391
+ edit(surface);
392
+ return [{ kind: "surface-changed", turn: turn.id, surface: id }];
393
+ }
394
+ /** Change the live turn's reasoning, opening it if this is the first. */
395
+ reasoning(edit) {
396
+ const turn = this.target();
397
+ turn.reasoning ??= { text: "", started_ms: null, ended_ms: null, complete: false };
398
+ edit(turn.reasoning);
399
+ return [{ kind: "reasoning-changed", turn: turn.id }];
400
+ }
401
+ settle(error) {
402
+ const turn = this.target();
403
+ turn.settled = true;
404
+ turn.error = error;
405
+ if (turn.reasoning && !turn.reasoning.complete) {
406
+ turn.reasoning.complete = true;
407
+ turn.reasoning.ended_ms ??= turn.reasoning.started_ms;
408
+ }
409
+ const closing = turn.cancelled ? "cancelled" : "done";
410
+ for (const surface of turn.surfaces) {
411
+ if (surface.status === "running") {
412
+ if (surface.kind.t === "markdown") surface.kind.complete = true;
413
+ surface.status = closing;
414
+ }
415
+ }
416
+ this.data.live = null;
417
+ return [{ kind: "turn-settled", turn: turn.id }];
418
+ }
419
+ /** A snapshot's `ui` replaces the document whole. It touches no turn. */
420
+ uiSnapshot(snapshot) {
421
+ const ui = snapshot?.ui;
422
+ if (ui === void 0) return [];
423
+ if (ui === null || typeof ui !== "object") {
424
+ return [{ kind: "ignored", what: "unreadable ui snapshot" }];
425
+ }
426
+ this.data.ui = structuredClone(ui);
427
+ return [{ kind: "ui-changed", path: "" }];
428
+ }
429
+ /** One op under `/ui`, with the RFC 6902 rules. Session-wide. */
430
+ uiPatch(op) {
431
+ const rel = uiRelative(op.path);
432
+ if (!this.data.ui) {
433
+ return [{ kind: "ignored", what: `ui patch before any ui snapshot: ${op.path}` }];
434
+ }
435
+ const why = jsonPatch(this.data.ui, op, rel);
436
+ if (why) return [{ kind: "ignored", what: `ui patch ${op.path}: ${why}` }];
437
+ return [{ kind: "ui-changed", path: rel }];
438
+ }
439
+ snapshot(snapshot) {
440
+ const surfaces = snapshot?.surfaces;
441
+ if (!surfaces) return [{ kind: "ignored", what: "STATE_SNAPSHOT" }];
442
+ const turn = this.target();
443
+ turn.surfaces = [];
444
+ const changes = [];
445
+ for (const [id, value] of Object.entries(surfaces)) {
446
+ turn.surfaces.push(surfaceFromJson(id, value));
447
+ changes.push({ kind: "surface-changed", turn: turn.id, surface: id });
448
+ }
449
+ return changes;
450
+ }
451
+ patch(op) {
452
+ const split = splitPointer(op.path);
453
+ if (!split) {
454
+ return [{ kind: "ignored", what: `patch outside /surfaces: ${op.path}` }];
455
+ }
456
+ const { id, rest } = split;
457
+ const turn = this.holding(id);
458
+ if (rest.length === 0) {
459
+ if (op.op === "remove") {
460
+ turn.surfaces = turn.surfaces.filter((s) => s.id !== id);
461
+ return [{ kind: "surface-removed", turn: turn.id, surface: id }];
462
+ }
463
+ const view = surfaceFromJson(id, op.value);
464
+ const at = turn.surfaces.findIndex((s) => s.id === id);
465
+ if (at >= 0) turn.surfaces[at] = view;
466
+ else turn.surfaces.push(view);
467
+ return [{ kind: "surface-changed", turn: turn.id, surface: id }];
468
+ }
469
+ const surface = turn.surfaces.find((s) => s.id === id);
470
+ if (!surface) {
471
+ return [{ kind: "ignored", what: `patch to an unknown surface ${id}` }];
472
+ }
473
+ const field = rest.join("/");
474
+ if (field === "kind/chunks" || field === "kind/chunks/-") {
475
+ if (op.op === "remove") return [];
476
+ if (op.op !== "add") return [{ kind: "ignored", what: `${op.op} on ${op.path}` }];
477
+ const text2 = Array.isArray(op.value) ? op.value.filter((v) => typeof v === "string").join("") : typeof op.value === "string" ? op.value : "";
478
+ const kind = surface.kind;
479
+ kind.value = (typeof kind.value === "string" ? kind.value : "") + text2;
480
+ return [{ kind: "surface-changed", turn: turn.id, surface: id }];
481
+ }
482
+ let slot = surface;
483
+ for (const segment of rest.slice(0, -1)) {
484
+ const next = slot[segment];
485
+ if (next === null || typeof next !== "object") {
486
+ return [{ kind: "ignored", what: `no such field ${op.path}` }];
487
+ }
488
+ slot = next;
489
+ }
490
+ const leaf = rest[rest.length - 1];
491
+ if (!(leaf in slot)) {
492
+ return [{ kind: "ignored", what: `no such field ${op.path}` }];
493
+ }
494
+ if (op.op === "remove") {
495
+ slot[leaf] = null;
496
+ } else {
497
+ slot[leaf] = op.value;
498
+ }
499
+ return [{ kind: "surface-changed", turn: turn.id, surface: id }];
500
+ }
501
+ custom(name, value) {
502
+ if (name === CONSENT_REQUEST) {
503
+ const prompt = promptFromJson(value);
504
+ if (!prompt) return [{ kind: "ignored", what: name }];
505
+ this.target().prompts.push(prompt);
506
+ return [{ kind: "prompt-raised", prompt: prompt.id }];
507
+ }
508
+ if (name === CONSENT_RESOLVED) {
509
+ const id = String(value["prompt_id"] ?? "");
510
+ const resolution = {
511
+ answer: String(value["answer"] ?? "deny"),
512
+ by: String(value["by"] ?? "fallback")
513
+ };
514
+ const existing = this.data.turns.flatMap((t) => t.prompts).find((p) => p.id === id);
515
+ if (existing) {
516
+ existing.resolved = resolution;
517
+ } else {
518
+ const prompt = promptFromJson(value) ?? {
519
+ id,
520
+ reason: "",
521
+ deadline_ms: 0,
522
+ resolved: null
523
+ };
524
+ prompt.resolved = resolution;
525
+ this.target().prompts.push(prompt);
526
+ }
527
+ return [{ kind: "prompt-resolved", prompt: id }];
528
+ }
529
+ if (name === ANIM) {
530
+ try {
531
+ const def = parseAnim(value);
532
+ (this.data.anims ??= {})[def.id] = structuredClone(def);
533
+ return [{ kind: "anim-changed", id: def.id }];
534
+ } catch (e) {
535
+ return [{ kind: "ignored", what: `${name}: ${e.message}` }];
536
+ }
537
+ }
538
+ if (name === SESSION_CHANGES) {
539
+ const v = value;
540
+ if (v === null || typeof v !== "object" || !Array.isArray(v.files) || !Array.isArray(v.changed) || v.totals == null) {
541
+ return [{ kind: "ignored", what: `${name}: not a change set` }];
542
+ }
543
+ const set = structuredClone(v);
544
+ set.files = set.files.filter((f) => f.kind !== "clean");
545
+ this.data.changes = set;
546
+ return [{ kind: "changes-updated" }];
547
+ }
548
+ if (name === TURN_CANCELLED) {
549
+ const turn = this.target();
550
+ turn.cancelled = true;
551
+ return [{ kind: "turn-settled", turn: turn.id }];
552
+ }
553
+ return [{ kind: "ignored", what: name }];
554
+ }
555
+ };
556
+ function surfaceFromJson(id, value) {
557
+ const raw = value ?? {};
558
+ return {
559
+ id,
560
+ status: raw.status ?? null,
561
+ kind: structuredClone(raw.kind ?? { t: "text", value: "" }),
562
+ ...raw.requester ? { requester: structuredClone(raw.requester) } : {}
563
+ };
564
+ }
565
+ function promptFromJson(value) {
566
+ const prompt = value["prompt"];
567
+ const id = prompt?.["id"];
568
+ if (typeof id !== "string") return null;
569
+ return {
570
+ id,
571
+ reason: typeof prompt?.["reason"] === "string" ? prompt["reason"] : "",
572
+ deadline_ms: typeof value["deadline_ms"] === "number" ? value["deadline_ms"] : 0,
573
+ resolved: null
574
+ };
575
+ }
576
+ function uiRelative(path) {
577
+ if (!path.startsWith("/ui")) return void 0;
578
+ const rest = path.slice(3);
579
+ return rest === "" || rest.startsWith("/") ? rest : void 0;
580
+ }
581
+ function jsonPatch(doc, op, rel) {
582
+ const tokens = rel.split("/").slice(1).map(unescape);
583
+ const last = tokens.pop();
584
+ if (last === void 0) return "cannot replace the whole document with a patch";
585
+ let parent = doc;
586
+ for (const token of tokens) {
587
+ if (Array.isArray(parent)) parent = parent[Number(token)];
588
+ else if (parent !== null && typeof parent === "object") {
589
+ parent = parent[token];
590
+ } else parent = void 0;
591
+ if (parent === void 0) return `no parent at ${tokens.join("/")}`;
592
+ }
593
+ if (Array.isArray(parent)) {
594
+ const at = last === "-" ? parent.length : Number(last);
595
+ if (!Number.isInteger(at)) return `bad index ${last}`;
596
+ if (op.op === "add") {
597
+ if (at > parent.length) return `index ${at} past the end`;
598
+ parent.splice(at, 0, structuredClone(op.value));
599
+ } else if (op.op === "replace") {
600
+ if (at >= parent.length) return `no element ${at}`;
601
+ parent[at] = structuredClone(op.value);
602
+ } else if (op.op === "remove") {
603
+ if (at >= parent.length) return `no element ${at}`;
604
+ parent.splice(at, 1);
605
+ }
606
+ return void 0;
607
+ }
608
+ if (parent === null || typeof parent !== "object") return "parent is not an object or an array";
609
+ const map = parent;
610
+ if (op.op === "add" || op.op === "replace") map[last] = structuredClone(op.value);
611
+ else {
612
+ if (!(last in map)) return `no key ${last}`;
613
+ delete map[last];
614
+ }
615
+ return void 0;
616
+ }
617
+
618
+ // src/transport.ts
619
+ var AGUI_PROTOCOL_VERSION = "1.0";
620
+ var ControlError = class extends Error {
621
+ code;
622
+ constructor(message, code) {
623
+ super(message);
624
+ this.name = "ControlError";
625
+ this.code = code;
626
+ }
627
+ };
628
+ function framesOf(transport) {
629
+ return {
630
+ [Symbol.asyncIterator]() {
631
+ const queue = [];
632
+ let done = false;
633
+ let failure;
634
+ let wake;
635
+ const stop = transport.subscribe(
636
+ (raw) => {
637
+ const frame = frameOf(raw);
638
+ if (!frame) return;
639
+ queue.push(frame);
640
+ wake?.();
641
+ },
642
+ (error) => {
643
+ done = true;
644
+ failure = error;
645
+ wake?.();
646
+ }
647
+ );
648
+ return {
649
+ async next() {
650
+ for (; ; ) {
651
+ const frame = queue.shift();
652
+ if (frame !== void 0) return { value: frame, done: false };
653
+ if (failure) throw failure;
654
+ if (done) return { value: void 0, done: true };
655
+ await new Promise((r) => wake = r);
656
+ wake = void 0;
657
+ }
658
+ },
659
+ async return() {
660
+ stop();
661
+ done = true;
662
+ return { value: void 0, done: true };
663
+ }
664
+ };
665
+ }
666
+ };
667
+ }
668
+ var FrameHub = class {
669
+ frames = /* @__PURE__ */ new Set();
670
+ closes = /* @__PURE__ */ new Set();
671
+ pending = [];
672
+ ended = null;
673
+ subscribe(onFrame, onClose) {
674
+ this.frames.add(onFrame);
675
+ if (onClose) this.closes.add(onClose);
676
+ if (this.pending.length > 0) {
677
+ const held = this.pending;
678
+ this.pending = [];
679
+ for (const f of held) onFrame(f);
680
+ }
681
+ if (this.ended && onClose) onClose(this.ended.error);
682
+ return () => {
683
+ this.frames.delete(onFrame);
684
+ if (onClose) this.closes.delete(onClose);
685
+ };
686
+ }
687
+ push(frame) {
688
+ if (this.frames.size === 0) {
689
+ this.pending.push(frame);
690
+ return;
691
+ }
692
+ for (const f of [...this.frames]) f(frame);
693
+ }
694
+ end(error) {
695
+ if (this.ended) return;
696
+ this.ended = error ? { error } : {};
697
+ for (const c of [...this.closes]) c(error);
698
+ }
699
+ get isEnded() {
700
+ return this.ended !== null;
701
+ }
702
+ };
703
+ function parseAddress(raw) {
704
+ const text2 = raw.trim();
705
+ const parts = text2.split("/");
706
+ const ok = (s) => s !== void 0 && s !== "" && !/\s/.test(s);
707
+ if (parts.length === 1 && ok(parts[0])) return { machine: null, session: parts[0] };
708
+ if (parts.length === 2 && ok(parts[0]) && ok(parts[1])) return { machine: parts[0], session: parts[1] };
709
+ throw new Error(`\`${raw}\` is not a session address: expected \`session\` or \`machine/session\``);
710
+ }
711
+ function formatAddress(a) {
712
+ return a.machine ? `${a.machine}/${a.session}` : a.session;
713
+ }
714
+
715
+ // src/transport-http.ts
716
+ var HttpSseTransport = class {
717
+ kind = "http";
718
+ url;
719
+ token;
720
+ hub = new FrameHub();
721
+ abort = null;
722
+ /** The subscription's number from `GET /events` (decision 0058), sent back on every `POST /control`. */
723
+ conn = null;
724
+ constructor(options) {
725
+ this.url = options.url.replace(/\/+$/, "");
726
+ this.token = options.token;
727
+ }
728
+ /** The headers every request carries. */
729
+ headers() {
730
+ return {
731
+ "content-type": "application/json",
732
+ ...this.token ? { Authorization: `Bearer ${this.token}` } : {},
733
+ // Without it a request is anonymous: it answers no question and no consent.
734
+ ...this.conn !== null ? { "x-orrery-conn": this.conn } : {}
735
+ };
736
+ }
737
+ async connect(hello) {
738
+ this.abort?.abort();
739
+ if (this.hub.isEnded || this.abort) this.hub = new FrameHub();
740
+ const abort = new AbortController();
741
+ this.abort = abort;
742
+ this.conn = null;
743
+ const params = new URLSearchParams();
744
+ if (hello.since !== void 0) params.set("since", String(hello.since));
745
+ if (hello.session !== void 0) params.set("session", hello.session);
746
+ if (hello.machine !== void 0) params.set("machine", hello.machine);
747
+ if (hello.client !== void 0) {
748
+ params.set("client", hello.client);
749
+ params.set("draws", (hello.draws ?? []).join(","));
750
+ }
751
+ const query = params.size > 0 ? `?${params.toString()}` : "";
752
+ const response = await fetch(`${this.url}/events${query}`, {
753
+ headers: this.headers(),
754
+ signal: abort.signal
755
+ });
756
+ if (!response.ok || !response.body) {
757
+ const why = response.ok ? "" : await response.text().catch(() => "");
758
+ throw new Error(`GET /events: ${response.status}${why ? ` ${why}` : ""}`);
759
+ }
760
+ this.conn = response.headers.get("x-orrery-conn");
761
+ const hub = this.hub;
762
+ void this.pump(response.body, hub, abort.signal);
763
+ const conn = Number(this.conn ?? NaN);
764
+ return { format: "json", protocol: AGUI_PROTOCOL_VERSION, ...Number.isInteger(conn) ? { conn } : {} };
765
+ }
766
+ subscribe(onFrame, onClose) {
767
+ return this.hub.subscribe(onFrame, onClose);
768
+ }
769
+ async send(request) {
770
+ const response = await fetch(`${this.url}/control`, {
771
+ method: "POST",
772
+ headers: this.headers(),
773
+ body: JSON.stringify(request)
774
+ });
775
+ if (!response.ok) {
776
+ throw new ControlError(
777
+ `POST /control: ${response.status} ${await response.text().catch(() => "")}`.trim(),
778
+ response.headers.get("x-orrery-code") ?? void 0
779
+ );
780
+ }
781
+ return response.json();
782
+ }
783
+ async close() {
784
+ this.abort?.abort();
785
+ this.abort = null;
786
+ this.hub.end();
787
+ }
788
+ async pump(body, hub, signal) {
789
+ try {
790
+ for await (const frame of sse(body)) {
791
+ if (signal.aborted) break;
792
+ hub.push(frame);
793
+ }
794
+ hub.end();
795
+ } catch (e) {
796
+ hub.end(signal.aborted ? void 0 : e instanceof Error ? e : new Error(String(e)));
797
+ }
798
+ }
799
+ };
800
+ async function* sse(body) {
801
+ const reader = body.getReader();
802
+ const decoder = new TextDecoder();
803
+ let buffer = "";
804
+ for (; ; ) {
805
+ const { done, value } = await reader.read();
806
+ if (done) return;
807
+ buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
808
+ let at = buffer.indexOf("\n\n");
809
+ while (at >= 0) {
810
+ const block = buffer.slice(0, at);
811
+ buffer = buffer.slice(at + 2);
812
+ for (const line of block.split("\n")) {
813
+ if (!line.startsWith("data: ")) continue;
814
+ const frame = frameOf(JSON.parse(line.slice("data: ".length)));
815
+ if (frame) yield frame;
816
+ }
817
+ at = buffer.indexOf("\n\n");
818
+ }
819
+ }
820
+ }
821
+
822
+ // src/transport-ws.ts
823
+ var OPEN = 1;
824
+ var REPLY_TIMEOUT_MS = 3e4;
825
+ function text(data) {
826
+ if (typeof data === "string") return data;
827
+ if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);
828
+ if (ArrayBuffer.isView(data)) return new TextDecoder().decode(data);
829
+ if (Array.isArray(data)) return data.map((d) => text(d) ?? "").join("");
830
+ return null;
831
+ }
832
+ var WebSocketTransport = class {
833
+ kind = "ws";
834
+ url;
835
+ token;
836
+ Ctor;
837
+ socket = null;
838
+ hub = new FrameHub();
839
+ pending = /* @__PURE__ */ new Map();
840
+ constructor(options) {
841
+ this.url = options.url;
842
+ this.token = options.token;
843
+ const ctor = options.WebSocket ?? globalThis.WebSocket;
844
+ if (!ctor) {
845
+ throw new Error("no WebSocket here: pass one as `WebSocket` (in Node < 22, the `ws` package)");
846
+ }
847
+ this.Ctor = ctor;
848
+ }
849
+ connect(hello) {
850
+ if (this.socket) this.socket.close(1e3, "reconnect");
851
+ if (this.hub.isEnded || this.socket) this.hub = new FrameHub();
852
+ const hub = this.hub;
853
+ const socket = new this.Ctor(this.url);
854
+ socket.binaryType = "arraybuffer";
855
+ this.socket = socket;
856
+ const first = { ...hello, format: "json", ...this.token && hello.token === void 0 ? { token: this.token } : {} };
857
+ return new Promise((resolve, reject) => {
858
+ let acked = false;
859
+ socket.addEventListener("open", () => socket.send(JSON.stringify(first)));
860
+ socket.addEventListener("message", (event) => {
861
+ const body = text(event.data);
862
+ if (body === null) return;
863
+ let value;
864
+ try {
865
+ value = JSON.parse(body);
866
+ } catch {
867
+ if (!acked) {
868
+ acked = true;
869
+ reject(new Error(`ws ${this.url}: the first message is not a HelloAck: ${body.slice(0, 120)}`));
870
+ socket.close(1002, "bad handshake");
871
+ }
872
+ return;
873
+ }
874
+ if (!acked) {
875
+ acked = true;
876
+ const ack = value;
877
+ const no = ack.refused ?? ack.error;
878
+ if (typeof no === "string" || typeof ack.protocol !== "string") {
879
+ reject(new Error(`ws ${this.url}: refused: ${no ?? body.slice(0, 200)}`));
880
+ socket.close(1e3, "refused");
881
+ return;
882
+ }
883
+ resolve({ format: "json", ...ack });
884
+ return;
885
+ }
886
+ const reply = value;
887
+ if (typeof reply === "object" && reply !== null && reply.t === "reply") {
888
+ if (typeof reply.reqId !== "string") return;
889
+ const waiting = this.pending.get(reply.reqId);
890
+ if (!waiting) return;
891
+ this.pending.delete(reply.reqId);
892
+ clearTimeout(waiting.timer);
893
+ if (reply.ok === true) waiting.resolve(reply.value ?? null);
894
+ else
895
+ waiting.reject(
896
+ new ControlError(
897
+ typeof reply.error === "string" ? reply.error : "the server refused the request",
898
+ typeof reply.code === "string" ? reply.code : void 0
899
+ )
900
+ );
901
+ return;
902
+ }
903
+ const frame = frameOf(value);
904
+ if (frame) hub.push(frame);
905
+ });
906
+ socket.addEventListener("error", () => {
907
+ if (acked) return;
908
+ acked = true;
909
+ const why = new Error(
910
+ `ws ${this.url}: could not open the WebSocket (refused, unreachable, or a certificate this process does not trust)`
911
+ );
912
+ reject(why);
913
+ if (this.socket === socket) this.socket = null;
914
+ this.failPending(why);
915
+ hub.end(why);
916
+ try {
917
+ socket.close();
918
+ } catch {
919
+ }
920
+ });
921
+ socket.addEventListener("close", (event) => {
922
+ if (this.socket === socket) this.socket = null;
923
+ const clean = event.code === 1e3 || event.code === 1005;
924
+ const why = new Error(`ws ${this.url}: closed (${event.code}${event.reason ? ` ${event.reason}` : ""})`);
925
+ if (!acked) {
926
+ acked = true;
927
+ reject(new Error(`ws ${this.url}: closed before the HelloAck (${event.code}${event.reason ? ` ${event.reason}` : ""})`));
928
+ }
929
+ if (this.socket === null) this.failPending(why);
930
+ hub.end(clean ? void 0 : why);
931
+ });
932
+ });
933
+ }
934
+ subscribe(onFrame, onClose) {
935
+ return this.hub.subscribe(onFrame, onClose);
936
+ }
937
+ async send(request) {
938
+ const socket = this.socket;
939
+ if (!socket || socket.readyState !== OPEN) {
940
+ throw new Error(`ws ${this.url}: not connected; call connect() first`);
941
+ }
942
+ return new Promise((resolve, reject) => {
943
+ const timer = setTimeout(() => {
944
+ this.pending.delete(request.id);
945
+ reject(new Error(`ws ${this.url}: no reply to \`${request.t}\` in ${REPLY_TIMEOUT_MS / 1e3} s`));
946
+ }, REPLY_TIMEOUT_MS);
947
+ this.pending.set(request.id, { resolve, reject, timer });
948
+ try {
949
+ socket.send(JSON.stringify(request));
950
+ } catch (error) {
951
+ clearTimeout(timer);
952
+ this.pending.delete(request.id);
953
+ reject(error instanceof Error ? error : new Error(String(error)));
954
+ }
955
+ });
956
+ }
957
+ failPending(why) {
958
+ for (const [id, p] of [...this.pending]) {
959
+ clearTimeout(p.timer);
960
+ this.pending.delete(id);
961
+ p.reject(new Error(`${why.message}; the request got no reply`));
962
+ }
963
+ }
964
+ async close() {
965
+ const socket = this.socket;
966
+ this.socket = null;
967
+ this.failPending(new Error(`ws ${this.url}: closed`));
968
+ socket?.close(1e3, "bye");
969
+ this.hub.end();
970
+ }
971
+ };
972
+
973
+ // src/session.ts
974
+ import { HttpAgent } from "@ag-ui/client";
975
+ function filePart(name, mimeType, base64) {
976
+ const source = { type: "data", value: base64, mimeType };
977
+ const metadata = { filename: name };
978
+ return mimeType.startsWith("image/") ? { type: "image", source, metadata } : { type: "document", source, metadata };
979
+ }
980
+ var MAX_FILE_BYTES = 5 * 1024 * 1024;
981
+ var MAX_MESSAGE_BYTES = 10 * 1024 * 1024;
982
+ function parseEndpoint(raw) {
983
+ if (raw === "inproc:" || raw === "inproc") return { kind: "inproc" };
984
+ if (raw.startsWith("pipe:")) {
985
+ const name = raw.slice("pipe:".length);
986
+ if (!name) throw new Error(`\`${raw}\` is not an endpoint`);
987
+ return { kind: "pipe", name };
988
+ }
989
+ for (const scheme of ["http://", "https://", "ws://", "wss://"]) {
990
+ if (!raw.startsWith(scheme)) continue;
991
+ const rest = raw.slice(scheme.length);
992
+ const at = rest.indexOf("@");
993
+ const token = at > 0 ? rest.slice(0, at) : void 0;
994
+ const host = (at >= 0 ? rest.slice(at + 1) : rest).replace(/\/+$/, "");
995
+ if (!host) throw new Error(`\`${raw}\` is not an endpoint`);
996
+ const kind = scheme.startsWith("ws") ? "ws" : "http";
997
+ return token === void 0 ? { kind, url: `${scheme}${host}` } : { kind, url: `${scheme}${host}`, token };
998
+ }
999
+ throw new Error(
1000
+ `\`${raw}\` is not an endpoint: expected \`inproc:\`, \`pipe:<name>\`, \`http://[token@]host:port\` or \`ws://[token@]host:port/path\``
1001
+ );
1002
+ }
1003
+ function reqId() {
1004
+ return crypto.randomUUID();
1005
+ }
1006
+ var STATE_CHANGED = "orrery.state.changed";
1007
+ var MOVED = "orrery.moved";
1008
+ function movedOf(frame) {
1009
+ const f = frame;
1010
+ if (f.type !== "CUSTOM" || f.name !== MOVED || typeof f.value?.machine !== "string" || typeof f.value?.session !== "string") return null;
1011
+ return f.value;
1012
+ }
1013
+ var AguiSession = class _AguiSession {
1014
+ constructor(transport, session) {
1015
+ this.transport = transport;
1016
+ this.session = session;
1017
+ }
1018
+ agent = null;
1019
+ lastSeq = null;
1020
+ declared = null;
1021
+ machine = null;
1022
+ ownConn = 0;
1023
+ /** Watch id → callback, for `orrery.state.changed` frames (decision 0016). */
1024
+ watchers = /* @__PURE__ */ new Map();
1025
+ /** Connect to an endpoint: `http(s)://` or `ws(s)://`. */
1026
+ static connect(endpoint, options = {}) {
1027
+ const parsed = typeof endpoint === "string" ? parseEndpoint(endpoint) : endpoint;
1028
+ if (parsed.kind === "http") {
1029
+ return new _AguiSession(new HttpSseTransport({ url: parsed.url, ...parsed.token ? { token: parsed.token } : {} }), null);
1030
+ }
1031
+ if (parsed.kind === "ws") {
1032
+ return new _AguiSession(
1033
+ new WebSocketTransport({
1034
+ url: parsed.url,
1035
+ ...parsed.token || options.token ? { token: parsed.token ?? options.token } : {},
1036
+ ...options.WebSocket ? { WebSocket: options.WebSocket } : {}
1037
+ }),
1038
+ null
1039
+ );
1040
+ }
1041
+ throw new Error(
1042
+ `${parsed.kind} is not reachable from this entry; use the HTTP or WebSocket listener (or \`@getorrery/client/node\` for the client channel)`
1043
+ );
1044
+ }
1045
+ /** A session over a transport this SDK did not build. */
1046
+ static over(transport) {
1047
+ return new _AguiSession(transport, null);
1048
+ }
1049
+ /** The stock `@ag-ui/client` agent, unmodified, for anyone who wants it. HTTP only. */
1050
+ httpAgent() {
1051
+ const t = this.http("httpAgent()");
1052
+ const auth = t.headers()["Authorization"];
1053
+ this.agent ??= new HttpAgent({ url: `${t.url}/run`, headers: auth ? { Authorization: auth } : {} });
1054
+ return this.agent;
1055
+ }
1056
+ /**
1057
+ * This client's connection (harness decision 0058), from the last `events()`
1058
+ * ack: a surface whose `requester.conn` is another is somebody else's
1059
+ * question ([`choosingLine`]). `0` until then, and for a server too old to say.
1060
+ */
1061
+ get conn() {
1062
+ return this.ownConn;
1063
+ }
1064
+ /** The last `seq` this session has seen. */
1065
+ seq() {
1066
+ return this.lastSeq;
1067
+ }
1068
+ /** The session attached to, as `[machine/]session`. */
1069
+ address() {
1070
+ if (!this.session) return null;
1071
+ return this.machine ? `${this.machine}/${this.session}` : this.session;
1072
+ }
1073
+ /**
1074
+ * Say what this client is and what it draws, so the host knows whether it
1075
+ * can answer consent (decision 0041 §2). An HTTP client has no manifest;
1076
+ * this is its `client/attach`, sent with `events()`. Without it the host
1077
+ * counts the client as unable to answer.
1078
+ */
1079
+ declare(client, draws) {
1080
+ this.declared = { client, draws: [...draws] };
1081
+ }
1082
+ /**
1083
+ * Attach to a session, `[machine/]session`, optionally replaying from where
1084
+ * this client left off.
1085
+ *
1086
+ * Over HTTP this is the `session.attach` request it always was. Over a
1087
+ * transport whose `Hello` names the session (the WebSocket), the address is
1088
+ * remembered and travels in the next `events()`'s `Hello`, as on the pipe.
1089
+ */
1090
+ async attach(address, since) {
1091
+ const a = parseAddress(address);
1092
+ this.session = a.session;
1093
+ this.machine = a.machine;
1094
+ if (this.transport.kind !== "http") return;
1095
+ await this.control({
1096
+ t: "session.attach",
1097
+ id: reqId(),
1098
+ session: a.session,
1099
+ ...since === void 0 ? {} : { since }
1100
+ });
1101
+ }
1102
+ /**
1103
+ * Submit a turn. Resolves with the turn's id from the server's answer
1104
+ * (the HTTP body, or the WebSocket `reply` value), and `null` when the
1105
+ * answer carries none.
1106
+ *
1107
+ * `parts` are files, as AG-UI 1.0 content parts (harness decision 0050):
1108
+ * the kernel puts each into the turn's context, or the turn says why not.
1109
+ * [`filePart`] makes one from a file's bytes.
1110
+ */
1111
+ async submit(text2, parts = []) {
1112
+ const answer = await this.control({
1113
+ t: "turn.submit",
1114
+ id: reqId(),
1115
+ session: this.requireSession(),
1116
+ input: parts.length > 0 ? { text: text2, parts: [...parts] } : { text: text2 }
1117
+ });
1118
+ if (answer === null || answer === void 0) return null;
1119
+ const turn = answer.turn;
1120
+ if (!turn) throw new Error(`no turn id in ${JSON.stringify(answer)}`);
1121
+ return turn;
1122
+ }
1123
+ /** Cancel a turn in flight. */
1124
+ async cancel(turn) {
1125
+ await this.control({
1126
+ t: "turn.cancel",
1127
+ id: reqId(),
1128
+ session: this.requireSession(),
1129
+ turn
1130
+ });
1131
+ }
1132
+ /** Answer a consent prompt. */
1133
+ async answer(prompt, answer) {
1134
+ await this.control({ t: "consent.answer", id: reqId(), prompt, answer });
1135
+ }
1136
+ /**
1137
+ * Send what a surface produced.
1138
+ *
1139
+ * The whole of a client's write path. A client edit arrives here, as an intent
1140
+ * the kernel validates, never as a state mutation.
1141
+ */
1142
+ async intent(surface, value) {
1143
+ await this.control({
1144
+ t: "intent",
1145
+ id: reqId(),
1146
+ session: this.requireSession(),
1147
+ surface,
1148
+ value
1149
+ });
1150
+ }
1151
+ /**
1152
+ * An action on a node of the UI document: a list item chosen, a button
1153
+ * pressed (decision 0002).
1154
+ */
1155
+ async uiIntent(node, action, value) {
1156
+ await this.control({
1157
+ t: "ui.intent",
1158
+ id: reqId(),
1159
+ session: this.requireSession(),
1160
+ node,
1161
+ action,
1162
+ value: value ?? null
1163
+ });
1164
+ }
1165
+ /**
1166
+ * The context API's `watch(ext, key, onChange)` (decision 0016), the same
1167
+ * call an extension makes in any runtime: `onChange` gets the key's value
1168
+ * now, then every change, picked out of the `events()` stream. Resolves to
1169
+ * `stop()`. Rejects, naming the key, when it does not exist or is private.
1170
+ * Works over HTTP and WebSocket: both answer requests, and changes arrive
1171
+ * as `orrery.state.changed` frames.
1172
+ */
1173
+ async watch(ext, key, onChange) {
1174
+ const answer = await this.control({
1175
+ t: "state.watch",
1176
+ id: reqId(),
1177
+ session: this.requireSession(),
1178
+ ext,
1179
+ key
1180
+ });
1181
+ if (answer === null) {
1182
+ throw new Error(`the ${this.transport.kind} server returned no answer to state.watch`);
1183
+ }
1184
+ this.watchers.set(answer.watch, onChange);
1185
+ onChange(answer.value);
1186
+ let stopped = false;
1187
+ return async () => {
1188
+ if (stopped) return;
1189
+ stopped = true;
1190
+ this.watchers.delete(answer.watch);
1191
+ await this.control({ t: "state.unwatch", id: reqId(), session: this.requireSession(), watch: answer.watch });
1192
+ };
1193
+ }
1194
+ /** Hand a frame's `orrery.state.changed` to the watch it names. */
1195
+ dispatch(frame) {
1196
+ const f = frame;
1197
+ if (f.type !== "CUSTOM" || f.name !== STATE_CHANGED || typeof f.value?.watch !== "number") return;
1198
+ this.watchers.get(f.value.watch)?.(f.value.value);
1199
+ }
1200
+ /**
1201
+ * Move this session to another machine of the account (platform decision
1202
+ * 0014): `to` is its name or id. The move runs as the session's `/handoff`
1203
+ * flow, so its steps arrive as frames; the node then sends `orrery.moved`
1204
+ * ([`movedOf`]). Needs the `handoff` client grant.
1205
+ */
1206
+ async handoff(to, options = {}) {
1207
+ return this.control({
1208
+ t: "session.handoff",
1209
+ id: reqId(),
1210
+ session: this.requireSession(),
1211
+ to,
1212
+ ...options.untracked ? { untracked: options.untracked } : {},
1213
+ ...options.turn ? { turn: options.turn } : {}
1214
+ });
1215
+ }
1216
+ /** Run a command the host owns: `/model`, `/providers`, `/help`. */
1217
+ async command(name, args) {
1218
+ await this.control({
1219
+ t: "command",
1220
+ id: reqId(),
1221
+ session: this.requireSession(),
1222
+ name,
1223
+ ...args === void 0 ? {} : { args }
1224
+ });
1225
+ }
1226
+ /**
1227
+ * Every frame of the session, from now (or from `since`) on: the transport's
1228
+ * `Hello`, then its frames.
1229
+ *
1230
+ * The passive half: a client that subscribes here sees every run — its own
1231
+ * and anybody else's — and the screen the host declares, which the kernel
1232
+ * re-sends on every subscription. `run` only ever sees the run it started.
1233
+ */
1234
+ async *events(since) {
1235
+ const hello = { format: "json", protocol: AGUI_PROTOCOL_VERSION };
1236
+ if (since !== void 0) hello.since = since;
1237
+ if (this.transport.kind !== "http") {
1238
+ if (this.session) hello.session = this.session;
1239
+ if (this.machine) hello.machine = this.machine;
1240
+ }
1241
+ if (this.declared) {
1242
+ hello.client = this.declared.client;
1243
+ hello.draws = this.declared.draws;
1244
+ }
1245
+ this.ownConn = (await this.transport.connect(hello)).conn ?? 0;
1246
+ for await (const frame of framesOf(this.transport)) {
1247
+ this.seen(frame);
1248
+ yield frame;
1249
+ }
1250
+ }
1251
+ /** `events()` from where this session left off: what a renderer's `frames()` wants. */
1252
+ frames() {
1253
+ return this.events(this.lastSeq ?? void 0);
1254
+ }
1255
+ /**
1256
+ * Start a run and yield its frames. HTTP only.
1257
+ *
1258
+ * `POST /run` with a `RunAgentInput`, parsed as SSE. `@ag-ui/client` would
1259
+ * give the same events with its own subscriber API; this form exists because
1260
+ * ours carry a `seq` the stock agent drops, and `seq` is the whole of the
1261
+ * resumption story.
1262
+ */
1263
+ async *run(text2) {
1264
+ const t = this.http("run()");
1265
+ const response = await fetch(`${t.url}/run`, {
1266
+ method: "POST",
1267
+ headers: t.headers(),
1268
+ body: JSON.stringify({
1269
+ threadId: this.requireSession(),
1270
+ messages: [{ role: "user", content: text2 }]
1271
+ })
1272
+ });
1273
+ if (!response.ok || !response.body) {
1274
+ throw new Error(`POST /run: ${response.status}`);
1275
+ }
1276
+ for await (const frame of sse(response.body)) {
1277
+ this.seen(frame);
1278
+ yield frame;
1279
+ }
1280
+ }
1281
+ /** Stop the stream. */
1282
+ async close() {
1283
+ await this.transport.close();
1284
+ }
1285
+ seen(frame) {
1286
+ if (frame.seq !== OUT_OF_ORDER) this.lastSeq = frame.seq;
1287
+ this.dispatch(frame);
1288
+ }
1289
+ http(what) {
1290
+ if (this.transport instanceof HttpSseTransport) return this.transport;
1291
+ throw new Error(`${what} is HTTP-only; over ${this.transport.kind}, use submit() and events()`);
1292
+ }
1293
+ requireSession() {
1294
+ if (!this.session) throw new Error("not attached to a session");
1295
+ return this.session;
1296
+ }
1297
+ async control(request) {
1298
+ return this.transport.send(request);
1299
+ }
1300
+ };
1301
+
1302
+ // src/workspace.ts
1303
+ var WORKSPACE_READ = "workspace.read";
1304
+ var WORKSPACE_COMMIT = "workspace.commit";
1305
+ var WORKSPACE_PUSH = "workspace.push";
1306
+ var WORKSPACE_READ_CAP_BYTES = 1 << 20;
1307
+ function requestId() {
1308
+ return globalThis.crypto?.randomUUID?.() ?? `ws-${Date.now()}-${Math.random().toString(16).slice(2)}`;
1309
+ }
1310
+ var workspaceRequest = {
1311
+ read(ask) {
1312
+ const req = { t: WORKSPACE_READ, id: requestId(), session: ask.session, path: ask.path };
1313
+ if (ask.base) req["base"] = true;
1314
+ return req;
1315
+ },
1316
+ commit(ask) {
1317
+ const req = { t: WORKSPACE_COMMIT, id: requestId(), session: ask.session, message: ask.message };
1318
+ if (ask.paths !== void 0) req["paths"] = ask.paths;
1319
+ return req;
1320
+ },
1321
+ push(ask) {
1322
+ return { t: WORKSPACE_PUSH, id: requestId(), session: ask.session };
1323
+ }
1324
+ };
1325
+ async function workspaceRead(transport, ask) {
1326
+ return await transport.send(workspaceRequest.read(ask));
1327
+ }
1328
+ async function workspaceCommit(transport, ask) {
1329
+ return await transport.send(workspaceRequest.commit(ask));
1330
+ }
1331
+ async function workspacePush(transport, ask) {
1332
+ return await transport.send(workspaceRequest.push(ask));
1333
+ }
1334
+ function workspaceErrorCode(error) {
1335
+ return error instanceof ControlError ? error.code : void 0;
1336
+ }
1337
+
1338
+ // src/kept.ts
1339
+ function resumeFrom(cursor, epoch) {
1340
+ if (epoch === void 0 || cursor.epoch === void 0 || cursor.epoch === epoch) {
1341
+ return cursor.frameSeq === void 0 ? { replay: false } : { since: cursor.frameSeq, replay: false };
1342
+ }
1343
+ return { replay: true };
1344
+ }
1345
+ function afterOf(cursor) {
1346
+ return cursor.entrySeq === void 0 ? void 0 : `${cursor.epoch ?? 0},${cursor.entrySeq}`;
1347
+ }
1348
+ async function readKept(page, cursor, seen) {
1349
+ const fresh = [];
1350
+ let at = cursor;
1351
+ let last = null;
1352
+ for (let after = afterOf(cursor), n = 0; n < 1e3; n += 1) {
1353
+ const p = await page(after);
1354
+ last = p;
1355
+ for (const e of p.entries) {
1356
+ const id = `${e.epoch},${e.seq}`;
1357
+ if (seen.has(id)) continue;
1358
+ seen.add(id);
1359
+ fresh.push(e);
1360
+ at = { ...at, epoch: e.epoch, entrySeq: e.seq };
1361
+ }
1362
+ if (!p.next) break;
1363
+ after = `${p.next.epoch},${p.next.seq}`;
1364
+ }
1365
+ return { entries: fresh, cursor: at, page: last };
1366
+ }
1367
+ function keptTranscript(entries) {
1368
+ const out = [];
1369
+ const tools = /* @__PURE__ */ new Map();
1370
+ const settle = (call, outcome) => {
1371
+ const tool = call ? tools.get(call) : void 0;
1372
+ if (tool) tool.ok = outcome?.t === "ok" || outcome?.t === "truncated";
1373
+ };
1374
+ for (const e of entries) {
1375
+ if (e.kind !== "turn") continue;
1376
+ const row = e.body;
1377
+ const p = row?.payload;
1378
+ if (!p) continue;
1379
+ switch (p.t) {
1380
+ case "user":
1381
+ out.push({ role: "user", text: p.input?.text ?? "", key: e.key });
1382
+ break;
1383
+ case "assistant":
1384
+ (p.content ?? []).forEach((b, i) => {
1385
+ const key = `${e.key}:${i}`;
1386
+ if (b.t === "text" && b.text) out.push({ role: "assistant", text: b.text, key });
1387
+ else if (b.t === "thinking" && b.text) out.push({ role: "thinking", text: b.text, key });
1388
+ else if (b.t === "image") out.push({ role: "assistant", text: `[image ${b.media_type ?? ""}]`.trim(), key });
1389
+ else if (b.t === "tool-use" && b.call) {
1390
+ const tool = { role: "tool", name: b.name ?? "tool", call: b.call, ok: null, key };
1391
+ tools.set(b.call, tool);
1392
+ out.push(tool);
1393
+ } else if (b.t === "tool-result") settle(b.call, b.outcome);
1394
+ });
1395
+ break;
1396
+ case "tool-result":
1397
+ settle(p.call, p.outcome);
1398
+ break;
1399
+ case "summary":
1400
+ if (p.text) out.push({ role: "assistant", text: p.text, key: e.key });
1401
+ break;
1402
+ }
1403
+ }
1404
+ return out;
1405
+ }
1406
+
1407
+ // src/conformance.ts
1408
+ var conformance_exports = {};
1409
+ __export(conformance_exports, {
1410
+ SCENARIO_COUNT: () => SCENARIO_COUNT,
1411
+ checkpoints: () => checkpoints,
1412
+ framesOf: () => framesOf2,
1413
+ parse: () => parse,
1414
+ replay: () => replay
1415
+ });
1416
+ var KNOWN = /* @__PURE__ */ new Set([
1417
+ "RUN_STARTED",
1418
+ "RUN_FINISHED",
1419
+ "RUN_ERROR",
1420
+ "STEP_STARTED",
1421
+ "STEP_FINISHED",
1422
+ "TEXT_MESSAGE_START",
1423
+ "TEXT_MESSAGE_CONTENT",
1424
+ "TEXT_MESSAGE_END",
1425
+ "TOOL_CALL_START",
1426
+ "TOOL_CALL_ARGS",
1427
+ "TOOL_CALL_END",
1428
+ "TOOL_CALL_RESULT",
1429
+ "REASONING_START",
1430
+ "REASONING_MESSAGE_START",
1431
+ "REASONING_MESSAGE_CONTENT",
1432
+ "REASONING_MESSAGE_END",
1433
+ "REASONING_END",
1434
+ "STATE_SNAPSHOT",
1435
+ "STATE_DELTA",
1436
+ "CUSTOM",
1437
+ "SUBAGENT_STARTED",
1438
+ "SUBAGENT_FINISHED",
1439
+ "SUBAGENT_ERROR"
1440
+ ]);
1441
+ function parse(text2, name, path = `${name}.jsonl`) {
1442
+ const steps = [];
1443
+ text2.split(/\r?\n/).forEach((raw, index) => {
1444
+ const line = raw.trim();
1445
+ if (!line || line.startsWith("#")) return;
1446
+ let value;
1447
+ try {
1448
+ value = JSON.parse(line);
1449
+ } catch (e) {
1450
+ throw new Error(`${path}:${index + 1}: ${e.message}`);
1451
+ }
1452
+ if ("ev" in value) {
1453
+ const ev = frameOf(value["ev"]);
1454
+ if (!ev) {
1455
+ throw new Error(`${path}:${index + 1}: an event with no seq`);
1456
+ }
1457
+ steps.push(
1458
+ KNOWN.has(ev.type ?? "") ? { kind: "event", frame: ev } : { kind: "unknown", seq: ev.seq, what: ev.type ?? "?" }
1459
+ );
1460
+ } else if ("expect" in value) {
1461
+ steps.push({ kind: "expect", state: value["expect"] });
1462
+ } else {
1463
+ throw new Error(`${path}:${index + 1}: expected \`ev\` or \`expect\``);
1464
+ }
1465
+ });
1466
+ return { name, path, steps };
1467
+ }
1468
+ function framesOf2(scenario) {
1469
+ return scenario.steps.flatMap((s) => s.kind === "event" ? [s.frame] : []);
1470
+ }
1471
+ var SCENARIO_COUNT = 68;
1472
+ function checkpoints(scenario) {
1473
+ const store = new SurfaceStore();
1474
+ const out = [];
1475
+ for (const step of scenario.steps) {
1476
+ if (step.kind === "event") store.apply(step.frame);
1477
+ else if (step.kind === "unknown") store.applyUnknown(step.seq, step.what);
1478
+ else {
1479
+ out.push({
1480
+ index: out.length + 1,
1481
+ actual: structuredClone(store.state()),
1482
+ expected: step.state
1483
+ });
1484
+ }
1485
+ }
1486
+ return out;
1487
+ }
1488
+ function replay(scenario) {
1489
+ const store = new SurfaceStore();
1490
+ for (const step of scenario.steps) {
1491
+ if (step.kind === "event") store.apply(step.frame);
1492
+ else if (step.kind === "unknown") store.applyUnknown(step.seq, step.what);
1493
+ }
1494
+ return store;
1495
+ }
1496
+
1497
+ export {
1498
+ CONSENT_REQUEST,
1499
+ CONSENT_RESOLVED,
1500
+ TURN_CANCELLED,
1501
+ PLACED,
1502
+ UNPLACED,
1503
+ frameOf,
1504
+ firstSeq,
1505
+ ANIM,
1506
+ OUT_OF_ORDER,
1507
+ parseAnim,
1508
+ SESSION_CHANGES,
1509
+ USER_MESSAGE,
1510
+ DETACHED_TURN,
1511
+ choosingLine,
1512
+ thoughtMs,
1513
+ NOTICES,
1514
+ SurfaceStore,
1515
+ AGUI_PROTOCOL_VERSION,
1516
+ ControlError,
1517
+ framesOf,
1518
+ FrameHub,
1519
+ parseAddress,
1520
+ formatAddress,
1521
+ HttpSseTransport,
1522
+ sse,
1523
+ WebSocketTransport,
1524
+ filePart,
1525
+ MAX_FILE_BYTES,
1526
+ MAX_MESSAGE_BYTES,
1527
+ parseEndpoint,
1528
+ STATE_CHANGED,
1529
+ MOVED,
1530
+ movedOf,
1531
+ AguiSession,
1532
+ WORKSPACE_READ,
1533
+ WORKSPACE_COMMIT,
1534
+ WORKSPACE_PUSH,
1535
+ WORKSPACE_READ_CAP_BYTES,
1536
+ workspaceRequest,
1537
+ workspaceRead,
1538
+ workspaceCommit,
1539
+ workspacePush,
1540
+ workspaceErrorCode,
1541
+ resumeFrom,
1542
+ afterOf,
1543
+ readKept,
1544
+ keptTranscript,
1545
+ parse,
1546
+ framesOf2,
1547
+ SCENARIO_COUNT,
1548
+ checkpoints,
1549
+ replay,
1550
+ conformance_exports
1551
+ };