@ably/ai-transport 0.0.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.
Files changed (118) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +426 -0
  3. package/dist/ably-ai-transport.js +1388 -0
  4. package/dist/ably-ai-transport.js.map +1 -0
  5. package/dist/ably-ai-transport.umd.cjs +2 -0
  6. package/dist/ably-ai-transport.umd.cjs.map +1 -0
  7. package/dist/constants.d.ts +50 -0
  8. package/dist/core/codec/decoder.d.ts +62 -0
  9. package/dist/core/codec/encoder.d.ts +56 -0
  10. package/dist/core/codec/index.d.ts +8 -0
  11. package/dist/core/codec/lifecycle-tracker.d.ts +74 -0
  12. package/dist/core/codec/types.d.ts +188 -0
  13. package/dist/core/transport/client-transport.d.ts +10 -0
  14. package/dist/core/transport/conversation-tree.d.ts +9 -0
  15. package/dist/core/transport/decode-history.d.ts +41 -0
  16. package/dist/core/transport/headers.d.ts +26 -0
  17. package/dist/core/transport/index.d.ts +4 -0
  18. package/dist/core/transport/pipe-stream.d.ts +16 -0
  19. package/dist/core/transport/server-transport.d.ts +7 -0
  20. package/dist/core/transport/stream-router.d.ts +19 -0
  21. package/dist/core/transport/turn-manager.d.ts +34 -0
  22. package/dist/core/transport/types.d.ts +407 -0
  23. package/dist/errors.d.ts +46 -0
  24. package/dist/event-emitter.d.ts +65 -0
  25. package/dist/index.d.ts +11 -0
  26. package/dist/logger.d.ts +103 -0
  27. package/dist/react/ably-ai-transport-react.js +823 -0
  28. package/dist/react/ably-ai-transport-react.js.map +1 -0
  29. package/dist/react/ably-ai-transport-react.umd.cjs +2 -0
  30. package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -0
  31. package/dist/react/index.d.ts +11 -0
  32. package/dist/react/use-ably-messages.d.ts +18 -0
  33. package/dist/react/use-active-turns.d.ts +8 -0
  34. package/dist/react/use-client-transport.d.ts +7 -0
  35. package/dist/react/use-conversation-tree.d.ts +20 -0
  36. package/dist/react/use-edit.d.ts +7 -0
  37. package/dist/react/use-history.d.ts +19 -0
  38. package/dist/react/use-messages.d.ts +7 -0
  39. package/dist/react/use-regenerate.d.ts +7 -0
  40. package/dist/react/use-send.d.ts +7 -0
  41. package/dist/utils.d.ts +127 -0
  42. package/dist/vercel/ably-ai-transport-vercel.js +2331 -0
  43. package/dist/vercel/ably-ai-transport-vercel.js.map +1 -0
  44. package/dist/vercel/ably-ai-transport-vercel.umd.cjs +2 -0
  45. package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -0
  46. package/dist/vercel/codec/accumulator.d.ts +21 -0
  47. package/dist/vercel/codec/decoder.d.ts +22 -0
  48. package/dist/vercel/codec/encoder.d.ts +41 -0
  49. package/dist/vercel/codec/index.d.ts +22 -0
  50. package/dist/vercel/index.d.ts +3 -0
  51. package/dist/vercel/react/ably-ai-transport-vercel-react.js +2082 -0
  52. package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -0
  53. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +2 -0
  54. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -0
  55. package/dist/vercel/react/index.d.ts +3 -0
  56. package/dist/vercel/react/use-chat-transport.d.ts +29 -0
  57. package/dist/vercel/react/use-message-sync.d.ts +19 -0
  58. package/dist/vercel/transport/chat-transport.d.ts +118 -0
  59. package/dist/vercel/transport/index.d.ts +36 -0
  60. package/package.json +123 -0
  61. package/react/README.md +3 -0
  62. package/react/index.d.ts +1 -0
  63. package/react/index.js +1 -0
  64. package/react/index.umd.cjs +1 -0
  65. package/src/constants.ts +98 -0
  66. package/src/core/codec/decoder.ts +402 -0
  67. package/src/core/codec/encoder.ts +470 -0
  68. package/src/core/codec/index.ts +28 -0
  69. package/src/core/codec/lifecycle-tracker.ts +140 -0
  70. package/src/core/codec/types.ts +249 -0
  71. package/src/core/transport/client-transport.ts +959 -0
  72. package/src/core/transport/conversation-tree.ts +434 -0
  73. package/src/core/transport/decode-history.ts +337 -0
  74. package/src/core/transport/headers.ts +46 -0
  75. package/src/core/transport/index.ts +34 -0
  76. package/src/core/transport/pipe-stream.ts +95 -0
  77. package/src/core/transport/server-transport.ts +458 -0
  78. package/src/core/transport/stream-router.ts +118 -0
  79. package/src/core/transport/turn-manager.ts +147 -0
  80. package/src/core/transport/types.ts +533 -0
  81. package/src/errors.ts +58 -0
  82. package/src/event-emitter.ts +103 -0
  83. package/src/index.ts +89 -0
  84. package/src/logger.ts +241 -0
  85. package/src/react/index.ts +11 -0
  86. package/src/react/use-ably-messages.ts +37 -0
  87. package/src/react/use-active-turns.ts +61 -0
  88. package/src/react/use-client-transport.ts +37 -0
  89. package/src/react/use-conversation-tree.ts +71 -0
  90. package/src/react/use-edit.ts +24 -0
  91. package/src/react/use-history.ts +111 -0
  92. package/src/react/use-messages.ts +32 -0
  93. package/src/react/use-regenerate.ts +24 -0
  94. package/src/react/use-send.ts +25 -0
  95. package/src/react/vite.config.ts +32 -0
  96. package/src/tsconfig.json +25 -0
  97. package/src/utils.ts +230 -0
  98. package/src/vercel/codec/accumulator.ts +603 -0
  99. package/src/vercel/codec/decoder.ts +615 -0
  100. package/src/vercel/codec/encoder.ts +396 -0
  101. package/src/vercel/codec/index.ts +37 -0
  102. package/src/vercel/index.ts +12 -0
  103. package/src/vercel/react/index.ts +4 -0
  104. package/src/vercel/react/use-chat-transport.ts +60 -0
  105. package/src/vercel/react/use-message-sync.ts +34 -0
  106. package/src/vercel/react/vite.config.ts +33 -0
  107. package/src/vercel/transport/chat-transport.ts +278 -0
  108. package/src/vercel/transport/index.ts +56 -0
  109. package/src/vercel/vite.config.ts +33 -0
  110. package/src/vite.config.ts +31 -0
  111. package/vercel/README.md +3 -0
  112. package/vercel/index.d.ts +1 -0
  113. package/vercel/index.js +1 -0
  114. package/vercel/index.umd.cjs +1 -0
  115. package/vercel/react/README.md +3 -0
  116. package/vercel/react/index.d.ts +1 -0
  117. package/vercel/react/index.js +1 -0
  118. package/vercel/react/index.umd.cjs +1 -0
@@ -0,0 +1,62 @@
1
+ import { Logger } from '../../logger.js';
2
+ import { DecoderOutput, MessagePayload, StreamTrackerState } from './types.js';
3
+ /**
4
+ * Decoder core — action dispatch and serial tracking machinery.
5
+ *
6
+ * Handles the Ably message action patterns (create, append, update, delete)
7
+ * and delegates to domain-specific hooks for event building and discrete
8
+ * event decoding.
9
+ *
10
+ * Domain decoders call `createDecoderCore(hooks, options)` and provide hooks
11
+ * for stream classification, event building, and discrete decoding.
12
+ */
13
+ import type * as Ably from 'ably';
14
+ /**
15
+ * Wrap a domain event as a single-element decoder output array.
16
+ * @param event - The domain event to wrap.
17
+ * @returns A single-element array containing the event as a decoder output.
18
+ */
19
+ export declare const eventOutput: <TEvent, TMessage>(event: TEvent) => DecoderOutput<TEvent, TMessage>[];
20
+ /** Options for creating a decoder core. */
21
+ export interface DecoderCoreOptions {
22
+ /** Called when a tracked stream is replaced (non-prefix update). Receives the tracker with updated state. */
23
+ onStreamUpdate?: (tracker: StreamTrackerState) => void;
24
+ /** Called when a message is deleted. Receives the serial and tracker (if one exists). */
25
+ onStreamDelete?: (serial: string, tracker: StreamTrackerState | undefined) => void;
26
+ /** Logger instance for diagnostic output. */
27
+ logger?: Logger;
28
+ }
29
+ /** Hooks that a domain codec provides to the decoder core for stream classification and event building. */
30
+ export interface DecoderCoreHooks<TEvent, TMessage> {
31
+ /**
32
+ * Build domain events emitted when a new stream starts. May return multiple
33
+ * events (e.g. a start event and a start-step event).
34
+ */
35
+ buildStartEvents(tracker: StreamTrackerState): DecoderOutput<TEvent, TMessage>[];
36
+ /** Build domain events for a text delta received on a stream. */
37
+ buildDeltaEvents(tracker: StreamTrackerState, delta: string): DecoderOutput<TEvent, TMessage>[];
38
+ /**
39
+ * Build domain events emitted when a stream finishes (x-ably-status:finished).
40
+ * Not called for aborted streams. The closing headers may differ from
41
+ * tracker.headers if the closing append carried updated headers.
42
+ */
43
+ buildEndEvents(tracker: StreamTrackerState, closingHeaders: Record<string, string>): DecoderOutput<TEvent, TMessage>[];
44
+ /**
45
+ * Decode a discrete message (message.create where x-ably-stream is "false",
46
+ * or a non-streamable first-contact update). Handles user messages, lifecycle
47
+ * events, tool lifecycle, data-*, etc.
48
+ */
49
+ decodeDiscrete(input: MessagePayload): DecoderOutput<TEvent, TMessage>[];
50
+ }
51
+ /** The decoder core returned by {@link createDecoderCore}. */
52
+ export interface DecoderCore<TEvent, TMessage> {
53
+ /** Decode a single Ably message into zero or more domain outputs. */
54
+ decode(message: Ably.InboundMessage): DecoderOutput<TEvent, TMessage>[];
55
+ }
56
+ /**
57
+ * Create a decoder core with the given domain hooks.
58
+ * @param hooks - Domain-specific hooks for stream classification, event building, and discrete decoding.
59
+ * @param options - Decoder configuration (callbacks, logger).
60
+ * @returns A new {@link DecoderCore} instance.
61
+ */
62
+ export declare const createDecoderCore: <TEvent, TMessage>(hooks: DecoderCoreHooks<TEvent, TMessage>, options?: DecoderCoreOptions) => DecoderCore<TEvent, TMessage>;
@@ -0,0 +1,56 @@
1
+ import { Logger } from '../../logger.js';
2
+ import { ChannelWriter, EncoderOptions, MessagePayload, StreamPayload, WriteOptions } from './types.js';
3
+ /**
4
+ * Encoder core — message append lifecycle machinery.
5
+ *
6
+ * Provides Ably primitives (publish, append, close, abort, flush) that
7
+ * domain-specific encoders wire their event types to.
8
+ *
9
+ * Domain encoders call `createEncoderCore(writer, options)` and use the
10
+ * returned core to map domain events to Ably operations without
11
+ * reimplementing the message append lifecycle.
12
+ */
13
+ import * as Ably from 'ably';
14
+ /** Options for creating an encoder core. Extends {@link EncoderOptions} with a logger. */
15
+ export interface EncoderCoreOptions extends EncoderOptions {
16
+ /** Logger instance for diagnostic output. */
17
+ logger?: Logger;
18
+ }
19
+ /** The core encoder primitives that domain codec encoders delegate to. */
20
+ export interface EncoderCore {
21
+ /** Publish a single discrete (non-streaming) message described by a payload. */
22
+ publishDiscrete(payload: MessagePayload, opts?: WriteOptions): Promise<Ably.PublishResult>;
23
+ /** Publish multiple discrete messages atomically in a single channel publish. */
24
+ publishDiscreteBatch(payloads: MessagePayload[], opts?: WriteOptions): Promise<Ably.PublishResult>;
25
+ /** Start a streamed message with x-ably-status:streaming. */
26
+ startStream(streamId: string, payload: StreamPayload, opts?: WriteOptions): Promise<void>;
27
+ /**
28
+ * Append data to an in-flight streamed message. Fire-and-forget: errors are
29
+ * collected internally and surfaced by {@link closeStream} or {@link close}.
30
+ */
31
+ appendStream(streamId: string, data: string): void;
32
+ /**
33
+ * Close a streamed message with x-ably-status:finished. Flushes all pending
34
+ * appends for recovery before returning. Repeats persistent and payload headers.
35
+ */
36
+ closeStream(streamId: string, payload: StreamPayload): Promise<void>;
37
+ /**
38
+ * Abort a single in-progress stream (x-ably-status:aborted) and flush all
39
+ * pending appends for recovery before returning.
40
+ */
41
+ abortStream(streamId: string, opts?: WriteOptions): Promise<void>;
42
+ /**
43
+ * Abort all in-progress streams (x-ably-status:aborted) and flush all
44
+ * pending appends for recovery before returning.
45
+ */
46
+ abortAllStreams(opts?: WriteOptions): Promise<void>;
47
+ /** Flush + clear trackers. Idempotent. */
48
+ close(): Promise<void>;
49
+ }
50
+ /**
51
+ * Create an encoder core bound to the given channel writer.
52
+ * @param writer - The channel writer to publish messages through.
53
+ * @param options - Encoder configuration (clientId, extras, hooks, logger).
54
+ * @returns A new {@link EncoderCore} instance.
55
+ */
56
+ export declare const createEncoderCore: (writer: ChannelWriter, options?: EncoderCoreOptions) => EncoderCore;
@@ -0,0 +1,8 @@
1
+ export { eventOutput } from './decoder.js';
2
+ export type { ChannelWriter, Codec, DecoderOutput, DiscreteEncoder, EncoderOptions, Extras, MessageAccumulator, MessagePayload, StreamDecoder, StreamEncoder, StreamPayload, StreamTrackerState, WriteOptions, } from './types.js';
3
+ export type { EncoderCore, EncoderCoreOptions } from './encoder.js';
4
+ export { createEncoderCore } from './encoder.js';
5
+ export type { DecoderCore, DecoderCoreHooks, DecoderCoreOptions } from './decoder.js';
6
+ export { createDecoderCore } from './decoder.js';
7
+ export type { LifecycleTracker, PhaseConfig } from './lifecycle-tracker.js';
8
+ export { createLifecycleTracker } from './lifecycle-tracker.js';
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Generic lifecycle tracker for codec decoders.
3
+ *
4
+ * Manages per-scope (typically per-turn) tracking of lifecycle phases that
5
+ * must be emitted before content events. When a phase has not been emitted
6
+ * (e.g. mid-stream join), the tracker synthesizes the missing events using
7
+ * codec-provided build functions.
8
+ *
9
+ * Codecs configure the tracker with an ordered list of phases, then compose
10
+ * it into their decoder hooks. The tracker is independent of any specific
11
+ * codec or event type.
12
+ */
13
+ /**
14
+ * Configuration for a single lifecycle phase that may need to be
15
+ * synthesized when missing from the wire stream.
16
+ */
17
+ export interface PhaseConfig<TEvent> {
18
+ /** Unique key identifying this phase (e.g. "start", "start-step"). */
19
+ key: string;
20
+ /**
21
+ * Build the synthetic event(s) for this phase. Called with a context
22
+ * record that codecs populate at the call site — the tracker passes
23
+ * it through without interpreting it.
24
+ * @param context - Key-value pairs from the call site (e.g. headers).
25
+ * @returns One or more synthetic events to emit for this phase.
26
+ */
27
+ build(context: Record<string, string | undefined>): TEvent[];
28
+ }
29
+ /**
30
+ * Per-scope lifecycle tracker that ensures required phases are emitted
31
+ * before content events, synthesizing missing ones for mid-stream joins.
32
+ *
33
+ * Scoped by an arbitrary string key (typically a turn ID). Each scope
34
+ * tracks independently which phases have been emitted.
35
+ */
36
+ export interface LifecycleTracker<TEvent> {
37
+ /**
38
+ * Ensure all configured phases have been emitted for the given scope.
39
+ * Returns synthetic events for any phases not yet marked as emitted,
40
+ * then marks them. Returns an empty array if all phases are current.
41
+ * @param scopeId - The scope to check (e.g. turn ID).
42
+ * @param context - Key-value pairs passed through to phase build functions.
43
+ * @returns Synthetic events for missing phases, in configuration order.
44
+ */
45
+ ensurePhases(scopeId: string, context: Record<string, string | undefined>): TEvent[];
46
+ /**
47
+ * Mark a phase as emitted from the wire (not synthetic). Call this
48
+ * when the real event arrives so the tracker does not re-synthesize it.
49
+ * @param scopeId - The scope (e.g. turn ID).
50
+ * @param phaseKey - The phase key to mark.
51
+ */
52
+ markEmitted(scopeId: string, phaseKey: string): void;
53
+ /**
54
+ * Reset a phase so it will be re-synthesized on the next
55
+ * {@link ensurePhases} call. Used for repeating phases (e.g. "start-step"
56
+ * resets after "finish-step").
57
+ * @param scopeId - The scope (e.g. turn ID).
58
+ * @param phaseKey - The phase key to reset.
59
+ */
60
+ resetPhase(scopeId: string, phaseKey: string): void;
61
+ /**
62
+ * Remove all tracking state for a scope. Call on turn completion
63
+ * (finish, abort) to free memory.
64
+ * @param scopeId - The scope to clear.
65
+ */
66
+ clearScope(scopeId: string): void;
67
+ }
68
+ /**
69
+ * Create a lifecycle tracker configured with the given phases.
70
+ * Phases are checked and synthesized in array order.
71
+ * @param phases - Ordered phase configurations.
72
+ * @returns A new {@link LifecycleTracker} instance.
73
+ */
74
+ export declare const createLifecycleTracker: <TEvent>(phases: PhaseConfig<TEvent>[]) => LifecycleTracker<TEvent>;
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Core codec interfaces as defined in the general codec specification.
3
+ *
4
+ * These types define the contract between domain event streams and Ably's
5
+ * native message primitives (publish, append, update, delete).
6
+ */
7
+ import type * as Ably from 'ably';
8
+ /**
9
+ * The I/O interface that encoders use to publish to a channel.
10
+ * An `Ably.RealtimeChannel` satisfies this directly, but the interface
11
+ * allows mocking, batching, logging, or any other decorator.
12
+ */
13
+ export interface ChannelWriter {
14
+ /** Publish one or more discrete messages to the channel. */
15
+ publish(message: Ably.Message | Ably.Message[], options?: Ably.PublishOptions): Promise<Ably.PublishResult>;
16
+ /** Append data to an existing message identified by its serial. */
17
+ appendMessage(message: Ably.Message, operation?: Ably.MessageOperation, options?: Ably.PublishOptions): Promise<Ably.UpdateDeleteResult>;
18
+ /** Replace the data of an existing message identified by its serial. */
19
+ updateMessage(message: Ably.Message, operation?: Ably.MessageOperation, options?: Ably.PublishOptions): Promise<Ably.UpdateDeleteResult>;
20
+ }
21
+ /** Shape of the extras object passed through WriteOptions and EncoderOptions. */
22
+ export interface Extras {
23
+ /** Headers to attach to the Ably message extras. */
24
+ headers?: Record<string, string>;
25
+ }
26
+ /** Per-write overrides for encoder operations. */
27
+ export interface WriteOptions {
28
+ /** Override the default clientId for this write. */
29
+ clientId?: string;
30
+ /** Override the default extras for this write. */
31
+ extras?: Extras;
32
+ /** Message identity for accumulator correlation. Stamped as `x-ably-msg-id`. */
33
+ messageId?: string;
34
+ }
35
+ /**
36
+ * A codec-agnostic description of a discrete Ably message. Used on both sides:
37
+ * - **Encode:** the domain encoder describes what to publish; the encoder core
38
+ * handles header merging, clientId resolution, and the actual publish.
39
+ * - **Decode:** the decoder core extracts these fields from an `Ably.InboundMessage`
40
+ * before calling domain hooks, keeping hooks free of Ably SDK types.
41
+ *
42
+ * Data is `unknown` because discrete messages can carry arbitrary payloads
43
+ * (strings, objects, etc.) — Ably handles serialization natively.
44
+ */
45
+ export interface MessagePayload {
46
+ /** Ably message name (e.g. "text", "tool-input", "user-message"). */
47
+ name: string;
48
+ /** Message data. Ably handles serialization — strings, objects, and arrays are all valid. */
49
+ data: unknown;
50
+ /** Headers from the Ably message extras. */
51
+ headers?: Record<string, string>;
52
+ /** Mark this message as ephemeral (not persisted in channel history). Only meaningful on encode. */
53
+ ephemeral?: boolean;
54
+ }
55
+ /**
56
+ * Payload for streamed messages. Data must be a string because
57
+ * the message append lifecycle uses text append/accumulate semantics —
58
+ * deltas are concatenated for recovery and prefix-matching on the decoder.
59
+ */
60
+ export interface StreamPayload {
61
+ /** Ably message name (e.g. "text", "reasoning", "tool-input"). */
62
+ name: string;
63
+ /** Initial or closing data for the stream. Must be a string for append/accumulate semantics. */
64
+ data: string;
65
+ /** Headers from the Ably message extras. */
66
+ headers?: Record<string, string>;
67
+ }
68
+ /**
69
+ * Running state of a streamed message tracked by the decoder core.
70
+ * Accumulates text across appends and tracks lifecycle (open/closed).
71
+ */
72
+ export interface StreamTrackerState {
73
+ /** Ably message name (e.g. "text", "reasoning", "tool-input"). */
74
+ name: string;
75
+ /** Stream identifier (e.g. chunk.id for text, toolCallId for tool-input). */
76
+ streamId: string;
77
+ /** Full accumulated text so far. */
78
+ accumulated: string;
79
+ /** Current headers for this stream. Initially set from the first publish, but may be replaced on update. */
80
+ headers: Record<string, string>;
81
+ /** Whether this stream has been closed (finished or aborted). */
82
+ closed: boolean;
83
+ }
84
+ /**
85
+ * The subset of encoder operations that are stateless — safe for long-lived
86
+ * reuse across turns. Publishes complete messages and discrete events without
87
+ * any streaming lifecycle (no trackers, no pending appends, no close).
88
+ *
89
+ * The server transport calls `writeMessages` to publish user messages to the
90
+ * channel. All messages in a single call are published atomically and share
91
+ * a single `x-ably-msg-id`, forming one node in the conversation tree.
92
+ * `writeEvent` is a public API for consumers to publish standalone discrete
93
+ * events outside the streaming flow — it is not called by the transport internally.
94
+ */
95
+ export interface DiscreteEncoder<TEvent, TMessage> {
96
+ /**
97
+ * Encode and publish one or more domain messages atomically in a single
98
+ * channel publish. All messages share the encoder's transport headers
99
+ * (including `x-ably-msg-id`), so they form one logical unit in the
100
+ * conversation tree.
101
+ */
102
+ writeMessages(messages: TMessage[], options?: WriteOptions): Promise<Ably.PublishResult>;
103
+ /**
104
+ * Encode and publish a single domain event as a standalone discrete message.
105
+ * Available for consumers to publish events outside the streaming flow.
106
+ * Implementations should throw for event types that are only meaningful
107
+ * within a stream (e.g. text deltas).
108
+ */
109
+ writeEvent(event: TEvent, options?: WriteOptions): Promise<Ably.PublishResult>;
110
+ }
111
+ /**
112
+ * Full streaming encoder with single-turn lifecycle. Extends
113
+ * `DiscreteEncoder` with stateful streaming operations (`appendEvent` for
114
+ * content streams, `close` to flush). Used by the server transport.
115
+ */
116
+ export interface StreamEncoder<TEvent, TMessage> extends DiscreteEncoder<TEvent, TMessage> {
117
+ /** Encode and append a streaming domain event to an in-progress stream (delta semantics). */
118
+ appendEvent(event: TEvent, options?: WriteOptions): Promise<void>;
119
+ /**
120
+ * Abort all in-progress streams and publish a codec-specific abort signal.
121
+ * Called by the transport when a turn is cancelled. Idempotent — calling
122
+ * abort after all streams are already aborted is a no-op.
123
+ * @param reason - Optional reason string for the abort (e.g. 'cancelled').
124
+ */
125
+ abort(reason?: string): Promise<void>;
126
+ /** Flush all pending appends and close the encoder. */
127
+ close(): Promise<void>;
128
+ }
129
+ /**
130
+ * A single output from the decoder: either a domain event or a complete domain message.
131
+ * Event outputs may carry a `messageId` read from the `x-ably-msg-id` header, which the
132
+ * accumulator uses to route events to the correct in-progress message.
133
+ */
134
+ export type DecoderOutput<TEvent, TMessage> = {
135
+ kind: 'event';
136
+ event: TEvent;
137
+ messageId?: string;
138
+ } | {
139
+ kind: 'message';
140
+ message: TMessage;
141
+ };
142
+ /** Decodes Ably messages into domain events and messages. */
143
+ export interface StreamDecoder<TEvent, TMessage> {
144
+ /** Decode a single Ably message into zero or more domain outputs. */
145
+ decode(message: Ably.InboundMessage): DecoderOutput<TEvent, TMessage>[];
146
+ }
147
+ /** Accumulates decoder outputs into a list of domain messages, tracking active streams. */
148
+ export interface MessageAccumulator<TEvent, TMessage> {
149
+ /** Process a batch of decoder outputs, updating internal message state. */
150
+ processOutputs(outputs: DecoderOutput<TEvent, TMessage>[]): void;
151
+ /** Apply an external update to a message (e.g. from an update callback). */
152
+ updateMessage(message: TMessage): void;
153
+ /** All messages accumulated so far (in-progress and completed). */
154
+ readonly messages: TMessage[];
155
+ /** Only messages whose streams have finished. */
156
+ readonly completedMessages: TMessage[];
157
+ /** Whether any stream is still actively receiving data. */
158
+ readonly hasActiveStream: boolean;
159
+ }
160
+ /** Options passed to a codec's `createEncoder` factory to configure default identity and message hooks. */
161
+ export interface EncoderOptions {
162
+ /** Default clientId for all writes. */
163
+ clientId?: string;
164
+ /** Default extras (e.g. headers) merged into every Ably message. */
165
+ extras?: Extras;
166
+ /** Hook called before each Ably message is published. Mutate the message in place to add transport-level headers. */
167
+ onMessage?: (message: Ably.Message) => void;
168
+ }
169
+ /**
170
+ * The complete codec contract that a core transport needs.
171
+ *
172
+ * Combines factory methods (createEncoder, createDecoder, createAccumulator)
173
+ * with protocol knowledge (isTerminal). Transport-level concerns like turn
174
+ * correlation, optimistic reconciliation, and cancel signals
175
+ * are handled by the transport layer using standard `x-ably-*` headers.
176
+ */
177
+ export interface Codec<TEvent, TMessage> {
178
+ /** Create a streaming encoder bound to the given channel. */
179
+ createEncoder(channel: ChannelWriter, options?: EncoderOptions): StreamEncoder<TEvent, TMessage>;
180
+ /** Create a decoder for converting Ably messages back into domain outputs. */
181
+ createDecoder(): StreamDecoder<TEvent, TMessage>;
182
+ /** Create an accumulator for building domain messages from decoder outputs. */
183
+ createAccumulator(): MessageAccumulator<TEvent, TMessage>;
184
+ /** Whether an event signals stream completion (finish, error, abort). */
185
+ isTerminal(event: TEvent): boolean;
186
+ /** Return a stable key for a message (used by MessageStore for upsert/delete). */
187
+ getMessageKey(message: TMessage): string;
188
+ }
@@ -0,0 +1,10 @@
1
+ import { ClientTransport, ClientTransportOptions } from './types.js';
2
+ /**
3
+ * Create a client-side transport that manages conversation state over an Ably channel.
4
+ *
5
+ * Subscribes to the channel immediately (before attach per RTL7g). The caller should
6
+ * ensure the channel is attached or will be attached shortly after creation.
7
+ * @param options - Configuration for the client transport.
8
+ * @returns A new {@link ClientTransport} instance.
9
+ */
10
+ export declare const createClientTransport: <TEvent, TMessage>(options: ClientTransportOptions<TEvent, TMessage>) => ClientTransport<TEvent, TMessage>;
@@ -0,0 +1,9 @@
1
+ import { Logger } from '../../logger.js';
2
+ import { ConversationTree } from './types.js';
3
+ /**
4
+ * Create a ConversationTree that materializes branching history from a flat oplog.
5
+ * @param getKey - Codec function that returns a stable key for a domain message.
6
+ * @param logger - Logger for diagnostic output.
7
+ * @returns A new {@link ConversationTree} instance.
8
+ */
9
+ export declare const createConversationTree: <TMessage>(getKey: (message: TMessage) => string, logger: Logger) => ConversationTree<TMessage>;
@@ -0,0 +1,41 @@
1
+ import { Logger } from '../../logger.js';
2
+ import { Codec } from '../codec/types.js';
3
+ import { LoadHistoryOptions, PaginatedMessages } from './types.js';
4
+ /**
5
+ * decodeHistory — load conversation history from an Ably channel and
6
+ * return decoded messages as a PaginatedMessages result.
7
+ *
8
+ * Uses a fresh decoder (not shared with the live subscription) to avoid
9
+ * state conflicts. Per-turn accumulators handle interleaved turns correctly.
10
+ *
11
+ * The `limit` option controls the number of **messages** returned,
12
+ * not the number of Ably wire messages fetched. The implementation pages
13
+ * back through Ably history until `limit` complete messages have
14
+ * been assembled. Partial turns (incomplete at the page boundary) are
15
+ * buffered internally and completed when `next()` fetches more pages.
16
+ *
17
+ * Only completed messages appear in `items`. A message is complete when
18
+ * its terminal event (finish/abort/error) has been received.
19
+ *
20
+ * Because Ably history returns newest-first while the decoder requires
21
+ * chronological order, all collected Ably messages are re-decoded from
22
+ * oldest to newest after each page fetch. This handles turns that span
23
+ * page boundaries correctly.
24
+ */
25
+ import type * as Ably from 'ably';
26
+ /**
27
+ * Load conversation history from a channel and return decoded messages.
28
+ *
29
+ * Attaches the channel if not already attached, then calls
30
+ * `channel.history({ untilAttach: true })` to guarantee no gap between
31
+ * historical and live messages. The attach is idempotent.
32
+ *
33
+ * The `limit` option controls the number of complete messages
34
+ * returned per page, not the number of Ably wire messages fetched.
35
+ * @param channel - The Ably channel to load history from.
36
+ * @param codec - The codec for decoding wire messages into domain messages.
37
+ * @param options - Pagination options.
38
+ * @param logger - Logger for diagnostic output.
39
+ * @returns The first page of decoded history messages.
40
+ */
41
+ export declare const decodeHistory: <TEvent, TMessage>(channel: Ably.RealtimeChannel, codec: Codec<TEvent, TMessage>, options: LoadHistoryOptions | undefined, logger: Logger) => Promise<PaginatedMessages<TMessage>>;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Transport header builder.
3
+ *
4
+ * Single source of truth for which `x-ably-*` headers every transport
5
+ * message carries. Used by the server transport (addMessages, streamResponse)
6
+ * and will be used by the client transport (optimistic message stamping).
7
+ */
8
+ /**
9
+ * Build the standard transport header set for a message.
10
+ * @param opts - The header values to include.
11
+ * @param opts.role - Message role (e.g. "user", "assistant").
12
+ * @param opts.turnId - Turn correlation ID.
13
+ * @param opts.msgId - Message identity.
14
+ * @param opts.turnClientId - ClientId of the turn initiator.
15
+ * @param opts.parent - Preceding message's msg-id (for branching). Null means root.
16
+ * @param opts.forkOf - Forked message's msg-id (for edit/regen).
17
+ * @returns A headers record with the `x-ably-*` transport headers set.
18
+ */
19
+ export declare const buildTransportHeaders: (opts: {
20
+ role: string;
21
+ turnId: string;
22
+ msgId: string;
23
+ turnClientId?: string;
24
+ parent?: string | null;
25
+ forkOf?: string;
26
+ }) => Record<string, string>;
@@ -0,0 +1,4 @@
1
+ export type { ActiveTurn, AddMessageOptions, AddMessagesResult, CancelFilter, CancelRequest, ClientTransport, ClientTransportOptions, CloseOptions, ConversationNode, ConversationTree, LoadHistoryOptions, MessageWithHeaders, NewTurnOptions, PaginatedMessages, SendOptions, ServerTransport, ServerTransportOptions, StreamResponseOptions, StreamResult, Turn, TurnEndReason, TurnLifecycleEvent, } from './types.js';
2
+ export { createServerTransport } from './server-transport.js';
3
+ export { createClientTransport } from './client-transport.js';
4
+ export { buildTransportHeaders } from './headers.js';
@@ -0,0 +1,16 @@
1
+ import { Logger } from '../../logger.js';
2
+ import { StreamEncoder } from '../codec/types.js';
3
+ import { StreamResult } from './types.js';
4
+ /**
5
+ * Pipe an event stream through an encoder to the channel.
6
+ *
7
+ * Returns when the stream completes, is cancelled (via signal), or errors.
8
+ * The `reason` field of the result indicates which case occurred.
9
+ * @param stream - The event stream to read from.
10
+ * @param encoder - The streaming encoder to write events through.
11
+ * @param signal - Abort signal to monitor for cancellation.
12
+ * @param onAbort - Optional callback invoked when the stream is cancelled, before the stream ends.
13
+ * @param logger - Optional logger for diagnostic output.
14
+ * @returns The reason the pipe ended.
15
+ */
16
+ export declare const pipeStream: <TEvent, TMessage>(stream: ReadableStream<TEvent>, encoder: StreamEncoder<TEvent, TMessage>, signal: AbortSignal | undefined, onAbort?: (write: (event: TEvent) => Promise<void>) => void | Promise<void>, logger?: Logger) => Promise<StreamResult>;
@@ -0,0 +1,7 @@
1
+ import { ServerTransport, ServerTransportOptions } from './types.js';
2
+ /**
3
+ * Create a server transport bound to the given channel and codec.
4
+ * @param options - Transport configuration.
5
+ * @returns A new {@link ServerTransport} instance.
6
+ */
7
+ export declare const createServerTransport: <TEvent, TMessage>(options: ServerTransportOptions<TEvent, TMessage>) => ServerTransport<TEvent, TMessage>;
@@ -0,0 +1,19 @@
1
+ import { Logger } from '../../logger.js';
2
+ /** Routes decoded events to the correct turn's ReadableStream. */
3
+ export interface StreamRouter<TEvent> {
4
+ /** Register a new stream for a turnId. Returns the ReadableStream the consumer reads from. */
5
+ createStream(turnId: string): ReadableStream<TEvent>;
6
+ /** Close the stream for a turnId. Returns true if a stream was closed. */
7
+ closeStream(turnId: string): boolean;
8
+ /** Enqueue an event to the correct stream. Returns true if routed successfully. */
9
+ route(turnId: string, event: TEvent): boolean;
10
+ /** Whether a specific turnId has an active stream. */
11
+ has(turnId: string): boolean;
12
+ }
13
+ /**
14
+ * Create a StreamRouter that routes decoded events to per-turn ReadableStreams.
15
+ * @param isTerminal - Predicate that returns true for events that close the stream.
16
+ * @param logger - Logger for diagnostic output.
17
+ * @returns A new {@link StreamRouter} instance.
18
+ */
19
+ export declare const createStreamRouter: <TEvent>(isTerminal: (event: TEvent) => boolean, logger: Logger) => StreamRouter<TEvent>;
@@ -0,0 +1,34 @@
1
+ import { Logger } from '../../logger.js';
2
+ import { TurnEndReason } from './types.js';
3
+ /**
4
+ * Server-side turn state management and lifecycle event publishing.
5
+ *
6
+ * Owns the authoritative turn lifecycle. Tracks active turns with their
7
+ * AbortControllers and clientIds. Publishes turn-start and turn-end events
8
+ * on the Ably channel so all clients can react to turn state changes.
9
+ */
10
+ import type * as Ably from 'ably';
11
+ /** Manages active turns and publishes turn lifecycle events on the channel. */
12
+ export interface TurnManager {
13
+ /** Register a new turn. Publishes turn-start on the channel. Returns AbortSignal. */
14
+ startTurn(turnId: string, clientId?: string, controller?: AbortController): Promise<AbortSignal>;
15
+ /** End a turn. Publishes turn-end on the channel. Cleans up internal state. */
16
+ endTurn(turnId: string, reason: TurnEndReason): Promise<void>;
17
+ /** Get the AbortSignal for a turn. */
18
+ getSignal(turnId: string): AbortSignal | undefined;
19
+ /** Get the clientId that owns a turn. */
20
+ getClientId(turnId: string): string | undefined;
21
+ /** Abort the signal for a turn. */
22
+ abort(turnId: string): void;
23
+ /** Get all active turn IDs. */
24
+ getActiveTurnIds(): string[];
25
+ /** Abort all active turns and clear state. */
26
+ close(): void;
27
+ }
28
+ /**
29
+ * Create a turn manager bound to the given channel.
30
+ * @param channel - The Ably channel to publish lifecycle events on.
31
+ * @param logger - Optional logger for diagnostic output.
32
+ * @returns A new {@link TurnManager} instance.
33
+ */
34
+ export declare const createTurnManager: (channel: Ably.RealtimeChannel, logger?: Logger) => TurnManager;