@cotal-ai/connector-core 0.18.0 → 0.20.0

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.
Files changed (46) hide show
  1. package/dist/agent.d.ts.map +1 -1
  2. package/dist/agent.js +12 -2
  3. package/dist/agent.js.map +1 -1
  4. package/dist/agui-holder.d.ts +89 -0
  5. package/dist/agui-holder.d.ts.map +1 -0
  6. package/dist/agui-holder.js +135 -0
  7. package/dist/agui-holder.js.map +1 -0
  8. package/dist/agui-render.d.ts +100 -0
  9. package/dist/agui-render.d.ts.map +1 -0
  10. package/dist/agui-render.js +283 -0
  11. package/dist/agui-render.js.map +1 -0
  12. package/dist/agui-wal-path.d.ts +103 -0
  13. package/dist/agui-wal-path.d.ts.map +1 -0
  14. package/dist/agui-wal-path.js +324 -0
  15. package/dist/agui-wal-path.js.map +1 -0
  16. package/dist/agui.d.ts +727 -0
  17. package/dist/agui.d.ts.map +1 -0
  18. package/dist/agui.js +1114 -0
  19. package/dist/agui.js.map +1 -0
  20. package/dist/docs-bundle.generated.d.ts.map +1 -1
  21. package/dist/docs-bundle.generated.js +16 -9
  22. package/dist/docs-bundle.generated.js.map +1 -1
  23. package/dist/durable-source.d.ts +127 -0
  24. package/dist/durable-source.d.ts.map +1 -0
  25. package/dist/durable-source.js +219 -0
  26. package/dist/durable-source.js.map +1 -0
  27. package/dist/event-wal.d.ts +265 -0
  28. package/dist/event-wal.d.ts.map +1 -0
  29. package/dist/event-wal.js +698 -0
  30. package/dist/event-wal.js.map +1 -0
  31. package/dist/index.d.ts +5 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +5 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/launch.d.ts +78 -2
  36. package/dist/launch.d.ts.map +1 -1
  37. package/dist/launch.js +120 -0
  38. package/dist/launch.js.map +1 -1
  39. package/dist/tool-specs.d.ts +40 -2
  40. package/dist/tool-specs.d.ts.map +1 -1
  41. package/dist/tool-specs.js +51 -1
  42. package/dist/tool-specs.js.map +1 -1
  43. package/dist/tools.d.ts.map +1 -1
  44. package/dist/tools.js +5 -6
  45. package/dist/tools.js.map +1 -1
  46. package/package.json +8 -2
package/dist/agui.d.ts ADDED
@@ -0,0 +1,727 @@
1
+ /**
2
+ * The AG-UI event VOCABULARY and the Cotal frame envelope.
3
+ *
4
+ * This is the file that makes the change an abolition rather than a rename. Renaming `tr-<name>` to
5
+ * `events.<name>` while the connectors still publish `condense()` output would move glyph-prefixed
6
+ * text to a new channel and change nothing a consumer can do with it — the channel name was never
7
+ * the complaint.
8
+ *
9
+ * **The vocabulary is adopted; the SDK is not.** `@ag-ui/core` is a `devDependency`,
10
+ * pinned EXACT at `0.0.57`, and this file imports from it with `import type` ONLY. The reason is
11
+ * measured rather than stylistic: `0.0.57` declares `dependencies: { zod: "^3.22.4" }` (verified
12
+ * against the registry, not remembered), and `connector-core` is esbuild-bundled into every seeded
13
+ * connector — so a runtime dependency would ship a second zod major to every customer in order to
14
+ * validate events we construct ourselves. The conformance smoke imports the real schemas and
15
+ * validates against them; production code carries types and string literals and no zod.
16
+ *
17
+ * **Promotion trigger, conditional and NOT scheduled:** if a 0.1.x ships stable with zod moved to
18
+ * `peerDependencies`, promote to a runtime dependency and use the schemas directly. As of this
19
+ * writing `latest` is `0.0.57`, the active `canary` still carries the zod-3 runtime dep, and the
20
+ * release that moves zod to a peer sits on no dist-tag at all.
21
+ *
22
+ * ## What is here, and what is deliberately NOT
23
+ *
24
+ * Here: the event constructors, the frame envelope, the routing/validity split
25
+ * ({@link isAguiFramePart} / {@link parseAguiFrame}), the {@link AguiBrackets} stream machine, and
26
+ * the `cotal.*` `CUSTOM` table, which is empty in v1.
27
+ *
28
+ * Not here, and NAMED rather than stubbed, because a stub is a claim that the shape is known: the
29
+ * channel derivation, the `max_payload` split, and the emitter that publishes. Each of those talks
30
+ * to something outside this module, and each lands with the surface that calls it.
31
+ *
32
+ * **Nothing constructs a frame outside a smoke.** No connector emits, and this module publishes
33
+ * nothing: every export below is a pure function of its arguments. That is a statement about the
34
+ * tree, not a disclaimer — a reader deciding whether a change here can reach a customer needs it to
35
+ * be accurate, so it is maintained rather than left to rot.
36
+ */
37
+ import type { CustomEvent, ReasoningMessageContentEvent, ReasoningMessageEndEvent, ReasoningMessageStartEvent, RunErrorEvent, RunFinishedEvent, RunStartedEvent, TextMessageContentEvent, TextMessageEndEvent, TextMessageStartEvent, ToolCallArgsEvent, ToolCallEndEvent, ToolCallResultEvent, ToolCallStartEvent } from "@ag-ui/core";
38
+ export { AGUI_FRAME_KIND, AGUI_EVENT_TYPE, isAguiFramePart } from "@cotal-ai/core";
39
+ import { AGUI_FRAME_KIND } from "@cotal-ai/core";
40
+ import { type Part } from "@cotal-ai/core";
41
+ import type { DurableSource } from "./durable-source.js";
42
+ import type { EventWal } from "./event-wal.js";
43
+ /**
44
+ * The AG-UI events this plane emits — the MAPPED SUBSET, not the whole protocol.
45
+ *
46
+ * Absent by decision, each recorded so its absence is not read as an oversight:
47
+ * `*_CHUNK` (all three sources are settled observations, so we emit the START/CONTENT/END
48
+ * triple, which is the subset every consumer implements: raw CHUNK needs a client transformer);
49
+ * `STATE_*` (reserved for a later lane, and ask state left this plane entirely);
50
+ * `MESSAGES_SNAPSHOT` (dropped as a compaction anchor, because a
51
+ * windowed snapshot DELETES the prefix); `THINKING_*` (deprecated at 0.0.57 in favour of
52
+ * `REASONING_*`, which is what we emit).
53
+ */
54
+ export type AguiEvent = RunStartedEvent | RunFinishedEvent | RunErrorEvent | TextMessageStartEvent | TextMessageContentEvent | TextMessageEndEvent | ToolCallStartEvent | ToolCallArgsEvent | ToolCallEndEvent | ToolCallResultEvent | ReasoningMessageStartEvent | ReasoningMessageContentEvent | ReasoningMessageEndEvent | CustomEvent;
55
+ /**
56
+ * Cotal metadata rides ONE key on a standard event.
57
+ *
58
+ * Legal because every AG-UI event schema is `.passthrough()` — asserted by the conformance smoke
59
+ * against the real schemas rather than trusted, since this whole vehicle collapses if a future
60
+ * release tightens it.
61
+ *
62
+ * **It does NOT work everywhere.** `RunFinishedOutcomeSchema` is STRICT: it refuses unrecognized
63
+ * keys, measured. So `cotal` may ride an EVENT and never an `outcome`. Recorded here because
64
+ * "AG-UI is passthrough" is the kind of sentence that gets generalized one level too far.
65
+ */
66
+ export interface CotalMeta {
67
+ /** Where the `timestamp` came from. Absent means the source carried a real one. */
68
+ tsSource?: "arrival";
69
+ /** Set when the connector minted the `runId` rather than reading one from the harness. */
70
+ runIdSource?: "connector";
71
+ /** The provider's own message id — preserved for correlation, never spent as `messageId`. */
72
+ providerMessageId?: string;
73
+ /** The harness's stop reason, on the event that carries one. */
74
+ stopReason?: string;
75
+ /** `tool_result.is_error` — AG-UI's result event has no error field of its own. */
76
+ isError?: boolean;
77
+ /** Subagent linkage. Deliberately NOT `parentRunId`, which is retry/edit lineage. */
78
+ delegation?: {
79
+ agentId: string;
80
+ toolCallId: string;
81
+ };
82
+ /**
83
+ * WHAT BEGAN THIS RUN. Attribution, and deliberately NOT a gate on run-opening.
84
+ *
85
+ * Run-opening ("did a turn begin?") and attribution ("who began it?") are two questions, and an
86
+ * earlier revision used one provenance predicate to answer both — so a turn started by a peer
87
+ * produced no run at all, and an agent-driven session mapped to nothing. Provenance ANNOTATES;
88
+ * it does not SELECT. A run with `"channel"` attribution is still a run.
89
+ *
90
+ * `"unknown"` is never written by the mapper: an unrecognised provenance FAILS LOUD instead, so a
91
+ * future harness value produces an error rather than a confident wrong attribution. It exists for
92
+ * consumers that must render something for a producer which did not set the field.
93
+ */
94
+ turnSource?: "human" | "channel" | "notification" | "sdk" | "unknown";
95
+ /** What was cut to fit the wire, and how big it was. Set only by the sizing path. */
96
+ truncated?: {
97
+ field: string;
98
+ originalBytes: number;
99
+ };
100
+ }
101
+ /** An event carrying Cotal metadata. Kept structural so it composes with any member of the union. */
102
+ export type WithCotal<E> = E & {
103
+ cotal?: CotalMeta;
104
+ };
105
+ /**
106
+ * The `cotal.*` `CUSTOM` event table — the second and ONLY other vehicle for Cotal-specific data.
107
+ *
108
+ * **The v1 table is EMPTY, and that is the specification, not an unfinished state.** Earlier
109
+ * revisions described a two-member table holding `cotal.ask.opened` / `cotal.ask.settled`. Both
110
+ * were removed when ask state left this plane, on the operative ground that there is no consumer:
111
+ * the board answers what is owed, the plane carries what is happening. Leaving the count in the
112
+ * prose invited re-adding them "because the table has two slots", so the table ships with no slots.
113
+ *
114
+ * It exists as the GATE: adding a member is a decision that touches this declaration, rather than
115
+ * a `CUSTOM` name invented at a call site where nobody reviews the vocabulary.
116
+ */
117
+ export declare const COTAL_CUSTOM_EVENTS: readonly string[];
118
+ /** The envelope version. One frame declares the AG-UI vocabulary version it was built against. */
119
+ export declare const AGUI_PROTOCOL = "ag-ui/0.0.57";
120
+ /**
121
+ * One Cotal message = one frame.
122
+ *
123
+ * `threadId` is the native harness session and `runId` is ONE native harness turn, and nothing
124
+ * else may claim either of them. `epoch` is the writer-identity fence recovered from the WAL
125
+ * (never re-minted on restart), and `seq` is this writer's frame counter, which is what lets a
126
+ * consumer detect a gap rather than merely fail to notice one.
127
+ *
128
+ * **A frame carries no text part, by design.** That is why the renderers are a binding precondition
129
+ * on the cutover rather than a follow-up.
130
+ *
131
+ * **RE-DERIVED, because core changed underneath this sentence.** It used to end "a viewer that does
132
+ * not understand this part shows nothing, and an empty pane is indistinguishable from a
133
+ * correctly-empty one." That is now true of some surfaces and false of others, and the split is
134
+ * exactly which ones adopted core's shared `partsToText`:
135
+ *
136
+ * - **3 ADOPTED IT** — `connector-core/src/agent.ts`, `cli/src/commands/join.ts`,
137
+ * `cli/src/view/mesh-view.ts`. These now render a marker naming the kind.
138
+ * - **4 DID NOT** — `implementations/web/src/web/app.js`, `.../graph.js`,
139
+ * `examples/02-self-improving-console/harness/observer.ts`,
140
+ * `examples/04-frontier-faces/tools/studio.mjs`. The two stringify-form copies still leave a
141
+ * stray separator; the two filter-form ones still leave no trace at all.
142
+ *
143
+ * Measured on a real frame from `aguiFrame` below, placed between two text parts: the adopted
144
+ * renderer produced `"before after"` before the core change and names the kind after it. **The
145
+ * worse half of that defect was never the missing frame — it was that `"before after"` is a
146
+ * well-formed sentence with a silent hole in it, so it prompts no question at all.**
147
+ *
148
+ * **THE PRECONDITION IS UNCHANGED AND THE MARKER IS NOT A LOOPHOLE IN IT.** A named marker proves a
149
+ * frame ARRIVED; it does not display one. Cutting a connector over on the strength of it would
150
+ * still ship events nothing can render.
151
+ */
152
+ export interface AguiFrame {
153
+ kind: typeof AGUI_FRAME_KIND;
154
+ protocol: typeof AGUI_PROTOCOL;
155
+ threadId: string;
156
+ runId: string;
157
+ epoch: string;
158
+ seq: number;
159
+ events: AguiEvent[];
160
+ }
161
+ /** Raised when a frame or an event sequence violates a structural rule of the vocabulary. */
162
+ export declare class AguiVocabularyError extends Error {
163
+ constructor(message: string);
164
+ }
165
+ /**
166
+ * The bracket machine's persisted form (WAL v2).
167
+ *
168
+ * It is a plain, JSON-round-trippable record on purpose: it is written into the write-ahead log, so
169
+ * it must survive `JSON.stringify`/`parse` unchanged and must be readable by a human staring at a
170
+ * WAL trying to work out why an emitter refused something.
171
+ */
172
+ export interface BracketState {
173
+ /** The run currently open, or `undefined` when the stream is at a legal stopping point. */
174
+ run: string | undefined;
175
+ text: string[];
176
+ reasoning: string[];
177
+ tools: string[];
178
+ }
179
+ /**
180
+ * Bracketing, checked INCREMENTALLY over the event sequence — deliberately not over one frame.
181
+ *
182
+ * **A frame is not guaranteed to be self-bracketed, and a validator demanding that it be would
183
+ * forbid a split this design requires.** An oversized frame splits on event boundaries with each
184
+ * part carrying its own `seq`, so a run can legally open in one frame and close in the next. The
185
+ * unit that must balance is the WRITER'S STREAM, not the message. Feeding this machine frame after
186
+ * frame is therefore the only way to check the property that is actually claimed.
187
+ *
188
+ * What it enforces:
189
+ * - one run open at a time; nothing may be emitted outside an open run
190
+ * - `TEXT_MESSAGE_*`, `REASONING_MESSAGE_*` and `TOOL_CALL_*` open and close by their own id, and an
191
+ * id may not be opened twice while already open
192
+ * - a run may not close while any message or tool call it opened is still open
193
+ *
194
+ * The id-reuse rule is the one with a measured defect behind it: `message.id` is a PROVIDER REQUEST
195
+ * id, and over one real session 833 of 1243 assistant `message.id` values appeared in more than one
196
+ * JSONL entry. Keying identity on it would open and close the same `messageId` repeatedly, which
197
+ * the AG-UI verifier rejects and the reference reducer collapses. This machine refuses that rather
198
+ * than letting it reach a consumer.
199
+ */
200
+ export declare class AguiBrackets {
201
+ private run;
202
+ private readonly text;
203
+ private readonly reasoning;
204
+ private readonly tools;
205
+ /**
206
+ * The machine's whole state, as plain JSON — what the WAL persists so a restart does not lose it.
207
+ *
208
+ * Sorted, because this value is written to disk and compared BY A HUMAN reading two documents.
209
+ * A `Set`'s iteration order is insertion order, so two machines that are semantically identical
210
+ * would serialize differently depending on the order events happened to arrive, and a diff of two
211
+ * WALs would show a change where there is none.
212
+ */
213
+ snapshot(): BracketState;
214
+ /** Rebuild a machine from a snapshot. The inverse of {@link snapshot}, and the reason a mid-run
215
+ * restart can continue instead of refusing its first event. */
216
+ static restore(s: BracketState): AguiBrackets;
217
+ /** An independent machine at the same state — used to VALIDATE a batch without advancing the
218
+ * machine that is in step with the disk. */
219
+ clone(): AguiBrackets;
220
+ /** True while a run is open — i.e. the stream is mid-turn and not at a legal stopping point. */
221
+ get open(): boolean;
222
+ /** The run currently open, for diagnostics and for checking a frame's envelope against it. */
223
+ get runId(): string | undefined;
224
+ /** Feed one event. Throws {@link AguiVocabularyError} on the first violation. */
225
+ accept(event: AguiEvent): void;
226
+ /**
227
+ * Assert the stream is at a legal stopping point.
228
+ *
229
+ * Called at the end of a synthesized sequence and by any consumer checking a writer closed
230
+ * cleanly. NOT called per frame: mid-turn frames are legally unbalanced.
231
+ */
232
+ assertClosed(): void;
233
+ private openId;
234
+ private requireOpen;
235
+ private closeId;
236
+ }
237
+ /**
238
+ * Build a frame, validating the envelope's own fields.
239
+ *
240
+ * Bracketing is NOT checked here — see {@link AguiBrackets} for why a single frame cannot be
241
+ * required to balance. The emitter holds one `AguiBrackets` across the whole stream and feeds it as
242
+ * it builds; that is the placement that checks the property actually claimed.
243
+ */
244
+ export declare function aguiFrame(opts: {
245
+ threadId: string;
246
+ runId: string;
247
+ epoch: string;
248
+ seq: number;
249
+ events: AguiEvent[];
250
+ }): AguiFrame;
251
+ /**
252
+ * **THE CONSUMER-SIDE ENFORCEMENT POINT.** `aguiFrame` above validates on the way OUT; this pair
253
+ * validates on the way IN, and they are separate functions because they are separate trust domains.
254
+ *
255
+ * It exists because the renderers cannot enforce a contract expressed as TypeScript types.
256
+ * `implementations/web/tsconfig.json` carries `"include": ["src"]` with `"exclude": ["src/web"]`,
257
+ * and the build copies `src/web` into `dist` verbatim — so the renderer is plain JavaScript that
258
+ * `tsc` never reads. A prose contract with no enforcement point is the defect, so the contract ships
259
+ * as **a function a consumer executes**, not as a shape a consumer is trusted to have read.
260
+ *
261
+ * **THE TWO ANSWERS ARE DIFFERENT AND MUST NOT BE FUSED.** "This part is not mine" is a routing
262
+ * decision a consumer makes constantly and quietly — every non-frame part on a channel it also
263
+ * reads. "This part claims to be mine and is malformed" is a defect that must be LOUD. One function
264
+ * returning `null` for both would make a version skew look exactly like someone else's message, and
265
+ * a renderer would show an empty pane for a stream it is actively failing to parse. So:
266
+ * {@link isAguiFramePart} answers the routing question with a boolean and never throws, and
267
+ * {@link parseAguiFrame} answers the validity question and throws with the field named.
268
+ */
269
+ /**
270
+ * Validate an incoming frame, or throw {@link AguiVocabularyError} naming the field that failed.
271
+ *
272
+ * Call it only on a part {@link isAguiFramePart} accepted. Everything after that check is a defect
273
+ * rather than a routing outcome, including an unknown `protocol` — **a version skew must fail loud
274
+ * rather than render partially**, because a consumer that drops the fields it does not recognise
275
+ * shows a confidently incomplete transcript, which is worse than showing nothing.
276
+ *
277
+ * It deliberately does NOT check bracketing. A frame is not guaranteed to be self-bracketed — an
278
+ * oversized frame splits on event boundaries — so the unit that must balance is the writer's stream,
279
+ * and {@link AguiBrackets} is the machine for that, fed frame after frame. A validator demanding a
280
+ * frame balance on its own would forbid a split the plan mandates.
281
+ */
282
+ export declare function parseAguiFrame(part: unknown): AguiFrame;
283
+ /** `RUN_STARTED` — `threadId` is the native session, `runId` one native harness turn. */
284
+ export declare function runStarted(o: {
285
+ threadId: string;
286
+ runId: string;
287
+ timestamp: number;
288
+ cotal?: CotalMeta;
289
+ }): WithCotal<RunStartedEvent>;
290
+ /**
291
+ * One entry of the `interrupt` outcome.
292
+ *
293
+ * Both fields are REQUIRED strings, measured against the real schema rather than read off its
294
+ * types: an entry missing either is refused naming `outcome.interrupts.<i>.<field>`. Extra keys on
295
+ * an entry are STRIPPED rather than refused, which is why nothing here polices them.
296
+ */
297
+ export interface AguiInterrupt {
298
+ id: string;
299
+ reason: string;
300
+ }
301
+ /**
302
+ * `RUN_FINISHED`.
303
+ *
304
+ * `outcome` is OPTIONAL — measured against the real schema, which accepts a `RUN_FINISHED` carrying
305
+ * none. That matters because the Claude `Stop` hook reports that a turn ended and nothing more, so
306
+ * manufacturing a `success` outcome would be asserting something the source never said. When an
307
+ * outcome IS supplied its discriminator key is `type`, not `status` (measured: the schema refuses
308
+ * `{status:"success"}` naming `outcome.type`), and the object is STRICT.
309
+ *
310
+ * The `interrupt` outcome is REPRESENTABLE and VALIDATED here, and UNSPENT: no source on this plane
311
+ * constructs one, because a harness-native park is what would justify it and none of the three
312
+ * sources reports one. It is checked anyway because the arm exists, and an arm that builds an event
313
+ * the schema refuses is worse than an arm that does not exist. Its first draft took `unknown[]` and
314
+ * passed it through, so `[]` and `[{}]` both produced a refused event with nothing looking.
315
+ */
316
+ export declare function runFinished(o: {
317
+ threadId: string;
318
+ runId: string;
319
+ timestamp: number;
320
+ outcome?: {
321
+ type: "success";
322
+ } | {
323
+ type: "interrupt";
324
+ interrupts: AguiInterrupt[];
325
+ };
326
+ cotal?: CotalMeta;
327
+ }): WithCotal<RunFinishedEvent>;
328
+ /** `RUN_ERROR` — carries `message` and an optional `code`, and NO `runId` of its own. */
329
+ export declare function runError(o: {
330
+ message: string;
331
+ timestamp: number;
332
+ code?: string;
333
+ cotal?: CotalMeta;
334
+ }): WithCotal<RunErrorEvent>;
335
+ /**
336
+ * `TEXT_MESSAGE_START`.
337
+ *
338
+ * `messageId` must be unique per OBSERVATION, not per provider message. The specified form is
339
+ * `${entry.uuid}#${blockIndex}` — the provider's own id is preserved as `cotal.providerMessageId`
340
+ * and never spent here, because it does not have the cardinality the field needs.
341
+ */
342
+ export declare function textMessageStart(o: {
343
+ messageId: string;
344
+ timestamp: number;
345
+ role?: "assistant" | "user";
346
+ cotal?: CotalMeta;
347
+ }): WithCotal<TextMessageStartEvent>;
348
+ /** `TEXT_MESSAGE_CONTENT` — one settled observation, never a token-level delta. */
349
+ export declare function textMessageContent(o: {
350
+ messageId: string;
351
+ delta: string;
352
+ timestamp: number;
353
+ cotal?: CotalMeta;
354
+ }): WithCotal<TextMessageContentEvent>;
355
+ /** `TEXT_MESSAGE_END`. */
356
+ export declare function textMessageEnd(o: {
357
+ messageId: string;
358
+ timestamp: number;
359
+ cotal?: CotalMeta;
360
+ }): WithCotal<TextMessageEndEvent>;
361
+ /** `TOOL_CALL_START` — `toolCallId` is the harness's own id, carried rather than re-minted. */
362
+ export declare function toolCallStart(o: {
363
+ toolCallId: string;
364
+ toolCallName: string;
365
+ timestamp: number;
366
+ parentMessageId?: string;
367
+ cotal?: CotalMeta;
368
+ }): WithCotal<ToolCallStartEvent>;
369
+ /**
370
+ * `TOOL_CALL_ARGS` — `delta` is the FULL `JSON.stringify(input)`.
371
+ *
372
+ * This is where `tr-`'s `salient()` died: it guessed which argument mattered and dropped the rest,
373
+ * so a reader could not reconstruct what the agent actually did. The whole input goes on the wire,
374
+ * and if it physically cannot fit, the sizing path truncates it with a label rather than silently.
375
+ */
376
+ export declare function toolCallArgs(o: {
377
+ toolCallId: string;
378
+ delta: string;
379
+ timestamp: number;
380
+ cotal?: CotalMeta;
381
+ }): WithCotal<ToolCallArgsEvent>;
382
+ /** `TOOL_CALL_END`. */
383
+ export declare function toolCallEnd(o: {
384
+ toolCallId: string;
385
+ timestamp: number;
386
+ cotal?: CotalMeta;
387
+ }): WithCotal<ToolCallEndEvent>;
388
+ /**
389
+ * `TOOL_CALL_RESULT`.
390
+ *
391
+ * **`messageId` is REQUIRED by the real schema** — measured; a result without one is refused. The
392
+ * plan's per-connector mapping table names only `toolCallId` for this row, so the identity of the
393
+ * result MESSAGE is unstated there and is raised as a plan gap rather than guessed at a call site.
394
+ * The parameter is required here so a mapper cannot omit it and discover the refusal downstream.
395
+ *
396
+ * `is_error` has no AG-UI field and rides `cotal.isError`.
397
+ */
398
+ export declare function toolCallResult(o: {
399
+ messageId: string;
400
+ toolCallId: string;
401
+ content: string;
402
+ timestamp: number;
403
+ cotal?: CotalMeta;
404
+ }): WithCotal<ToolCallResultEvent>;
405
+ /**
406
+ * `REASONING_MESSAGE_START` — off by default; the signature is never emitted, ever.
407
+ *
408
+ * **`role` is a REQUIRED literal `"reasoning"`**, unlike `TEXT_MESSAGE_START` where `role` is
409
+ * optional. Measured: the first version of this constructor omitted it and the real schema refused
410
+ * the event. It is set here rather than exposed as a parameter, because there is exactly one legal
411
+ * value and a caller-supplied one could only ever be wrong.
412
+ */
413
+ export declare function reasoningMessageStart(o: {
414
+ messageId: string;
415
+ timestamp: number;
416
+ cotal?: CotalMeta;
417
+ }): WithCotal<ReasoningMessageStartEvent>;
418
+ /** `REASONING_MESSAGE_CONTENT`. */
419
+ export declare function reasoningMessageContent(o: {
420
+ messageId: string;
421
+ delta: string;
422
+ timestamp: number;
423
+ cotal?: CotalMeta;
424
+ }): WithCotal<ReasoningMessageContentEvent>;
425
+ /** `REASONING_MESSAGE_END`. */
426
+ export declare function reasoningMessageEnd(o: {
427
+ messageId: string;
428
+ timestamp: number;
429
+ cotal?: CotalMeta;
430
+ }): WithCotal<ReasoningMessageEndEvent>;
431
+ /**
432
+ * Split `events` into as many frames as the wire requires, truncating only what physically cannot
433
+ * cross it, and LABELLING every truncation.
434
+ *
435
+ * **THIS IS THE PREVIEW PLANE'S SPLITTER, AND IT HAS NO DURABLE-PLANE CALLER BY DESIGN.**
436
+ * Read that as a boundary, not as an oversight: the durable emitter packs with {@link packUnits} at
437
+ * SOURCE-RECORD boundaries and refuses an oversized unit, because one durable emit unit must be one
438
+ * frame carrying a cursor that resumes after it, and a frame ending mid-record has no cursor it can
439
+ * honestly store. This event-boundary split and its labelled truncation were specified before the
440
+ * durable plane had a cursor contract; where the two disagree, the durable plane's rule wins. The
441
+ * PREVIEW plane has no resume obligation at all, which is exactly where truncate-and-label is the
442
+ * right answer and why this machinery is worth keeping.
443
+ *
444
+ * **Calling this from the durable emitter would be a silent-loss bug**, not a performance choice —
445
+ * so if you are here looking for the packer, you want `packUnits`. And it is marked rather than
446
+ * deleted for the reason one module over already demonstrated: `assertExpectationSemantics()` sat
447
+ * with zero production callers looking exactly like live code, and unreachable code that looks live
448
+ * is a hazard whichever direction the next reader resolves it in.
449
+ *
450
+ * **Say the uncomfortable thing:** this is a content truncation, which is one of the
451
+ * sins `tr-` is being abolished for. The difference is not that we are gentler about it. `tr-` cut
452
+ * *every* result at 700 characters, silently and unconditionally, as a design choice; this cuts only
453
+ * what cannot physically be sent, three orders of magnitude higher, and records what it cut and how
454
+ * big it was. If routine results start tripping the ceiling the honest response is a
455
+ * content-addressed side channel, not a quieter limit.
456
+ *
457
+ * **Splitting happens on EVENT boundaries**, each part carrying its own `seq`, so a run may legally
458
+ * open in one frame and close in the next. That is why {@link AguiBrackets} checks the writer's
459
+ * stream and not the frame — a per-frame balance check would forbid the split this function
460
+ * performs.
461
+ *
462
+ * **`seq` is measured, not assumed.** Each candidate is measured at the `seq` it will actually carry,
463
+ * because `seq` is a header-adjacent value in the encoded body: sizing at 9 and publishing at 10 is
464
+ * one byte, and a frame one byte over the ceiling is refused. The same reason `encodedSize` takes
465
+ * `expectedLastSubjectSeq` as a parameter rather than sizing at zero.
466
+ *
467
+ * @param measure the EXACT encoded size of a candidate, headers included — `CotalEndpoint.encodedSize`
468
+ * in production. Never re-implement it here.
469
+ * @param limit the broker's `max_payload`.
470
+ * @throws {AguiVocabularyError} if a single event cannot be made to fit even fully truncated, or
471
+ * carries no truncatable field. Failing loud is required: the alternative is looping forever or
472
+ * dropping the event, and a dropped event on this plane is the silent loss the plane exists to
473
+ * make impossible.
474
+ */
475
+ export declare function splitFrames(opts: {
476
+ threadId: string;
477
+ runId: string;
478
+ epoch: string;
479
+ /** The `seq` the FIRST emitted frame carries; each subsequent part takes the next. */
480
+ firstSeq: number;
481
+ events: AguiEvent[];
482
+ measure: (frame: AguiFrame) => number;
483
+ limit: number;
484
+ }): AguiFrame[];
485
+ /**
486
+ * The endpoint surface the emitter needs, declared STRUCTURALLY rather than as `CotalEndpoint`.
487
+ *
488
+ * Not for testability as an end in itself — for a specific one. A cell that needs a live broker to
489
+ * exercise the duplicate-ack halt cannot be written at all before a broker exists, and a cell that
490
+ * re-implements `encodedSize` is measuring a copy. This interface is the exact set of methods the
491
+ * emitter calls, so a cell substitutes an instrument and the production path substitutes the real
492
+ * endpoint, and neither one is a re-implementation of the other.
493
+ */
494
+ export interface EmitterEndpoint {
495
+ readonly principal: {
496
+ owner: string;
497
+ actor: string;
498
+ };
499
+ readonly actorIsEphemeral: boolean;
500
+ /** The broker's live `max_payload`. Throws when not connected — never guesses a default. */
501
+ readonly maxPayload: number;
502
+ /** The single-replica preflight. The emitter calls this at startup, before anything can publish. */
503
+ assertExpectationSemantics(): Promise<void>;
504
+ encodedSize(o: {
505
+ channel: string;
506
+ parts: Part[];
507
+ id: string;
508
+ expectedLastSubjectSeq: number;
509
+ }): number;
510
+ multicastExpecting(o: {
511
+ channel: string;
512
+ parts: Part[];
513
+ id: string;
514
+ expectedLastSubjectSeq: number;
515
+ }): Promise<{
516
+ ack: {
517
+ seq: number;
518
+ duplicate: boolean;
519
+ };
520
+ }>;
521
+ }
522
+ /**
523
+ * One source record's worth of events, with the cursor that resumes AFTER that record.
524
+ *
525
+ * One durable emit unit is one frame: the emitter splits only at source-observation boundaries that
526
+ * are independently reconstructable from the durable source. A frame therefore ends where a record
527
+ * ends, and carries that record's cursor, so folding it means exactly "every record here is
528
+ * consumed".
529
+ */
530
+ export interface EmitUnit {
531
+ /** The run these events belong to. A frame's envelope names ONE run, so units are never
532
+ * mixed across runs in one frame. */
533
+ runId: string;
534
+ events: AguiEvent[];
535
+ cursor: string;
536
+ }
537
+ /** What the mapper returns for one source record: its run and its events, or `null` for a record
538
+ * this plane deliberately drops, and it drops many. `null` is NOT an error: a deliberate drop and
539
+ * a failed map are kept apart, because conflating them turns a parser bug into skipped history. */
540
+ export type RecordMapper<T> = (record: T) => {
541
+ runId: string;
542
+ events: AguiEvent[];
543
+ } | null;
544
+ /**
545
+ * The emitter has stopped and will not publish again without operator action.
546
+ *
547
+ * Halting is a SUCCESS of this design, not a failure of it: every halt below is a case where the
548
+ * alternative is to report success for a message that was not stored, or to fold an ack for a body
549
+ * we did not write. A halt is loud, bounded and recoverable by a human; the alternative is silent
550
+ * and permanent.
551
+ */
552
+ /**
553
+ * A bracket violation that is OURS, not the writer's: the machine that tracks open runs and messages
554
+ * was lost across a process restart.
555
+ *
556
+ * **This exists because two halts that both say "unbalanced" prove nothing about which produced
557
+ * one.** The WAL persists `epoch`, `frontier` and the pending frame, and NOT the set of open
558
+ * runs and messages, so a process that dies mid-run restarts with an empty {@link AguiBrackets},
559
+ * resumes from `sourceCursor` at events whose `RUN_STARTED` was already published, and refuses the
560
+ * first of them. Without this class the operator sees "nothing may be emitted outside an open run"
561
+ * and files a bug against a writer that did nothing wrong.
562
+ *
563
+ * It is deliberately a SUBCLASS: every existing catch of {@link AguiVocabularyError} still catches
564
+ * it, and only code that wants to tell the two apart has to know it exists.
565
+ */
566
+ export declare class AguiBracketStateLost extends AguiVocabularyError {
567
+ readonly cause: Error;
568
+ constructor(message: string, cause: Error);
569
+ }
570
+ export declare class AguiEmitterHalted extends Error {
571
+ readonly reason: "duplicate-ack" | "cas-loss";
572
+ constructor(reason: "duplicate-ack" | "cas-loss", message: string);
573
+ }
574
+ /**
575
+ * Pack units into frames, splitting ONLY at unit boundaries and never inside one.
576
+ *
577
+ * Deliberately NOT {@link splitFrames}, and the difference is the durable plane's one-unit-one-frame
578
+ * rule. `splitFrames` splits at EVENT boundaries, which is the right answer for a frame considered
579
+ * on its own, but a frame that ends mid-record has no cursor it can honestly store: the only value
580
+ * available says the whole record was consumed, and folding that after a crash skips the rest of the
581
+ * record's events with no `seq` gap for a consumer to notice.
582
+ *
583
+ * **So the event-boundary split and the one-unit-one-frame rule are in tension, and this resolves it
584
+ * in the direction the durable plane requires: a single unit that does not fit FAILS LOUD rather
585
+ * than being truncated at a frame boundary.** That leaves `splitFrames`'s truncation path with no
586
+ * caller on the durable plane, which is reported as a design conflict rather than decided here.
587
+ *
588
+ * @throws {AguiVocabularyError} when one unit cannot fit in a frame alone.
589
+ */
590
+ export declare function packUnits(opts: {
591
+ threadId: string;
592
+ epoch: string;
593
+ firstSeq: number;
594
+ units: readonly EmitUnit[];
595
+ measure: (frame: AguiFrame) => number;
596
+ limit: number;
597
+ }): {
598
+ frame: AguiFrame;
599
+ cursor: string;
600
+ }[];
601
+ /**
602
+ * The event emitter: one per principal, one thread at a time.
603
+ *
604
+ * **BRACKET STATE SURVIVES A RESTART, AND THIS PARAGRAPH USED TO SAY THE OPPOSITE.** It described a
605
+ * declared gap — an emitter coming back with an empty machine, resuming at events whose
606
+ * `RUN_STARTED` had already been published, and refusing the first of them — long after the WAL
607
+ * started persisting the machine. The words were true when they were written and stayed on the page
608
+ * through the change that falsified them, which is the failure mode a class header is worst at
609
+ * showing: it is the first thing a cutover author reads about recovery, and it was telling them to
610
+ * expect a halt the code no longer produces.
611
+ *
612
+ * What actually happens: {@link AguiBrackets} is a property of the WRITER'S STREAM across frames, so
613
+ * the WAL freezes the machine's state WITH each pending frame and promotes it on fold. A restart
614
+ * therefore reopens knowing exactly which run, messages and tool calls were open at the last FOLDED
615
+ * position, and {@link AguiBrackets.restore} continues from there rather than from empty.
616
+ *
617
+ * **The lost-state path still exists, and it is now the narrow case it should always have been:** a
618
+ * document that CANNOT SAY what was open. That is a WAL migrated from v1, which recorded no bracket
619
+ * state at all, and it loads as `null` rather than as an empty machine precisely so the difference
620
+ * stays visible. Only there does the emitter start empty, resume into an already-open run, and
621
+ * refuse the first event with {@link AguiBracketStateLost} — a halt rather than a loss, which is the
622
+ * safe direction, and diagnosed by name rather than surfacing as an anonymous protocol violation.
623
+ */
624
+ export declare class AguiEmitter<T> {
625
+ private readonly ep;
626
+ private readonly wal;
627
+ private readonly source;
628
+ private readonly map;
629
+ /** Derived from the endpoint's OWN principal, never from a config name or the launch env. */
630
+ readonly channel: string;
631
+ readonly threadId: string;
632
+ /**
633
+ * The bracket machine AT THE FOLDED POSITION — deliberately not "wherever validation got to".
634
+ *
635
+ * It advances one frame at a time, immediately before that frame's `beginSend`, so the state
636
+ * frozen with a pending frame is the state that belongs to it. A machine advanced by the whole
637
+ * batch up front would freeze a state describing events that had not been sent.
638
+ */
639
+ private brackets;
640
+ private halted;
641
+ /** True once THIS process has fed an event through the bracket machine. It is the half of the
642
+ * restart diagnosis that keeps a genuine mid-stream violation from being blamed on a restart. */
643
+ private fedAnyEvent;
644
+ private constructor();
645
+ /**
646
+ * Start an emitter: resolve the channel, run the single-replica preflight, and settle any pending
647
+ * frame.
648
+ *
649
+ * **THIS IS THAT PREFLIGHT'S PRODUCTION CALL SITE, AND UNTIL THIS FUNCTION EXISTED THERE WAS
650
+ * NONE.** `CotalEndpoint.assertExpectationSemantics()` had zero production callers: it was a
651
+ * check that shipped, was covered by its own suite, and never ran outside one. That is why it is
652
+ * called HERE, before recovery and therefore before any publish — a serialized append on an
653
+ * unverified stream is the exact case it exists to prevent, and doing it after recovery would
654
+ * leave the one publish that matters most, the re-publish of a frozen frame, outside the guard.
655
+ */
656
+ static start<T>(opts: {
657
+ endpoint: EmitterEndpoint;
658
+ /** Already open, so the caller owns `space`, the WAL path, and the `subjectMayExist` judgement
659
+ * — none of which the emitter can make honestly on the caller's behalf. */
660
+ wal: EventWal;
661
+ source: DurableSource<T>;
662
+ map: RecordMapper<T>;
663
+ }): Promise<AguiEmitter<T>>;
664
+ /** True once the emitter has stopped for good. */
665
+ get stopped(): boolean;
666
+ /**
667
+ * Boot recovery, branching on the WAL's tag.
668
+ *
669
+ * `acked` NEVER republishes: the frame landed and we know it, so the only remaining work is to
670
+ * fold. `sent_unacked` is the genuinely uncertain case and republishes with the SAME frozen `id`
671
+ * and `E` — never the current tip, because re-deriving either is what turns an uncertain publish
672
+ * into a second, different message.
673
+ */
674
+ private recover;
675
+ /**
676
+ * Read forward, map, pack, and publish. Returns what it did, so a caller can distinguish "nothing
677
+ * to do" from "did work" without inspecting the WAL.
678
+ */
679
+ pump(): Promise<{
680
+ frames: number;
681
+ events: number;
682
+ }>;
683
+ /** Measure a candidate frame EXACTLY as the wire will, at an upper bound over id and expectation. */
684
+ private measure;
685
+ /** Transition 1 then the first network attempt. */
686
+ private publish;
687
+ /**
688
+ * One publish attempt — first or retry — with the FROZEN id and the FROZEN `E`. Never the tip.
689
+ *
690
+ * The three outcomes are not symmetric and the asymmetry is the design:
691
+ * - `!duplicate` → transition 2 then 3. Success becomes durable before the frontier moves.
692
+ * - `duplicate` → HALT. On a first attempt it means a body WE DID NOT WRITE holds our id, and
693
+ * folding its `ackSeq` would advance the frontier and the source cursor past events that were
694
+ * never published. On a retry it cannot happen on a single-replica stream at all, because such a
695
+ * stream evaluates the expectation before the dedup cache, so observing it proves the stream is
696
+ * not single-replica. Both are
697
+ * fail-loud, and neither is a case where guessing is better than stopping.
698
+ * - CAS loss → HALT. Someone else moved the tip on a subject only this principal may write, or
699
+ * the subject was purged. Uncertainty plus a moved tip is exactly what must not be guessed at.
700
+ *
701
+ * A NETWORK error is deliberately none of these: it leaves `pending` as `sent_unacked`, which is
702
+ * the state that means "we do not know", and the next boot retries the same frozen frame.
703
+ */
704
+ private attempt;
705
+ /**
706
+ * Decide whether a bracket refusal is the WRITER's fault or OURS, and say which.
707
+ *
708
+ * Ours iff ALL THREE hold, and each is load-bearing:
709
+ * - this process has fed NO event through the machine yet, so the machine cannot have been put
710
+ * into a bad state by anything we did in this run; and
711
+ * - the frontier is non-virgin, so frames — and therefore possibly an open `RUN_STARTED` — were
712
+ * published by a PREVIOUS process; and
713
+ * - the WAL cannot say what was open. Since v2 the machine is PERSISTED, so an ordinary restart
714
+ * restores it and never reaches here at all; `null` means the document was migrated from v1 and
715
+ * genuinely never recorded the state. Without this condition the diagnosis would survive as a
716
+ * permanent excuse for a case the migration fixed.
717
+ *
718
+ * Drop the first condition and a genuine mid-stream violation by the writer gets blamed on a
719
+ * restart that happened an hour ago. Drop the second and a violation on a virgin thread, where
720
+ * nothing was ever published and nothing could have been lost, gets blamed on a restart that never
721
+ * happened. Each condition alone produces a confident, wrong diagnosis — which is worse than the
722
+ * undiagnosed error it replaced, because a named cause stops the search.
723
+ */
724
+ private diagnoseBracket;
725
+ private halt;
726
+ }
727
+ //# sourceMappingURL=agui.d.ts.map