@ably/ai-transport 0.1.0 → 0.2.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 (163) hide show
  1. package/README.md +91 -100
  2. package/dist/ably-ai-transport.js +1553 -1238
  3. package/dist/ably-ai-transport.js.map +1 -1
  4. package/dist/ably-ai-transport.umd.cjs +1 -1
  5. package/dist/ably-ai-transport.umd.cjs.map +1 -1
  6. package/dist/constants.d.ts +116 -42
  7. package/dist/core/agent.d.ts +29 -0
  8. package/dist/core/codec/decoder.d.ts +20 -23
  9. package/dist/core/codec/encoder.d.ts +11 -8
  10. package/dist/core/codec/index.d.ts +1 -2
  11. package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
  12. package/dist/core/codec/types.d.ts +407 -115
  13. package/dist/core/transport/agent-session.d.ts +10 -0
  14. package/dist/core/transport/branch-chain.d.ts +43 -0
  15. package/dist/core/transport/client-session.d.ts +13 -0
  16. package/dist/core/transport/decode-fold.d.ts +47 -0
  17. package/dist/core/transport/headers.d.ts +96 -18
  18. package/dist/core/transport/index.d.ts +5 -6
  19. package/dist/core/transport/internal/bounded-map.d.ts +20 -0
  20. package/dist/core/transport/invocation.d.ts +74 -0
  21. package/dist/core/transport/load-conversation.d.ts +128 -0
  22. package/dist/core/transport/load-history.d.ts +39 -0
  23. package/dist/core/transport/pipe-stream.d.ts +9 -9
  24. package/dist/core/transport/run-manager.d.ts +78 -0
  25. package/dist/core/transport/tree.d.ts +373 -109
  26. package/dist/core/transport/types/agent.d.ts +353 -0
  27. package/dist/core/transport/types/client.d.ts +168 -0
  28. package/dist/core/transport/types/shared.d.ts +24 -0
  29. package/dist/core/transport/types/tree.d.ts +315 -0
  30. package/dist/core/transport/types/view.d.ts +222 -0
  31. package/dist/core/transport/types.d.ts +13 -553
  32. package/dist/core/transport/view.d.ts +272 -84
  33. package/dist/errors.d.ts +21 -10
  34. package/dist/index.d.ts +6 -8
  35. package/dist/logger.d.ts +12 -0
  36. package/dist/react/ably-ai-transport-react.js +976 -990
  37. package/dist/react/ably-ai-transport-react.js.map +1 -1
  38. package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
  39. package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
  40. package/dist/react/contexts/client-session-context.d.ts +36 -0
  41. package/dist/react/contexts/client-session-provider.d.ts +53 -0
  42. package/dist/react/create-session-hooks.d.ts +116 -0
  43. package/dist/react/index.d.ts +12 -12
  44. package/dist/react/internal/use-resolved-session.d.ts +36 -0
  45. package/dist/react/use-ably-messages.d.ts +17 -14
  46. package/dist/react/use-client-session.d.ts +81 -0
  47. package/dist/react/use-create-view.d.ts +14 -13
  48. package/dist/react/use-tree.d.ts +30 -15
  49. package/dist/react/use-view.d.ts +82 -51
  50. package/dist/utils.d.ts +32 -23
  51. package/dist/vercel/ably-ai-transport-vercel.js +2573 -2086
  52. package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
  53. package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
  54. package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
  55. package/dist/vercel/codec/decoder.d.ts +5 -18
  56. package/dist/vercel/codec/encoder.d.ts +6 -36
  57. package/dist/vercel/codec/events.d.ts +51 -0
  58. package/dist/vercel/codec/index.d.ts +24 -12
  59. package/dist/vercel/codec/reducer.d.ts +144 -0
  60. package/dist/vercel/codec/tool-transitions.d.ts +2 -2
  61. package/dist/vercel/index.d.ts +4 -5
  62. package/dist/vercel/react/ably-ai-transport-vercel-react.js +3907 -3266
  63. package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
  64. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +33 -8
  65. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
  66. package/dist/vercel/react/contexts/chat-transport-context.d.ts +7 -6
  67. package/dist/vercel/react/contexts/chat-transport-provider.d.ts +53 -41
  68. package/dist/vercel/react/index.d.ts +1 -2
  69. package/dist/vercel/react/use-chat-transport.d.ts +30 -26
  70. package/dist/vercel/react/use-message-sync.d.ts +17 -30
  71. package/dist/vercel/run-end-reason.d.ts +29 -0
  72. package/dist/vercel/transport/chat-transport.d.ts +43 -24
  73. package/dist/vercel/transport/index.d.ts +25 -21
  74. package/dist/vercel/transport/run-output-stream.d.ts +56 -0
  75. package/dist/version.d.ts +2 -0
  76. package/package.json +30 -23
  77. package/src/constants.ts +124 -51
  78. package/src/core/agent.ts +68 -0
  79. package/src/core/codec/decoder.ts +71 -98
  80. package/src/core/codec/encoder.ts +113 -65
  81. package/src/core/codec/index.ts +13 -6
  82. package/src/core/codec/lifecycle-tracker.ts +10 -9
  83. package/src/core/codec/types.ts +436 -120
  84. package/src/core/transport/agent-session.ts +1344 -0
  85. package/src/core/transport/branch-chain.ts +58 -0
  86. package/src/core/transport/client-session.ts +775 -0
  87. package/src/core/transport/decode-fold.ts +91 -0
  88. package/src/core/transport/headers.ts +181 -22
  89. package/src/core/transport/index.ts +25 -26
  90. package/src/core/transport/internal/bounded-map.ts +27 -0
  91. package/src/core/transport/invocation.ts +98 -0
  92. package/src/core/transport/load-conversation.ts +355 -0
  93. package/src/core/transport/load-history.ts +269 -0
  94. package/src/core/transport/pipe-stream.ts +54 -39
  95. package/src/core/transport/run-manager.ts +249 -0
  96. package/src/core/transport/tree.ts +926 -308
  97. package/src/core/transport/types/agent.ts +407 -0
  98. package/src/core/transport/types/client.ts +211 -0
  99. package/src/core/transport/types/shared.ts +27 -0
  100. package/src/core/transport/types/tree.ts +344 -0
  101. package/src/core/transport/types/view.ts +259 -0
  102. package/src/core/transport/types.ts +13 -706
  103. package/src/core/transport/view.ts +864 -433
  104. package/src/errors.ts +22 -9
  105. package/src/event-emitter.ts +3 -2
  106. package/src/index.ts +52 -41
  107. package/src/logger.ts +14 -1
  108. package/src/react/contexts/client-session-context.ts +41 -0
  109. package/src/react/contexts/client-session-provider.tsx +186 -0
  110. package/src/react/create-session-hooks.ts +141 -0
  111. package/src/react/index.ts +23 -13
  112. package/src/react/internal/use-resolved-session.ts +63 -0
  113. package/src/react/use-ably-messages.ts +32 -22
  114. package/src/react/use-client-session.ts +201 -0
  115. package/src/react/use-create-view.ts +33 -29
  116. package/src/react/use-tree.ts +61 -30
  117. package/src/react/use-view.ts +139 -97
  118. package/src/utils.ts +63 -45
  119. package/src/vercel/codec/decoder.ts +336 -258
  120. package/src/vercel/codec/encoder.ts +343 -205
  121. package/src/vercel/codec/events.ts +87 -0
  122. package/src/vercel/codec/index.ts +60 -13
  123. package/src/vercel/codec/reducer.ts +977 -0
  124. package/src/vercel/codec/tool-transitions.ts +2 -2
  125. package/src/vercel/index.ts +6 -19
  126. package/src/vercel/react/contexts/chat-transport-context.ts +7 -6
  127. package/src/vercel/react/contexts/chat-transport-provider.tsx +87 -59
  128. package/src/vercel/react/index.ts +3 -5
  129. package/src/vercel/react/use-chat-transport.ts +47 -49
  130. package/src/vercel/react/use-message-sync.ts +80 -39
  131. package/src/vercel/run-end-reason.ts +78 -0
  132. package/src/vercel/transport/chat-transport.ts +392 -98
  133. package/src/vercel/transport/index.ts +39 -38
  134. package/src/vercel/transport/run-output-stream.ts +170 -0
  135. package/src/version.ts +2 -0
  136. package/dist/core/transport/client-transport.d.ts +0 -10
  137. package/dist/core/transport/decode-history.d.ts +0 -43
  138. package/dist/core/transport/server-transport.d.ts +0 -7
  139. package/dist/core/transport/stream-router.d.ts +0 -29
  140. package/dist/core/transport/turn-manager.d.ts +0 -37
  141. package/dist/react/contexts/transport-context.d.ts +0 -31
  142. package/dist/react/contexts/transport-provider.d.ts +0 -49
  143. package/dist/react/create-transport-hooks.d.ts +0 -124
  144. package/dist/react/use-active-turns.d.ts +0 -12
  145. package/dist/react/use-client-transport.d.ts +0 -80
  146. package/dist/vercel/codec/accumulator.d.ts +0 -21
  147. package/dist/vercel/react/use-staged-add-tool-approval-response.d.ts +0 -30
  148. package/dist/vercel/tool-approvals.d.ts +0 -124
  149. package/dist/vercel/tool-events.d.ts +0 -26
  150. package/src/core/transport/client-transport.ts +0 -977
  151. package/src/core/transport/decode-history.ts +0 -485
  152. package/src/core/transport/server-transport.ts +0 -612
  153. package/src/core/transport/stream-router.ts +0 -136
  154. package/src/core/transport/turn-manager.ts +0 -165
  155. package/src/react/contexts/transport-context.ts +0 -37
  156. package/src/react/contexts/transport-provider.tsx +0 -164
  157. package/src/react/create-transport-hooks.ts +0 -144
  158. package/src/react/use-active-turns.ts +0 -72
  159. package/src/react/use-client-transport.ts +0 -197
  160. package/src/vercel/codec/accumulator.ts +0 -588
  161. package/src/vercel/react/use-staged-add-tool-approval-response.ts +0 -87
  162. package/src/vercel/tool-approvals.ts +0 -380
  163. package/src/vercel/tool-events.ts +0 -53
@@ -1 +1 @@
1
- {"version":3,"file":"ably-ai-transport.umd.cjs","names":[],"sources":["../src/constants.ts","../src/errors.ts","../src/utils.ts","../src/core/transport/headers.ts","../src/core/transport/pipe-stream.ts","../src/core/transport/turn-manager.ts","../src/core/transport/server-transport.ts","../src/event-emitter.ts","../src/logger.ts","../src/core/transport/stream-router.ts","../src/core/transport/tree.ts","../src/core/transport/decode-history.ts","../src/core/transport/view.ts","../src/core/transport/client-transport.ts","../src/core/codec/decoder.ts","../src/core/codec/encoder.ts","../src/core/codec/lifecycle-tracker.ts"],"sourcesContent":["/**\n * Shared constants used by both codec and transport layers.\n *\n * Header constants define the `x-ably-*` wire protocol. Message and event\n * name constants define the transport lifecycle signals on the channel.\n *\n * These live at the top level (not in codec/ or transport/) because both\n * layers need them — the codec core reads/writes stream and status headers,\n * while the transport layer reads/writes turn, cancel, and role headers.\n */\n\n// ---------------------------------------------------------------------------\n// Stream headers (used by codec encoder/decoder core)\n// ---------------------------------------------------------------------------\n\n/** Header: whether this Ably message uses streaming (message appends) or is discrete. Always \"true\" or \"false\". */\nexport const HEADER_STREAM = 'x-ably-stream';\n\n/** Header: lifecycle status of a streamed message. Only set when x-ably-stream is \"true\". */\nexport const HEADER_STATUS = 'x-ably-status';\n\n/** Header: stream identity. Set by the encoder on every streamed message; read by the decoder to correlate streams. */\nexport const HEADER_STREAM_ID = 'x-ably-stream-id';\n\n/** Header: marks a message as a discrete message part (from writeMessages). Set by publishDiscreteBatch; not set on lifecycle events from publishDiscrete. */\nexport const HEADER_DISCRETE = 'x-ably-discrete';\n\n// ---------------------------------------------------------------------------\n// Identity headers (used by transport for turn correlation)\n// ---------------------------------------------------------------------------\n\n/** Header: turn correlation ID. Set on every message in a turn. */\nexport const HEADER_TURN_ID = 'x-ably-turn-id';\n\n/** Header: message identity. Assigned per message (user or assistant). Used for optimistic reconciliation on the client. */\nexport const HEADER_MSG_ID = 'x-ably-msg-id';\n\n/** Header: clientId of the user who initiated the turn. Set by the server on stream messages. */\nexport const HEADER_TURN_CLIENT_ID = 'x-ably-turn-client-id';\n\n/** Header: message role (e.g. \"user\", \"assistant\"). */\nexport const HEADER_ROLE = 'x-ably-role';\n\n/** Header: the msg-id of the existing message this Ably message amends. Present on cross-turn amendment events. */\nexport const HEADER_AMEND = 'x-ably-amend';\n\n// ---------------------------------------------------------------------------\n// Cancel headers\n// ---------------------------------------------------------------------------\n\n/** Header: cancel a specific turn by ID. */\nexport const HEADER_CANCEL_TURN_ID = 'x-ably-cancel-turn-id';\n\n/** Header: cancel all turns belonging to the sender's clientId. */\nexport const HEADER_CANCEL_OWN = 'x-ably-cancel-own';\n\n/** Header: cancel all turns on the channel. */\nexport const HEADER_CANCEL_ALL = 'x-ably-cancel-all';\n\n/** Header: cancel all turns belonging to a specific clientId. */\nexport const HEADER_CANCEL_CLIENT_ID = 'x-ably-cancel-client-id';\n\n// ---------------------------------------------------------------------------\n// Fork / branching headers\n// ---------------------------------------------------------------------------\n\n/** Header: the msg-id of the immediately preceding message in this branch. */\nexport const HEADER_PARENT = 'x-ably-parent';\n\n/** Header: the msg-id of the message this one replaces (creates a fork). */\nexport const HEADER_FORK_OF = 'x-ably-fork-of';\n\n// ---------------------------------------------------------------------------\n// Turn lifecycle headers\n// ---------------------------------------------------------------------------\n\n/** Header: reason a turn ended (on x-ably-turn-end messages). */\nexport const HEADER_TURN_REASON = 'x-ably-turn-reason';\n\n// ---------------------------------------------------------------------------\n// Message / event names\n// ---------------------------------------------------------------------------\n\n/** Message name: client->server cancel signal. */\nexport const EVENT_CANCEL = 'x-ably-cancel';\n\n/** Message name: server publishes this to signal a turn has started. */\nexport const EVENT_TURN_START = 'x-ably-turn-start';\n\n/** Message name: server publishes this to signal a turn has ended. */\nexport const EVENT_TURN_END = 'x-ably-turn-end';\n\n/** Message name: transport-level abort signal (stream cancelled). */\nexport const EVENT_ABORT = 'x-ably-abort';\n\n/** Message name: transport-level error signal. */\nexport const EVENT_ERROR = 'x-ably-error';\n\n// ---------------------------------------------------------------------------\n// Domain header prefix (used by codec implementations)\n// ---------------------------------------------------------------------------\n\n/** Prefix for domain-specific headers. Distinguishes codec-layer headers from transport `x-ably-*` headers. */\nexport const DOMAIN_HEADER_PREFIX = 'x-domain-';\n","import * as Ably from 'ably';\n\n/**\n * Error codes for the AI Transport SDK.\n */\nexport enum ErrorCode {\n /**\n * The request was invalid.\n */\n BadRequest = 40000,\n\n /**\n * Invalid argument provided.\n */\n InvalidArgument = 40003,\n\n // 104000 - 104999 are reserved for AI Transport SDK errors\n\n /**\n * Encoder recovery failed after flush — one or more updateMessage calls\n * could not recover a failed append pipeline.\n */\n EncoderRecoveryFailed = 104000,\n\n /**\n * A transport-level channel subscription callback threw unexpectedly.\n */\n TransportSubscriptionError = 104001,\n\n /**\n * Cancel listener or onCancel hook threw while processing a cancel message.\n */\n CancelListenerError = 104002,\n\n /**\n * A publish within a turn failed (lifecycle event, message, or event).\n */\n TurnLifecycleError = 104003,\n\n /**\n * An operation was attempted on a transport that has already been closed.\n */\n TransportClosed = 104004,\n\n /**\n * The HTTP POST to the server endpoint failed (network error or non-2xx response).\n */\n TransportSendFailed = 104005,\n\n /**\n * The Ably channel lost message continuity — the channel entered FAILED,\n * SUSPENDED, or DETACHED, or re-attached with `resumed: false`. Active\n * streams can no longer be guaranteed to receive all events.\n */\n ChannelContinuityLost = 104006,\n\n /**\n * An operation was attempted but the channel is not in a usable state\n * (not ATTACHED or ATTACHING).\n */\n ChannelNotReady = 104007,\n\n /**\n * An error occurred while piping a response stream to the channel — either\n * the source event stream threw (e.g. LLM provider rate limit, model error,\n * network failure) or an underlying publish failed mid-stream.\n */\n StreamError = 104008,\n}\n\n/**\n * Returns true if the {@link Ably.ErrorInfo} code matches the provided ErrorCode value.\n * @param errorInfo The error info to check.\n * @param error The error code to compare against.\n * @returns true if the error code matches, false otherwise.\n */\n// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison\nexport const errorInfoIs = (errorInfo: Ably.ErrorInfo, error: ErrorCode): boolean => errorInfo.code === error;\n","/**\n * Shared utilities for working with Ably messages.\n *\n * These are general-purpose helpers used by both the codec and transport\n * layers. They live at the top level to avoid either layer depending on\n * the other.\n */\n\nimport type * as Ably from 'ably';\n\nimport { DOMAIN_HEADER_PREFIX } from './constants.js';\n\n/**\n * Extract extras.headers from an Ably InboundMessage.\n * @param message - The Ably message to extract headers from.\n * @returns The headers record, or an empty object if absent.\n */\nexport const getHeaders = (message: Ably.InboundMessage): Record<string, string> => {\n // CAST: Ably SDK types `extras` as `any`; runtime checks below guard access.\n const extras = message.extras as unknown;\n if (!extras || typeof extras !== 'object') return {};\n const headers = (extras as { headers?: unknown }).headers;\n if (!headers || typeof headers !== 'object') return {};\n // CAST: Ably wire protocol guarantees headers is Record<string, string>\n // when present, verified by the runtime guards above.\n return headers as Record<string, string>;\n};\n\n/**\n * Parse a JSON string, returning undefined on failure.\n * @param value - The JSON string to parse.\n * @returns The parsed value, or undefined if parsing fails.\n */\nexport const parseJson = (value: string | undefined): unknown => {\n if (value === undefined) return undefined;\n try {\n return JSON.parse(value) as unknown;\n } catch {\n return undefined;\n }\n};\n\n/**\n * Set a header value if defined, skipping undefined and null. Strings are set directly,\n * booleans and numbers are stringified, objects are JSON-serialized.\n * @param headers - The headers object to mutate.\n * @param key - The header key.\n * @param value - The value to set.\n */\nexport const setIfPresent = (headers: Record<string, string>, key: string, value: unknown): void => {\n if (value === undefined || value === null) return;\n if (typeof value === 'string') {\n headers[key] = value;\n } else if (typeof value === 'boolean' || typeof value === 'number') {\n headers[key] = String(value);\n } else if (typeof value === 'object') {\n headers[key] = JSON.stringify(value);\n }\n};\n\n/**\n * Set multiple headers at once, skipping entries whose values are undefined or null.\n * Each value is converted using the same rules as {@link setIfPresent}.\n * @param headers - The headers object to mutate.\n * @param entries - Key-value pairs to set.\n */\nexport const setHeadersIfPresent = (headers: Record<string, string>, entries: Record<string, unknown>): void => {\n for (const [key, value] of Object.entries(entries)) {\n setIfPresent(headers, key, value);\n }\n};\n\n/**\n * Merge two header records into a new object. Later values override earlier ones.\n * Undefined inputs are treated as empty.\n * @param base - Base headers (lower priority).\n * @param overrides - Override headers (higher priority).\n * @returns A new merged headers object.\n */\nexport const mergeHeaders = (\n base: Record<string, string> | undefined,\n overrides: Record<string, string> | undefined,\n): Record<string, string> => ({\n ...base,\n ...overrides,\n});\n\n/**\n * Parse a boolean header (\"true\"/\"false\"), returning undefined if absent.\n * @param value - The header string to parse.\n * @returns True if \"true\", false for any other string, or undefined if absent.\n */\nexport const parseBool = (value: string | undefined): boolean | undefined => {\n if (value === undefined) return undefined;\n return value === 'true';\n};\n\n/**\n * Build a domain headers record from key-value pairs. Each key is automatically\n * prefixed with {@link DOMAIN_HEADER_PREFIX}. Values that are undefined or null\n * are skipped; strings are set directly; booleans, numbers, and objects are\n * converted using the same rules as {@link setIfPresent}.\n * @param entries - Unprefixed key-value pairs (e.g. `{ toolCallId: 'tc-1' }` becomes `{ 'x-domain-toolCallId': 'tc-1' }`).\n * @returns A new headers record with prefixed keys.\n */\nexport const domainHeaders = (entries: Record<string, unknown>): Record<string, string> => {\n const h: Record<string, string> = {};\n for (const [key, value] of Object.entries(entries)) {\n setIfPresent(h, DOMAIN_HEADER_PREFIX + key, value);\n }\n return h;\n};\n\n/**\n * Read a domain header value from a headers record.\n * @param headers - The headers record to read from.\n * @param key - The unprefixed domain key (e.g. `'toolCallId'` reads `'x-domain-toolCallId'`).\n * @returns The header value, or undefined if absent.\n */\nexport const getDomainHeader = (headers: Record<string, string>, key: string): string | undefined =>\n headers[DOMAIN_HEADER_PREFIX + key];\n\n/**\n * Mapped type that converts properties whose type includes `undefined`\n * into optional properties with `undefined` excluded from the value.\n * Properties typed as `unknown` are kept required (since `undefined extends unknown`\n * is always true, but `unknown` fields are intentionally broad, not optional).\n */\nexport type Stripped<T> = {\n [K in keyof T as undefined extends T[K] ? (unknown extends T[K] ? K : never) : K]: T[K];\n} & {\n [K in keyof T as undefined extends T[K] ? (unknown extends T[K] ? never : K) : never]?: Exclude<T[K], undefined>;\n};\n\n/**\n * Remove all keys whose value is `undefined` from a shallow object.\n * Returns a new object — the input is not mutated. Useful for building\n * chunk literals with optional fields without conditional spread noise.\n *\n * The return type converts `{ foo: T | undefined }` to `{ foo?: T }`,\n * matching the optional-field pattern used by the AI SDK chunk types.\n * @param obj - The object to strip undefined values from.\n * @returns A shallow copy with undefined-valued keys removed.\n */\nexport const stripUndefined = <T extends Record<string, unknown>>(obj: T): Stripped<T> => {\n const result = {} as Record<string, unknown>;\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key) && obj[key] !== undefined) {\n result[key] = obj[key];\n }\n }\n // CAST: The runtime strip guarantees the Stripped<T> contract —\n // required keys are always present, optional keys are absent when undefined.\n return result as Stripped<T>;\n};\n\n// ---------------------------------------------------------------------------\n// DomainHeaderReader — typed accessors for domain headers\n// ---------------------------------------------------------------------------\n\n/**\n * Typed accessor wrapper around a headers record for reading domain headers.\n * Reduces repetitive `getDomainHeader` + `parseBool` / `parseJson` chains.\n */\nexport interface DomainHeaderReader {\n /** Read a domain header as a string, or undefined if absent. */\n str(key: string): string | undefined;\n /** Read a domain header as a string, falling back to a default if absent. */\n strOr(key: string, fallback: string): string;\n /** Read a domain header as a boolean (\"true\"/\"false\"), or undefined if absent. */\n bool(key: string): boolean | undefined;\n /** Read a domain header as parsed JSON, or undefined if absent or invalid. */\n json(key: string): unknown;\n}\n\n/**\n * Create a {@link DomainHeaderReader} over a headers record.\n * @param headers - The raw headers record to read domain headers from.\n * @returns A typed accessor for domain header values.\n */\nexport const headerReader = (headers: Record<string, string>): DomainHeaderReader => ({\n str: (key: string) => getDomainHeader(headers, key),\n strOr: (key: string, fallback: string) => getDomainHeader(headers, key) ?? fallback,\n bool: (key: string) => parseBool(getDomainHeader(headers, key)),\n json: (key: string) => parseJson(getDomainHeader(headers, key)),\n});\n\n// ---------------------------------------------------------------------------\n// DomainHeaderWriter — typed builder for domain headers\n// ---------------------------------------------------------------------------\n\n/**\n * Fluent builder for constructing domain header records with typed setters.\n * Mirrors {@link DomainHeaderReader} with the same method names for symmetry.\n * Undefined values are silently skipped on all setters.\n */\nexport interface DomainHeaderWriter {\n /** Set a string domain header. Skips if value is undefined. */\n str(key: string, value: string | undefined): DomainHeaderWriter;\n /** Set a boolean domain header (serialized as \"true\"/\"false\"). Skips if value is undefined. */\n bool(key: string, value: boolean | undefined): DomainHeaderWriter;\n /** Set a JSON-serialized domain header. Skips if value is undefined or null. */\n json(key: string, value: unknown): DomainHeaderWriter;\n /** Return the accumulated headers record. */\n build(): Record<string, string>;\n}\n\n/**\n * Create a {@link DomainHeaderWriter} for building a domain headers record.\n * @returns A fluent builder that prefixes each key with the domain header prefix.\n */\nexport const headerWriter = (): DomainHeaderWriter => {\n const h: Record<string, string> = {};\n const writer: DomainHeaderWriter = {\n str: (key: string, value: string | undefined) => {\n if (value !== undefined) h[DOMAIN_HEADER_PREFIX + key] = value;\n return writer;\n },\n bool: (key: string, value: boolean | undefined) => {\n if (value !== undefined) h[DOMAIN_HEADER_PREFIX + key] = String(value);\n return writer;\n },\n json: (key: string, value: unknown) => {\n if (value !== undefined && value !== null) h[DOMAIN_HEADER_PREFIX + key] = JSON.stringify(value);\n return writer;\n },\n build: () => h,\n };\n return writer;\n};\n","/**\n * Transport header builder.\n *\n * Single source of truth for which `x-ably-*` headers every transport\n * message carries. Used by the server transport (addMessages, streamResponse)\n * and will be used by the client transport (optimistic message stamping).\n */\n\nimport {\n HEADER_AMEND,\n HEADER_FORK_OF,\n HEADER_MSG_ID,\n HEADER_PARENT,\n HEADER_ROLE,\n HEADER_TURN_CLIENT_ID,\n HEADER_TURN_ID,\n} from '../../constants.js';\n\n/**\n * Build the standard transport header set for a message.\n * @param opts - The header values to include.\n * @param opts.role - Message role (e.g. \"user\", \"assistant\").\n * @param opts.turnId - Turn correlation ID.\n * @param opts.msgId - Message identity.\n * @param opts.turnClientId - ClientId of the turn initiator.\n * @param opts.parent - Preceding message's msg-id (for branching).\n * @param opts.forkOf - Forked message's msg-id (for edit/regen).\n * @param opts.amend - The msg-id of the existing message this message targets (cross-turn events).\n * @returns A headers record with the `x-ably-*` transport headers set.\n */\nexport const buildTransportHeaders = (opts: {\n role: string;\n turnId: string;\n msgId: string;\n turnClientId?: string;\n parent?: string;\n forkOf?: string;\n amend?: string;\n}): Record<string, string> => {\n const h: Record<string, string> = {\n [HEADER_ROLE]: opts.role,\n [HEADER_TURN_ID]: opts.turnId,\n [HEADER_MSG_ID]: opts.msgId,\n };\n if (opts.turnClientId !== undefined) h[HEADER_TURN_CLIENT_ID] = opts.turnClientId;\n if (opts.parent) h[HEADER_PARENT] = opts.parent;\n if (opts.forkOf) h[HEADER_FORK_OF] = opts.forkOf;\n if (opts.amend) h[HEADER_AMEND] = opts.amend;\n return h;\n};\n","/**\n * Pure stream piping function.\n *\n * Reads events from a ReadableStream, writes them to a streaming encoder,\n * and handles abort/error. No dependencies on turn state or transport internals.\n */\n\nimport type { Logger } from '../../logger.js';\nimport type { StreamEncoder, WriteOptions } from '../codec/types.js';\nimport type { StreamResult } from './types.js';\n\n/**\n * Pipe an event stream through an encoder to the channel.\n *\n * Returns when the stream completes, is cancelled (via signal), or errors.\n * The `reason` field of the result indicates which case occurred.\n * @param stream - The event stream to read from.\n * @param encoder - The streaming encoder to write events through.\n * @param signal - Abort signal to monitor for cancellation.\n * @param onAbort - Optional callback invoked when the stream is cancelled, before the stream ends.\n * @param resolveWriteOptions - Optional per-event hook returning {@link WriteOptions} overrides to pass to `encoder.appendEvent`.\n * @param logger - Optional logger for diagnostic output.\n * @returns The reason the pipe ended.\n */\nexport const pipeStream = async <TEvent, TMessage>(\n stream: ReadableStream<TEvent>,\n encoder: StreamEncoder<TEvent, TMessage>,\n signal: AbortSignal | undefined,\n onAbort?: (write: (event: TEvent) => Promise<void>) => void | Promise<void>,\n resolveWriteOptions?: (event: TEvent) => WriteOptions | undefined,\n logger?: Logger,\n): Promise<StreamResult> => {\n logger?.trace('pipeStream();');\n\n const reader = stream.getReader();\n\n let abortListener: (() => void) | undefined;\n const abortPromise = signal\n ? new Promise<void>((resolve) => {\n if (signal.aborted) {\n resolve();\n return;\n }\n abortListener = () => {\n resolve();\n };\n signal.addEventListener('abort', abortListener, { once: true });\n })\n : // eslint-disable-next-line @typescript-eslint/no-empty-function -- never-resolving promise: no signal means no cancellation path\n new Promise<void>(() => {});\n\n let reason: StreamResult['reason'] = 'complete';\n let caughtError: Error | undefined;\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- intentional infinite loop broken by return/break\n while (true) {\n // .then() is intentional: transforms the abort signal into a discriminant\n // for Promise.race — no async/await equivalent for this pattern.\n const result = await Promise.race([reader.read(), abortPromise.then(() => 'aborted' as const)]);\n\n if (result === 'aborted') {\n reason = 'cancelled';\n logger?.debug('pipeStream(); stream cancelled by abort signal');\n if (onAbort) {\n await onAbort(async (event: TEvent) => encoder.appendEvent(event));\n }\n await encoder.abort('cancelled');\n break;\n }\n\n const { done, value } = result;\n if (done) {\n await encoder.close();\n logger?.debug('pipeStream(); stream completed');\n break;\n }\n\n await encoder.appendEvent(value, resolveWriteOptions?.(value));\n }\n } catch (error) {\n reason = 'error';\n caughtError = error instanceof Error ? error : new Error(String(error));\n logger?.error('pipeStream(); stream error', { error: caughtError.message });\n try {\n await encoder.close();\n } catch {\n // Best-effort: encoder close in the error path may also fail\n // (e.g. channel disconnected). The original error is preserved in\n // the StreamResult reason (\"error\").\n }\n } finally {\n if (abortListener) signal?.removeEventListener('abort', abortListener);\n reader.releaseLock();\n }\n\n return { reason, error: caughtError };\n};\n","/**\n * Server-side turn state management and lifecycle event publishing.\n *\n * Owns the authoritative turn lifecycle. Tracks active turns with their\n * AbortControllers and clientIds. Publishes turn-start and turn-end events\n * on the Ably channel so all clients can react to turn state changes.\n */\n\nimport type * as Ably from 'ably';\n\nimport {\n EVENT_TURN_END,\n EVENT_TURN_START,\n HEADER_FORK_OF,\n HEADER_PARENT,\n HEADER_TURN_CLIENT_ID,\n HEADER_TURN_ID,\n HEADER_TURN_REASON,\n} from '../../constants.js';\nimport type { Logger } from '../../logger.js';\nimport type { TurnEndReason } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Interface\n// ---------------------------------------------------------------------------\n\n/** Manages active turns and publishes turn lifecycle events on the channel. */\nexport interface TurnManager {\n /** Register a new turn. Publishes turn-start on the channel. Returns AbortSignal. */\n startTurn(\n turnId: string,\n clientId?: string,\n controller?: AbortController,\n metadata?: { parent?: string; forkOf?: string },\n ): Promise<AbortSignal>;\n /** End a turn. Publishes turn-end on the channel. Cleans up internal state. */\n endTurn(turnId: string, reason: TurnEndReason): Promise<void>;\n /** Get the AbortSignal for a turn. */\n getSignal(turnId: string): AbortSignal | undefined;\n /** Get the clientId that owns a turn. */\n getClientId(turnId: string): string | undefined;\n /** Abort the signal for a turn. */\n abort(turnId: string): void;\n /** Get all active turn IDs. */\n getActiveTurnIds(): string[];\n /** Abort all active turns and clear state. */\n close(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal state\n// ---------------------------------------------------------------------------\n\ninterface ActiveTurnsEntry {\n controller: AbortController;\n clientId: string;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nclass DefaultTurnManager implements TurnManager {\n private readonly _channel: Ably.RealtimeChannel;\n private readonly _logger: Logger | undefined;\n private readonly _activeTurns = new Map<string, ActiveTurnsEntry>();\n\n constructor(channel: Ably.RealtimeChannel, logger?: Logger) {\n this._channel = channel;\n this._logger = logger?.withContext({ component: 'TurnManager' });\n }\n\n async startTurn(\n turnId: string,\n clientId?: string,\n externalController?: AbortController,\n metadata?: { parent?: string; forkOf?: string },\n ): Promise<AbortSignal> {\n this._logger?.trace('DefaultTurnManager.startTurn();', { turnId, clientId });\n\n const controller = externalController ?? new AbortController();\n const resolvedClientId = clientId ?? '';\n this._activeTurns.set(turnId, { controller, clientId: resolvedClientId });\n\n const headers: Record<string, string> = {\n [HEADER_TURN_ID]: turnId,\n [HEADER_TURN_CLIENT_ID]: resolvedClientId,\n };\n if (metadata?.parent !== undefined) {\n headers[HEADER_PARENT] = metadata.parent;\n }\n if (metadata?.forkOf !== undefined) {\n headers[HEADER_FORK_OF] = metadata.forkOf;\n }\n\n await this._channel.publish({\n name: EVENT_TURN_START,\n extras: { headers },\n });\n\n this._logger?.debug('DefaultTurnManager.startTurn(); turn started', { turnId });\n return controller.signal;\n }\n\n async endTurn(turnId: string, reason: TurnEndReason): Promise<void> {\n this._logger?.trace('DefaultTurnManager.endTurn();', { turnId, reason });\n\n const state = this._activeTurns.get(turnId);\n const resolvedClientId = state?.clientId ?? '';\n\n // Publish before deleting local state so that if publish fails,\n // the turn remains in the active set and can be retried or cleaned up.\n await this._channel.publish({\n name: EVENT_TURN_END,\n extras: {\n headers: {\n [HEADER_TURN_ID]: turnId,\n [HEADER_TURN_CLIENT_ID]: resolvedClientId,\n [HEADER_TURN_REASON]: reason,\n },\n },\n });\n\n this._activeTurns.delete(turnId);\n this._logger?.debug('DefaultTurnManager.endTurn(); turn ended', { turnId, reason });\n }\n\n getSignal(turnId: string): AbortSignal | undefined {\n return this._activeTurns.get(turnId)?.controller.signal;\n }\n\n getClientId(turnId: string): string | undefined {\n return this._activeTurns.get(turnId)?.clientId;\n }\n\n abort(turnId: string): void {\n this._logger?.debug('DefaultTurnManager.abort();', { turnId });\n this._activeTurns.get(turnId)?.controller.abort();\n }\n\n getActiveTurnIds(): string[] {\n return [...this._activeTurns.keys()];\n }\n\n close(): void {\n this._logger?.trace('DefaultTurnManager.close();', { activeTurns: this._activeTurns.size });\n for (const state of this._activeTurns.values()) {\n state.controller.abort();\n }\n this._activeTurns.clear();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a turn manager bound to the given channel.\n * @param channel - The Ably channel to publish lifecycle events on.\n * @param logger - Optional logger for diagnostic output.\n * @returns A new {@link TurnManager} instance.\n */\nexport const createTurnManager = (channel: Ably.RealtimeChannel, logger?: Logger): TurnManager =>\n new DefaultTurnManager(channel, logger);\n","/**\n * Core server-side transport, parameterized by codec.\n *\n * Composes TurnManager and pipeStream to handle the full server-side turn\n * lifecycle. Cancel message routing is handled directly by the transport's\n * single channel subscription — no separate cancel manager needed.\n *\n * The transport exposes a single factory method — `newTurn()` — which returns\n * a Turn object with explicit lifecycle methods: start(), addMessages(),\n * streamResponse(), and end().\n */\n\nimport * as Ably from 'ably';\n\nimport {\n EVENT_CANCEL,\n HEADER_CANCEL_ALL,\n HEADER_CANCEL_CLIENT_ID,\n HEADER_CANCEL_OWN,\n HEADER_CANCEL_TURN_ID,\n} from '../../constants.js';\nimport { ErrorCode } from '../../errors.js';\nimport type { Logger } from '../../logger.js';\nimport { getHeaders, mergeHeaders } from '../../utils.js';\nimport { buildTransportHeaders } from './headers.js';\nimport { pipeStream } from './pipe-stream.js';\nimport type { TurnManager } from './turn-manager.js';\nimport { createTurnManager } from './turn-manager.js';\nimport type {\n AddMessageOptions,\n AddMessagesResult,\n CancelFilter,\n CancelRequest,\n EventsNode,\n MessageNode,\n NewTurnOptions,\n ServerTransport,\n ServerTransportOptions,\n StreamResponseOptions,\n StreamResult,\n Turn,\n TurnEndReason,\n} from './types.js';\n\n// ---------------------------------------------------------------------------\n// Internal turn record for cancel routing\n// ---------------------------------------------------------------------------\n\ninterface RegisteredTurn {\n turnId: string;\n clientId: string;\n controller: AbortController;\n /** Composite signal that fires when either the internal controller or the external signal aborts. */\n signal: AbortSignal;\n onCancel?: (request: CancelRequest) => Promise<boolean>;\n onError?: (error: Ably.ErrorInfo) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal state machines\n// ---------------------------------------------------------------------------\n\nenum ServerTransportState {\n READY = 'ready',\n CLOSED = 'closed',\n}\n\nenum TurnState {\n INITIALIZED = 'initialized',\n STARTED = 'started',\n ENDED = 'ended',\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n// Spec: AIT-ST1\nclass DefaultServerTransport<TEvent, TMessage> implements ServerTransport<TEvent, TMessage> {\n private readonly _channel: Ably.RealtimeChannel;\n private readonly _codec: ServerTransportOptions<TEvent, TMessage>['codec'];\n private readonly _logger: Logger | undefined;\n private readonly _onError: ((error: Ably.ErrorInfo) => void) | undefined;\n private readonly _turnManager: TurnManager;\n private readonly _registeredTurns = new Map<string, RegisteredTurn>();\n private readonly _channelListener: (msg: Ably.InboundMessage) => void;\n private readonly _attachPromise: Promise<void>;\n\n private _state = ServerTransportState.READY;\n private _hasAttachedOnce: boolean;\n private readonly _onChannelStateChange: Ably.channelEventCallback;\n\n constructor(options: ServerTransportOptions<TEvent, TMessage>) {\n this._channel = options.channel;\n this._codec = options.codec;\n this._logger = options.logger?.withContext({ component: 'ServerTransport' });\n this._onError = options.onError;\n this._turnManager = createTurnManager(this._channel, this._logger);\n\n this._channelListener = (msg: Ably.InboundMessage) => {\n this._handleChannelMessage(msg);\n };\n\n // Spec: AIT-ST2\n // Subscribe before attach (RTL7g) — ensures no messages are missed.\n this._attachPromise = this._channel.subscribe(EVENT_CANCEL, this._channelListener).then(\n /* eslint-disable @typescript-eslint/no-empty-function -- discard subscription handle */\n () => {},\n /* eslint-enable @typescript-eslint/no-empty-function */\n (error: unknown) => {\n const errInfo = new Ably.ErrorInfo(\n `unable to subscribe to cancel messages; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.TransportSubscriptionError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n this._logger?.error('DefaultServerTransport(); subscribe failed');\n this._onError?.(errInfo);\n },\n );\n\n // Spec: AIT-ST12, AIT-ST12a\n // Listen for channel state changes that break message continuity. The\n // server only consumes cancel messages from the channel, so losing one\n // is survivable — but the developer needs to know so they can decide\n // whether to abort in-flight work. _hasAttachedOnce is seeded from the\n // channel's current state so pre-attached channels are handled correctly;\n // it distinguishes the initial attach from a genuine discontinuity.\n this._hasAttachedOnce = this._channel.state === 'attached';\n this._onChannelStateChange = (stateChange: Ably.ChannelStateChange) => {\n this._handleChannelStateChange(stateChange);\n };\n this._channel.on(this._onChannelStateChange);\n\n this._logger?.debug('DefaultServerTransport(); transport created');\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n // Spec: AIT-ST3\n newTurn(turnOpts: NewTurnOptions<TEvent>): Turn<TEvent, TMessage> {\n this._logger?.trace('DefaultServerTransport.newTurn();', { turnId: turnOpts.turnId });\n return this._createTurn(turnOpts);\n }\n\n // Spec: AIT-ST11\n close(): void {\n if (this._state === ServerTransportState.CLOSED) return;\n this._state = ServerTransportState.CLOSED;\n this._logger?.trace('DefaultServerTransport.close();');\n this._channel.unsubscribe(EVENT_CANCEL, this._channelListener);\n this._channel.off(this._onChannelStateChange);\n for (const reg of this._registeredTurns.values()) {\n reg.controller.abort();\n }\n this._registeredTurns.clear();\n this._turnManager.close();\n this._logger?.debug('DefaultServerTransport.close(); transport closed');\n }\n\n // -------------------------------------------------------------------------\n // Cancel message routing\n // -------------------------------------------------------------------------\n\n private _resolveFilter(filter: CancelFilter, senderClientId?: string): string[] {\n const turnIds = [...this._registeredTurns.keys()];\n\n if (filter.all) return turnIds;\n if (filter.own && senderClientId) {\n return turnIds.filter((id) => this._registeredTurns.get(id)?.clientId === senderClientId);\n }\n if (filter.clientId) {\n return turnIds.filter((id) => this._registeredTurns.get(id)?.clientId === filter.clientId);\n }\n if (filter.turnId && this._registeredTurns.has(filter.turnId)) {\n return [filter.turnId];\n }\n return [];\n }\n\n // Spec: AIT-ST8, AIT-ST8a, AIT-ST8b, AIT-ST8c, AIT-ST8d, AIT-ST9, AIT-ST9a\n private async _handleCancelMessage(msg: Ably.InboundMessage): Promise<void> {\n const headers = getHeaders(msg);\n\n // Spec: AIT-ST8a, AIT-ST8b, AIT-ST8c, AIT-ST8d\n const filter: CancelFilter = {};\n if (headers[HEADER_CANCEL_TURN_ID]) {\n filter.turnId = headers[HEADER_CANCEL_TURN_ID];\n } else if (headers[HEADER_CANCEL_OWN] === 'true') {\n filter.own = true;\n } else if (headers[HEADER_CANCEL_CLIENT_ID]) {\n filter.clientId = headers[HEADER_CANCEL_CLIENT_ID];\n } else if (headers[HEADER_CANCEL_ALL] === 'true') {\n filter.all = true;\n }\n\n const matchedTurnIds = this._resolveFilter(filter, msg.clientId);\n if (matchedTurnIds.length === 0) return;\n\n this._logger?.debug('DefaultServerTransport._handleCancelMessage(); matched turns', {\n matchedTurnIds,\n filter,\n });\n\n const owners = new Map<string, string>();\n for (const tid of matchedTurnIds) {\n const reg = this._registeredTurns.get(tid);\n owners.set(tid, reg?.clientId ?? '');\n }\n const request: CancelRequest = { message: msg, filter, matchedTurnIds, turnOwners: owners };\n\n for (const turnId of matchedTurnIds) {\n const reg = this._registeredTurns.get(turnId);\n if (!reg) continue;\n\n try {\n if (reg.onCancel) {\n const allowed = await reg.onCancel(request);\n if (!allowed) {\n this._logger?.debug('DefaultServerTransport._handleCancelMessage(); cancel rejected by onCancel', {\n turnId,\n });\n continue;\n }\n }\n reg.controller.abort();\n this._logger?.debug('DefaultServerTransport._handleCancelMessage(); turn aborted', { turnId });\n } catch (error) {\n // A throwing onCancel handler must not prevent other turns from being cancelled.\n const errInfo = new Ably.ErrorInfo(\n `unable to process cancel for turn ${turnId}; onCancel handler threw: ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.CancelListenerError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n this._logger?.error('DefaultServerTransport._handleCancelMessage(); onCancel threw', { turnId });\n (reg.onError ?? this._onError)?.(errInfo);\n }\n }\n }\n\n // -------------------------------------------------------------------------\n // Channel state change handler\n // -------------------------------------------------------------------------\n\n // Spec: AIT-ST12, AIT-ST12a\n private _handleChannelStateChange(stateChange: Ably.ChannelStateChange): void {\n if (this._state === ServerTransportState.CLOSED) return;\n\n const { current, resumed } = stateChange;\n\n // Track the initial attach so we don't treat it as a discontinuity\n if (current === 'attached' && !this._hasAttachedOnce) {\n this._hasAttachedOnce = true;\n return;\n }\n\n // Continuity-breaking states:\n // - FAILED, SUSPENDED, DETACHED: no more messages expected (or gap)\n // - ATTACHED with resumed: false (UPDATE): messages were lost\n const continuityLost =\n current === 'failed' || current === 'suspended' || current === 'detached' || (current === 'attached' && !resumed);\n\n if (!continuityLost) return;\n\n this._logger?.error('DefaultServerTransport._handleChannelStateChange(); channel continuity lost', {\n current,\n resumed,\n previous: stateChange.previous,\n });\n\n const err = new Ably.ErrorInfo(\n `unable to deliver cancel messages; channel continuity lost (${current}${current === 'attached' ? ', resumed: false' : ''})`,\n ErrorCode.ChannelContinuityLost,\n 500,\n stateChange.reason,\n );\n\n // Transport-level notification only: continuity loss is not scoped to any\n // turn. Per-turn onError handlers are reserved for errors from that turn's\n // own operations (publish failures, encoder errors). Developers that need\n // per-turn reaction can iterate active turns from the transport handler.\n this._onError?.(err);\n }\n\n // -------------------------------------------------------------------------\n // Channel subscription handler\n // -------------------------------------------------------------------------\n\n private _handleChannelMessage(msg: Ably.InboundMessage): void {\n try {\n if (msg.name === EVENT_CANCEL) {\n // Fire-and-forget async handler — errors are caught internally.\n this._handleCancelMessage(msg).catch((error: unknown) => {\n const errInfo = new Ably.ErrorInfo(\n `unable to route cancel message; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.CancelListenerError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n this._logger?.error('DefaultServerTransport._handleChannelMessage(); cancel routing error');\n this._onError?.(errInfo);\n });\n }\n } catch (error) {\n const errInfo = new Ably.ErrorInfo(\n `unable to process channel message; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.TransportSubscriptionError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n this._logger?.error('DefaultServerTransport._handleChannelMessage(); subscription error');\n this._onError?.(errInfo);\n }\n }\n\n // -------------------------------------------------------------------------\n // Turn creation\n // -------------------------------------------------------------------------\n\n private _createTurn(turnOpts: NewTurnOptions<TEvent>): Turn<TEvent, TMessage> {\n const {\n turnId,\n clientId: turnClientId,\n onMessage,\n onAbort,\n onCancel,\n onError: turnOnError,\n parent: turnParent,\n forkOf: turnForkOf,\n signal: externalSignal,\n } = turnOpts;\n\n const controller = new AbortController();\n let state = TurnState.INITIALIZED;\n\n // Compose the internal controller signal with the external signal (e.g.\n // req.signal) so platform-level cancellation (request cancellation, function\n // timeout) aborts the turn through the same path as Ably cancel messages.\n const signal = externalSignal ? AbortSignal.any([controller.signal, externalSignal]) : controller.signal;\n\n // Spec: AIT-ST3a — register immediately so early cancels can fire the abort signal.\n const registration: RegisteredTurn = {\n turnId,\n clientId: turnClientId ?? '',\n controller,\n signal,\n onCancel,\n onError: turnOnError,\n };\n this._registeredTurns.set(turnId, registration);\n\n // Capture instance members as locals so arrow functions close over them\n // without needing `this` (avoids unicorn/no-this-assignment).\n const logger = this._logger;\n const turnManager = this._turnManager;\n const attachPromise = this._attachPromise;\n const codec = this._codec;\n const channel = this._channel;\n const registeredTurns = this._registeredTurns;\n\n const turn: Turn<TEvent, TMessage> = {\n get turnId() {\n return turnId;\n },\n get abortSignal() {\n return signal;\n },\n\n // Spec: AIT-ST4, AIT-ST4a, AIT-ST4b\n start: async (): Promise<void> => {\n logger?.trace('Turn.start();', { turnId });\n\n // Spec: AIT-ST4a\n if (signal.aborted) {\n throw new Ably.ErrorInfo(\n `unable to start turn; turn ${turnId} was cancelled before start()`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n if (state !== TurnState.INITIALIZED) return;\n state = TurnState.STARTED;\n\n try {\n await turnManager.startTurn(turnId, turnClientId, controller, {\n parent: turnParent,\n forkOf: turnForkOf,\n });\n } catch (error) {\n const errInfo = new Ably.ErrorInfo(\n `unable to publish turn-start for turn ${turnId}; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.TurnLifecycleError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n logger?.error('Turn.start(); failed to publish turn-start', { turnId });\n throw errInfo;\n }\n\n logger?.debug('Turn.start(); turn started', { turnId });\n },\n\n // Spec: AIT-ST5, AIT-ST5a, AIT-ST5b, AIT-ST5c\n addMessages: async (nodes: MessageNode<TMessage>[], opts?: AddMessageOptions): Promise<AddMessagesResult> => {\n logger?.trace('Turn.addMessages();', { turnId, count: nodes.length });\n\n if (state === TurnState.INITIALIZED) {\n throw new Ably.ErrorInfo(\n `unable to add messages; start() must be called before addMessages() (turn ${turnId})`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n await attachPromise;\n\n const msgIds: string[] = [];\n\n try {\n for (const node of nodes) {\n // Build transport headers from the node's typed fields, then merge\n // any extra headers from the node (e.g. domain-specific headers).\n const headers = mergeHeaders(\n buildTransportHeaders({\n role: 'user',\n turnId,\n msgId: node.msgId,\n turnClientId: opts?.clientId,\n parent: node.parentId ?? turnParent,\n forkOf: node.forkOf ?? turnForkOf,\n }),\n node.headers,\n );\n\n const encoder = codec.createEncoder(channel, {\n extras: { headers },\n onMessage,\n });\n\n await encoder.writeMessages([node.message], opts?.clientId ? { clientId: opts.clientId } : undefined);\n\n msgIds.push(node.msgId);\n }\n } catch (error) {\n const errInfo = new Ably.ErrorInfo(\n `unable to publish messages for turn ${turnId}; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.TurnLifecycleError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n logger?.error('Turn.addMessages(); publish failed', { turnId });\n throw errInfo;\n }\n\n logger?.debug('Turn.addMessages(); messages published', { turnId, count: nodes.length });\n return { msgIds };\n },\n\n // Spec: AIT-ST5c\n addEvents: async (nodes: EventsNode<TEvent>[]): Promise<void> => {\n logger?.trace('Turn.addEvents();', { turnId, count: nodes.length });\n\n if (state === TurnState.INITIALIZED) {\n throw new Ably.ErrorInfo(\n `unable to add events; start() must be called before addEvents() (turn ${turnId})`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n await attachPromise;\n\n const turnOwnerClientId = turnManager.getClientId(turnId);\n\n try {\n for (const node of nodes) {\n const headers = buildTransportHeaders({\n role: 'assistant',\n turnId,\n msgId: node.msgId,\n turnClientId: turnOwnerClientId,\n amend: node.msgId,\n });\n\n const encoder = codec.createEncoder(channel, {\n extras: { headers },\n onMessage,\n });\n\n for (const event of node.events) {\n await encoder.writeEvent(event);\n }\n\n await encoder.close();\n }\n } catch (error) {\n const errInfo = new Ably.ErrorInfo(\n `unable to publish events for turn ${turnId}; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.TurnLifecycleError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n logger?.error('Turn.addEvents(); publish failed', { turnId });\n throw errInfo;\n }\n\n logger?.debug('Turn.addEvents(); events published', { turnId, count: nodes.length });\n },\n\n // Spec: AIT-ST6, AIT-ST6a, AIT-ST6b, AIT-ST6b1, AIT-ST6b2, AIT-ST6b3, AIT-ST6b4, AIT-ST6c\n streamResponse: async (\n stream: ReadableStream<TEvent>,\n streamOpts?: StreamResponseOptions<TEvent>,\n ): Promise<StreamResult> => {\n logger?.trace('Turn.streamResponse();', { turnId });\n\n if (state === TurnState.INITIALIZED) {\n throw new Ably.ErrorInfo(\n `unable to stream response; start() must be called before streamResponse() (turn ${turnId})`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n await attachPromise;\n\n const turnOwnerClientId = turnManager.getClientId(turnId);\n\n // Per-operation parent overrides the turn-level default.\n const assistantParent = streamOpts?.parent === undefined ? turnParent : streamOpts.parent;\n\n const msgId = crypto.randomUUID();\n const defaultHeaders = buildTransportHeaders({\n role: 'assistant',\n turnId,\n msgId,\n turnClientId: turnOwnerClientId,\n parent: assistantParent,\n forkOf: streamOpts?.forkOf ?? turnForkOf,\n });\n const encoder = codec.createEncoder(channel, {\n extras: { headers: defaultHeaders },\n onMessage,\n messageId: msgId,\n });\n\n const result = await pipeStream(stream, encoder, signal, onAbort, streamOpts?.resolveWriteOptions, logger);\n\n if (result.error) {\n const errInfo = new Ably.ErrorInfo(\n `unable to stream response for turn ${turnId}; ${result.error.message}`,\n ErrorCode.StreamError,\n 500,\n result.error instanceof Ably.ErrorInfo ? result.error : undefined,\n );\n logger?.error('Turn.streamResponse(); stream error', { turnId });\n turnOnError?.(errInfo);\n }\n\n logger?.debug('Turn.streamResponse(); stream finished', { turnId, reason: result.reason });\n return result;\n },\n\n // Spec: AIT-ST7, AIT-ST7a, AIT-ST7b\n end: async (reason: TurnEndReason): Promise<void> => {\n logger?.trace('Turn.end();', { turnId, reason });\n\n if (state === TurnState.INITIALIZED) {\n throw new Ably.ErrorInfo(\n `unable to end turn; start() must be called before end() (turn ${turnId})`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n if (state === TurnState.ENDED) return;\n state = TurnState.ENDED;\n\n try {\n await turnManager.endTurn(turnId, reason);\n } catch (error) {\n const errInfo = new Ably.ErrorInfo(\n `unable to publish turn-end for turn ${turnId}; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.TurnLifecycleError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n logger?.error('Turn.end(); failed to publish turn-end', { turnId });\n throw errInfo;\n } finally {\n registeredTurns.delete(turnId);\n }\n\n logger?.debug('Turn.end(); turn ended', { turnId, reason });\n },\n };\n\n return turn;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a server transport bound to the given channel and codec.\n * @param options - Transport configuration.\n * @returns A new {@link ServerTransport} instance.\n */\nexport const createServerTransport = <TEvent, TMessage>(\n options: ServerTransportOptions<TEvent, TMessage>,\n): ServerTransport<TEvent, TMessage> => new DefaultServerTransport(options);\n","/**\n * Type-safe EventEmitter wrapping Ably's internal EventEmitter.\n *\n * Takes a single `EventsMap` type parameter — an interface mapping event names\n * to payload types — rather than Ably's three type parameters. Adapted from\n * the ably-chat-js SDK.\n *\n * ```ts\n * interface MyEvents {\n * reaction: { emoji: string };\n * status: { online: boolean };\n * }\n *\n * const emitter = new EventEmitter<MyEvents>(logger);\n * emitter.on('reaction', (event) => console.log(event.emoji));\n * emitter.emit('reaction', { emoji: '👍' });\n * ```\n */\n\nimport * as Ably from 'ably';\n\nimport type { Logger } from './logger.js';\n\n/** Callback receiving a union of all possible event payloads. */\ntype Callback<EventsMap> = (arg: EventsMap[keyof EventsMap]) => void;\n\n/** Callback receiving the payload for a single event type. */\ntype CallbackSingle<K> = (arg: K) => void;\n\n/**\n * Type-safe interface for the Ably EventEmitter, parameterized by an EventsMap\n * that maps event names to their payload types.\n */\ninterface InterfaceEventEmitter<EventsMap> extends Ably.EventEmitter<Callback<EventsMap>, void, keyof EventsMap> {\n /** Emit an event with a type-safe payload. Payload is optional for `undefined`-typed events. */\n emit<K extends keyof EventsMap>(\n event: K,\n ...args: EventsMap[K] extends undefined ? [EventsMap[K]?] : [EventsMap[K]]\n ): void;\n\n /** Subscribe to a single event with a typed callback. */\n on<K extends keyof EventsMap>(event: K, callback: CallbackSingle<EventsMap[K]>): void;\n /** Subscribe to two events with a union-typed callback. */\n on<K1 extends keyof EventsMap, K2 extends keyof EventsMap>(\n events: [K1, K2],\n callback: CallbackSingle<EventsMap[K1] | EventsMap[K2]>,\n ): void;\n /** Subscribe to three events with a union-typed callback. */\n on<K1 extends keyof EventsMap, K2 extends keyof EventsMap, K3 extends keyof EventsMap>(\n events: [K1, K2, K3],\n callback: CallbackSingle<EventsMap[K1] | EventsMap[K2] | EventsMap[K3]>,\n ): void;\n /** Subscribe to an array of events. */\n on(events: (keyof EventsMap)[], callback: Callback<EventsMap>): void;\n /** Subscribe to all events. */\n on(callback: Callback<EventsMap>): void;\n\n /** Unsubscribe a callback from a specific event. */\n off<K extends keyof EventsMap>(event: K, listener: CallbackSingle<EventsMap[K]>): void;\n /** Unsubscribe a callback from all events, or remove all listeners if no callback provided. */\n off(listener?: Callback<EventsMap>): void;\n}\n\n/**\n * Bridge from our {@link Logger} to the Ably EventEmitter's internal logger\n * contract. Ably's EventEmitter calls `logger.logAction(level, action, message)`\n * when a listener throws — we route that to our Logger's `error` method.\n * @param logger - The application logger to delegate to.\n * @returns An object satisfying the Ably EventEmitter's logger interface.\n */\nconst toAblyLogger = (logger: Logger): unknown => ({\n logAction: (_level: number, action: string, message?: string) => {\n logger.error(action, { detail: message });\n },\n shouldLog: () => true,\n});\n\n// CAST: Access Ably's internal EventEmitter constructor. Not publicly exported\n// but available to other Ably SDKs. The logger parameter ensures listener\n// exceptions are caught and logged rather than crashing.\nconst InternalEventEmitter: new <EventsMap>(logger: unknown) => InterfaceEventEmitter<EventsMap> = (\n Ably.Realtime as unknown as { EventEmitter: new <EventsMap>(logger: unknown) => InterfaceEventEmitter<EventsMap> }\n).EventEmitter;\n\n/**\n * Type-safe EventEmitter based on Ably's internal EventEmitter.\n *\n * Provides the same semantics as {@link Ably.EventEmitter} (error isolation\n * between listeners, synchronous dispatch) but with a single `EventsMap` type\n * parameter for ergonomic type safety.\n *\n * Requires a {@link Logger} so that listener exceptions are routed through\n * the application's logging infrastructure rather than silently swallowed.\n */\nexport class EventEmitter<EventsMap> extends InternalEventEmitter<EventsMap> {\n /**\n * Create a new EventEmitter.\n * @param logger - Application logger. Listener exceptions are logged at error level.\n */\n constructor(logger: Logger) {\n super(toAblyLogger(logger));\n }\n}\n","import * as Ably from 'ably';\n\nimport { ErrorCode } from './errors.js';\n\n/**\n * Structured logger with leveled output and hierarchical context.\n * Implementations filter messages by level and delegate to a {@link LogHandler}.\n */\nexport interface Logger {\n /**\n * Log a message at the trace level.\n * @param message The message to log.\n * @param context The context of the log message as key-value pairs.\n */\n trace(message: string, context?: LogContext): void;\n\n /**\n * Log a message at the debug level.\n * @param message The message to log.\n * @param context The context of the log message as key-value pairs.\n */\n debug(message: string, context?: LogContext): void;\n\n /**\n * Log a message at the info level.\n * @param message The message to log.\n * @param context The context of the log message as key-value pairs.\n */\n info(message: string, context?: LogContext): void;\n\n /**\n * Log a message at the warn level.\n * @param message The message to log.\n * @param context The context of the log message as key-value pairs.\n */\n warn(message: string, context?: LogContext): void;\n\n /**\n * Log a message at the error level.\n * @param message The message to log.\n * @param context The context of the log message as key-value pairs.\n */\n error(message: string, context?: LogContext): void;\n\n /**\n * Creates a new logger with a context that will be merged with any context provided to individual log calls.\n * The context will be overridden by any matching keys in the individual log call's context.\n * @param context The context to use for all log calls.\n * @returns A new logger instance with the context.\n */\n withContext(context: LogContext): Logger;\n}\n\n/**\n * Represents the different levels of logging that can be used.\n */\nexport enum LogLevel {\n /**\n * Something routine and expected has occurred. This level will provide logs for the vast majority of operations\n * and function calls.\n */\n Trace = 'trace',\n\n /**\n * Development information, messages that are useful when trying to debug library behavior,\n * but superfluous to normal operation.\n */\n Debug = 'debug',\n\n /**\n * Informational messages. Operationally significant to the library but not out of the ordinary.\n */\n Info = 'info',\n\n /**\n * Anything that is not immediately an error, but could cause unexpected behavior in the future. For example,\n * passing an invalid value to an option. Indicates that some action should be taken to prevent future errors.\n */\n Warn = 'warn',\n\n /**\n * A given operation has failed and cannot be automatically recovered. The error may threaten the continuity\n * of operation.\n */\n Error = 'error',\n\n /**\n * No logging will be performed.\n */\n Silent = 'silent',\n}\n\n/**\n * Represents the context of a log message.\n * It is an object of key-value pairs that can be used to provide additional context to a log message.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type LogContext = Record<string, any>;\n\n/**\n * A function that can be used to handle log messages.\n * @param message The message to log.\n * @param level The log level of the message.\n * @param context The context of the log message as key-value pairs.\n */\nexport type LogHandler = (message: string, level: LogLevel, context?: LogContext) => void;\n\n/**\n * A simple console logger that logs messages to the console.\n * @param message The message to log.\n * @param level The log level of the message.\n * @param context - The context of the log message as key-value pairs.\n */\nexport const consoleLogger = (message: string, level: LogLevel, context?: LogContext) => {\n const contextString = context ? `, context: ${JSON.stringify(context)}` : '';\n const formattedMessage = `[${new Date().toISOString()}] ${level.valueOf().toUpperCase()} ably-ai-transport: ${message}${contextString}`;\n\n switch (level) {\n case LogLevel.Trace:\n case LogLevel.Debug: {\n console.log(formattedMessage);\n break;\n }\n case LogLevel.Info: {\n console.info(formattedMessage);\n break;\n }\n case LogLevel.Warn: {\n console.warn(formattedMessage);\n break;\n }\n case LogLevel.Error: {\n console.error(formattedMessage);\n break;\n }\n case LogLevel.Silent: {\n break;\n }\n }\n};\n\n/**\n * Options for creating a logger.\n */\nexport interface LoggerOptions {\n logHandler?: LogHandler;\n logLevel: LogLevel;\n}\n\nexport const makeLogger = (options: LoggerOptions): Logger => {\n const logHandler = options.logHandler ?? consoleLogger;\n\n return new DefaultLogger(logHandler, options.logLevel);\n};\n\n/**\n * A convenient list of log levels as numbers that can be used for easier comparison.\n */\nenum LogLevelNumber {\n Trace = 0,\n Debug = 1,\n Info = 2,\n Warn = 3,\n Error = 4,\n Silent = 5,\n}\n\n/**\n * A mapping of log levels to their numeric equivalents.\n */\nconst logLevelNumberMap = new Map<LogLevel, LogLevelNumber>([\n [LogLevel.Trace, LogLevelNumber.Trace],\n [LogLevel.Debug, LogLevelNumber.Debug],\n [LogLevel.Info, LogLevelNumber.Info],\n [LogLevel.Warn, LogLevelNumber.Warn],\n [LogLevel.Error, LogLevelNumber.Error],\n [LogLevel.Silent, LogLevelNumber.Silent],\n]);\n\n/**\n * A default logger implementation.\n */\nclass DefaultLogger implements Logger {\n private readonly _handler: LogHandler;\n private readonly _levelNumber: LogLevelNumber;\n private readonly _context?: LogContext;\n\n constructor(handler: LogHandler, level: LogLevel, context?: LogContext) {\n this._handler = handler;\n this._context = context;\n\n const levelNumber = logLevelNumberMap.get(level);\n if (levelNumber === undefined) {\n throw new Ably.ErrorInfo(`unable to create logger; invalid log level: ${level}`, ErrorCode.InvalidArgument, 400);\n }\n\n this._levelNumber = levelNumber;\n }\n\n trace(message: string, context?: LogContext): void {\n this._write(message, LogLevel.Trace, LogLevelNumber.Trace, context);\n }\n\n debug(message: string, context?: LogContext): void {\n this._write(message, LogLevel.Debug, LogLevelNumber.Debug, context);\n }\n\n info(message: string, context?: LogContext): void {\n this._write(message, LogLevel.Info, LogLevelNumber.Info, context);\n }\n\n warn(message: string, context?: LogContext): void {\n this._write(message, LogLevel.Warn, LogLevelNumber.Warn, context);\n }\n\n error(message: string, context?: LogContext): void {\n this._write(message, LogLevel.Error, LogLevelNumber.Error, context);\n }\n\n withContext(context: LogContext): Logger {\n // Get the original log level by finding the key in logLevelNumberMap that matches this._levelNumber\n const originalLevel =\n [...logLevelNumberMap.entries()].find(([, value]) => value === this._levelNumber)?.[0] ?? LogLevel.Error;\n\n return new DefaultLogger(this._handler, originalLevel, this._mergeContext(context));\n }\n\n private _write(message: string, level: LogLevel, levelNumber: LogLevelNumber, context?: LogContext): void {\n if (levelNumber >= this._levelNumber) {\n this._handler(message, level, this._mergeContext(context));\n }\n }\n\n private _mergeContext(context?: LogContext): LogContext | undefined {\n if (!this._context) {\n return context ?? undefined;\n }\n\n return context ? { ...this._context, ...context } : this._context;\n }\n}\n","/**\n * Client-side stream routing.\n *\n * Maintains a map of turnId to ReadableStreamController. Routes decoded events\n * to the correct stream. Closes streams on terminal events, explicit close, or\n * error.\n */\n\nimport * as Ably from 'ably';\n\nimport { ErrorCode } from '../../errors.js';\nimport type { Logger } from '../../logger.js';\nimport type { TurnEntry } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Interface\n// ---------------------------------------------------------------------------\n\n/** Routes decoded events to the correct turn's ReadableStream. */\nexport interface StreamRouter<TEvent> {\n /** Register a new stream for a turnId. Returns the ReadableStream the consumer reads from. */\n createStream(turnId: string): ReadableStream<TEvent>;\n /** Close the stream for a turnId. Returns true if a stream existed. */\n closeStream(turnId: string): boolean;\n /** Error the stream for a turnId. The consumer's reader will reject with the given error. Returns true if a stream existed. */\n errorStream(turnId: string, error: Ably.ErrorInfo): boolean;\n /** Enqueue an event to the correct stream. Returns true if routed successfully. */\n route(turnId: string, event: TEvent): boolean;\n /** Whether a specific turnId has an active stream. */\n has(turnId: string): boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n// Spec: AIT-CT14\nclass DefaultStreamRouter<TEvent> implements StreamRouter<TEvent> {\n private readonly _turns = new Map<string, TurnEntry<TEvent>>();\n private readonly _isTerminal: (event: TEvent) => boolean;\n private readonly _logger: Logger;\n\n constructor(isTerminal: (event: TEvent) => boolean, logger: Logger) {\n this._isTerminal = isTerminal;\n this._logger = logger;\n }\n\n createStream(turnId: string): ReadableStream<TEvent> {\n this._logger.trace('StreamRouter.createStream();', { turnId });\n\n // Build stream+controller together. ReadableStream's start() runs synchronously\n // per spec, so the controller is captured before the constructor returns.\n const entry: { controller?: ReadableStreamDefaultController<TEvent> } = {};\n const stream = new ReadableStream<TEvent>({\n start(controller) {\n entry.controller = controller;\n },\n });\n if (!entry.controller) {\n throw new Ably.ErrorInfo(\n 'unable to create stream; ReadableStream start() was not called synchronously',\n ErrorCode.TransportSubscriptionError,\n 500,\n );\n }\n this._turns.set(turnId, { controller: entry.controller, turnId });\n return stream;\n }\n\n // Spec: AIT-CT14b\n closeStream(turnId: string): boolean {\n const turn = this._turns.get(turnId);\n if (!turn) return false;\n\n this._logger.debug('StreamRouter.closeStream(); closing stream', { turnId });\n try {\n turn.controller.close();\n } catch {\n /* consumer cancelled the stream */\n }\n this._turns.delete(turnId);\n return true;\n }\n\n // Spec: AIT-CT14c\n errorStream(turnId: string, error: Ably.ErrorInfo): boolean {\n const turn = this._turns.get(turnId);\n if (!turn) return false;\n\n this._logger.debug('StreamRouter.errorStream(); erroring stream', { turnId });\n try {\n turn.controller.error(error);\n } catch {\n /* consumer cancelled the stream */\n }\n this._turns.delete(turnId);\n return true;\n }\n\n // Spec: AIT-CT14a\n route(turnId: string, event: TEvent): boolean {\n const turn = this._turns.get(turnId);\n if (!turn) return false;\n\n try {\n turn.controller.enqueue(event);\n } catch {\n this._turns.delete(turnId);\n return false;\n }\n\n if (this._isTerminal(event)) {\n this.closeStream(turnId);\n }\n return true;\n }\n\n has(turnId: string): boolean {\n return this._turns.has(turnId);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a StreamRouter that routes decoded events to per-turn ReadableStreams.\n * @param isTerminal - Predicate that returns true for events that close the stream.\n * @param logger - Logger for diagnostic output.\n * @returns A new {@link StreamRouter} instance.\n */\nexport const createStreamRouter = <TEvent>(\n isTerminal: (event: TEvent) => boolean,\n logger: Logger,\n): StreamRouter<TEvent> => new DefaultStreamRouter(isTerminal, logger);\n","/**\n * Tree — materializes a branching conversation from a flat\n * oplog of Ably messages using serial-first ordering.\n *\n * Serial order (the total order assigned by Ably) is the primary mechanism\n * for linear message sequences. `x-ably-parent` and `x-ably-fork-of` headers\n * are only structurally meaningful at branch points — where the user is\n * interacting with a visible message and the client always has it loaded.\n *\n * `upsert()` is the sole mutation method. Messages can arrive in any order\n * (live subscription, history pages, seed data) and the tree produces the\n * correct `flattenNodes()` output once all messages are present.\n *\n * The tree owns conversation state. `flattenNodes()` returns the linear node\n * list for the currently selected branches — this is what the transport's\n * `getMessages()` delegates to.\n */\n\nimport type * as Ably from 'ably';\n\nimport { HEADER_FORK_OF, HEADER_PARENT } from '../../constants.js';\nimport { EventEmitter } from '../../event-emitter.js';\nimport type { Logger } from '../../logger.js';\nimport type { MessageNode, Tree, TurnLifecycleEvent } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Internal node type\n// ---------------------------------------------------------------------------\n\ninterface InternalNode<TMessage> {\n node: MessageNode<TMessage>;\n /** Insertion sequence — tiebreaker for null-serial messages. */\n insertSeq: number;\n}\n\n// ---------------------------------------------------------------------------\n// Internal interface — extended surface consumed by View\n// ---------------------------------------------------------------------------\n\n/** Internal tree surface used by View — not part of the public Tree API. */\nexport interface TreeInternal<TMessage> extends Tree<TMessage> {\n /**\n * Monotonic counter that increments on structural changes (node insert,\n * delete, serial promotion/reorder) but NOT on content-only updates\n * (existing node's message replaced). Allows the View to skip full\n * tree walks when only message content changed.\n */\n readonly structuralVersion: number;\n\n /**\n * Flatten the tree along selected branches into a linear node list.\n * The `selections` map provides the selected sibling's msgId at each\n * fork point, keyed by group root msgId. Fork points not present in\n * the map default to the latest sibling. If a selectedMsgId is not\n * found in the sibling group (stale/deleted), falls back to latest.\n */\n flattenNodes(selections: Map<string, string>): MessageNode<TMessage>[];\n\n /**\n * Get the \"group root\" msgId for a sibling group — the original message\n * that all forks in the group trace back to.\n */\n getGroupRoot(msgId: string): string;\n\n /**\n * Get the sibling group that `msgId` belongs to, as full MessageNode objects.\n * Allows callers to resolve index ↔ msgId without losing identity.\n */\n getSiblingNodes(msgId: string): MessageNode<TMessage>[];\n\n /** Forward a raw Ably message event to tree subscribers. */\n emitAblyMessage(msg: Ably.InboundMessage): void;\n /** Forward a turn lifecycle event to tree subscribers. */\n emitTurn(event: TurnLifecycleEvent): void;\n /** Register an active turn. */\n trackTurn(turnId: string, clientId: string): void;\n /** Unregister an active turn. */\n untrackTurn(turnId: string): void;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/** EventEmitter events map for the tree. */\ninterface TreeEventsMap {\n update: undefined;\n 'ably-message': Ably.InboundMessage;\n turn: TurnLifecycleEvent;\n}\n\n// Spec: AIT-CT13\nexport class DefaultTree<TMessage> implements TreeInternal<TMessage> {\n /** All nodes indexed by msgId (x-ably-msg-id). */\n private readonly _nodeIndex = new Map<string, InternalNode<TMessage>>();\n\n /**\n * All nodes sorted by serial (lexicographic). Null-serial messages\n * (optimistic inserts, seed data) sort after all serial-bearing messages,\n * ordered among themselves by insertion sequence.\n */\n private readonly _sortedList: InternalNode<TMessage>[] = [];\n\n /**\n * Parent index: parentId to set of child msgIds.\n * Nodes with no parent are indexed under the key `null`.\n */\n private readonly _parentIndex = new Map<string | undefined, Set<string>>();\n\n private readonly _emitter: EventEmitter<TreeEventsMap>;\n private readonly _logger: Logger;\n\n /** Active turns: turnId → clientId. */\n private readonly _turnClientIds = new Map<string, string>();\n\n /** Monotonically increasing counter for insertion sequence. */\n private _seqCounter = 0;\n\n /** Incremented on structural changes; unchanged on content-only updates. */\n private _structuralVersion = 0;\n\n get structuralVersion(): number {\n return this._structuralVersion;\n }\n\n constructor(logger: Logger) {\n this._logger = logger;\n this._emitter = new EventEmitter<TreeEventsMap>(logger);\n }\n\n // -------------------------------------------------------------------------\n // Sorted list maintenance\n // -------------------------------------------------------------------------\n\n /**\n * Compare two nodes for sorted list ordering.\n * Serial-bearing nodes sort by serial (lexicographic).\n * Null-serial nodes sort after all serial-bearing nodes.\n * Among null-serial nodes, sort by insertion sequence.\n * @param a - First node to compare.\n * @param b - Second node to compare.\n * @returns Negative if a sorts before b, positive if after, zero if equal.\n */\n // Spec: AIT-CT13a\n private _compareNodes(a: InternalNode<TMessage>, b: InternalNode<TMessage>): number {\n const sa = a.node.serial;\n const sb = b.node.serial;\n if (sa === undefined && sb === undefined) return a.insertSeq - b.insertSeq;\n if (sa === undefined) return 1; // a sorts after serial-bearing b\n if (sb === undefined) return -1; // b sorts after serial-bearing a\n if (sa < sb) return -1;\n if (sa > sb) return 1;\n return a.insertSeq - b.insertSeq; // same serial: preserve insertion order\n }\n\n /**\n * Insert a node into sortedList at the correct position via binary search.\n * @param internal - The node to insert.\n */\n private _insertSorted(internal: InternalNode<TMessage>): void {\n const serial = internal.node.serial;\n\n // Fast path: null-serial always appends to end (among other null-serials)\n if (serial === undefined) {\n this._sortedList.push(internal);\n return;\n }\n\n // Binary search for insertion point among serial-bearing nodes.\n let lo = 0;\n let hi = this._sortedList.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n const midNode = this._sortedList[mid];\n if (!midNode) break; // unreachable: mid is always in bounds\n if (this._compareNodes(midNode, internal) <= 0) {\n lo = mid + 1;\n } else {\n hi = mid;\n }\n }\n this._sortedList.splice(lo, 0, internal);\n }\n\n /**\n * Remove a node from sortedList.\n * @param internal - The node to remove.\n */\n private _removeSorted(internal: InternalNode<TMessage>): void {\n const idx = this._sortedList.indexOf(internal);\n if (idx !== -1) this._sortedList.splice(idx, 1);\n }\n\n // -------------------------------------------------------------------------\n // Parent index maintenance\n // -------------------------------------------------------------------------\n\n private _addToParentIndex(parentId: string | undefined, msgId: string): void {\n let set = this._parentIndex.get(parentId);\n if (!set) {\n set = new Set();\n this._parentIndex.set(parentId, set);\n }\n set.add(msgId);\n }\n\n private _removeFromParentIndex(parentId: string | undefined, msgId: string): void {\n const set = this._parentIndex.get(parentId);\n if (set) {\n set.delete(msgId);\n if (set.size === 0) this._parentIndex.delete(parentId);\n }\n }\n\n // -------------------------------------------------------------------------\n // Sibling grouping\n // -------------------------------------------------------------------------\n\n /**\n * Get the sibling group that `msgId` belongs to.\n *\n * A sibling group is: the original message + all messages whose `forkOf`\n * points to the original (or transitively to a sibling). We find the\n * group root by following `forkOf` chains to the earliest ancestor that\n * has no `forkOf` (or whose `forkOf` target doesn't share the same parent).\n * @param msgId - The msg-id to look up the sibling group for.\n * @returns The ordered list of sibling nodes.\n */\n // Spec: AIT-CT13b\n private _getSiblingGroup(msgId: string): MessageNode<TMessage>[] {\n const entry = this._nodeIndex.get(msgId);\n if (!entry) return [];\n\n // Find the \"original\" — the message at the root of the fork chain\n // that shares the same parentId. Guard against cycles in forkOf chains.\n let original = entry.node;\n const visitedGroup = new Set<string>([original.msgId]);\n while (original.forkOf) {\n if (visitedGroup.has(original.forkOf)) break; // cycle guard\n const forkTarget = this._nodeIndex.get(original.forkOf);\n if (!forkTarget || forkTarget.node.parentId !== original.parentId) break;\n original = forkTarget.node;\n visitedGroup.add(original.msgId);\n }\n\n // Collect all siblings: nodes with the same parentId that either\n // ARE the original, or have a forkOf chain leading to the original.\n const parentId = original.parentId;\n const originalId = original.msgId;\n const siblings: InternalNode<TMessage>[] = [];\n\n const candidateIds = this._parentIndex.get(parentId);\n if (candidateIds) {\n for (const childId of candidateIds) {\n const childEntry = this._nodeIndex.get(childId);\n if (childEntry && this._isSiblingOf(childEntry.node, originalId)) {\n siblings.push(childEntry);\n }\n }\n }\n\n // Sort by Ably serial (lexicographic). Messages without a serial\n // (optimistic inserts before server relay) sort after all serial-bearing\n // siblings — they represent the user's most recent action.\n siblings.sort((a, b) => this._compareNodes(a, b));\n return siblings.map((s) => s.node);\n }\n\n /**\n * Check if `node` belongs to the sibling group rooted at `originalId`.\n * A node is a sibling if it IS the original or its forkOf chain leads\n * to the original (with the same parentId).\n * @param node - The node to check.\n * @param originalId - The group root to match against.\n * @returns True if the node belongs to the sibling group.\n */\n private _isSiblingOf(node: MessageNode<TMessage>, originalId: string): boolean {\n if (node.msgId === originalId) return true;\n let current = node;\n const visited = new Set<string>([current.msgId]);\n while (current.forkOf) {\n if (current.forkOf === originalId) return true;\n if (visited.has(current.forkOf)) break; // cycle guard\n const target = this._nodeIndex.get(current.forkOf);\n if (!target) break;\n current = target.node;\n visited.add(current.msgId);\n }\n return false;\n }\n\n /**\n * Get the \"group root\" msgId for a sibling group — the original message\n * that all forks trace back to.\n * @param msgId - Any msg-id in the sibling group.\n * @returns The msg-id of the group root.\n */\n getGroupRoot(msgId: string): string {\n const entry = this._nodeIndex.get(msgId);\n if (!entry) return msgId;\n\n let current = entry.node;\n const visited = new Set<string>([current.msgId]);\n while (current.forkOf) {\n if (visited.has(current.forkOf)) break; // cycle guard\n const forkTarget = this._nodeIndex.get(current.forkOf);\n if (!forkTarget || forkTarget.node.parentId !== current.parentId) break;\n current = forkTarget.node;\n visited.add(current.msgId);\n }\n return current.msgId;\n }\n\n // -------------------------------------------------------------------------\n // Public query methods\n // -------------------------------------------------------------------------\n\n flattenNodes(selections: Map<string, string>): MessageNode<TMessage>[] {\n this._logger.trace('DefaultTree.flattenNodes();');\n const result: MessageNode<TMessage>[] = [];\n const currentPath = new Set<string>();\n // Track which sibling groups we've already resolved to avoid\n // re-resolving for every member of the group.\n const resolvedGroups = new Map<string, string>(); // groupRootId → selected msgId\n\n for (const internal of this._sortedList) {\n const node = internal.node;\n const { msgId, parentId } = node;\n\n // Step 1: Check parent reachability.\n if (parentId !== undefined && !currentPath.has(parentId)) {\n continue;\n }\n\n // Step 2: Check sibling selection.\n const group = this._getSiblingGroup(msgId);\n if (group.length > 1) {\n const groupRootId = this.getGroupRoot(msgId);\n let selectedId = resolvedGroups.get(groupRootId);\n if (selectedId === undefined) {\n const preferredId = selections.get(groupRootId);\n // Verify the preferred msgId is in the group, otherwise default to latest\n if (preferredId && group.some((n) => n.msgId === preferredId)) {\n selectedId = preferredId;\n } else {\n const latest = group.at(-1);\n if (!latest) break; // unreachable: group.length > 1\n selectedId = latest.msgId;\n }\n resolvedGroups.set(groupRootId, selectedId);\n }\n if (msgId !== selectedId) {\n continue;\n }\n }\n\n currentPath.add(msgId);\n result.push(node);\n }\n\n return result;\n }\n\n getSiblings(msgId: string): TMessage[] {\n this._logger.trace('DefaultTree.getSiblings();', { msgId });\n return this._getSiblingGroup(msgId).map((n) => n.message);\n }\n\n getSiblingNodes(msgId: string): MessageNode<TMessage>[] {\n return this._getSiblingGroup(msgId);\n }\n\n hasSiblings(msgId: string): boolean {\n return this._getSiblingGroup(msgId).length > 1;\n }\n\n getNode(msgId: string): MessageNode<TMessage> | undefined {\n this._logger.trace('DefaultTree.getNode();', { msgId });\n return this._nodeIndex.get(msgId)?.node;\n }\n\n getHeaders(msgId: string): Record<string, string> | undefined {\n this._logger.trace('DefaultTree.getHeaders();', { msgId });\n return this._nodeIndex.get(msgId)?.node.headers;\n }\n\n // -------------------------------------------------------------------------\n // Mutation\n // -------------------------------------------------------------------------\n\n upsert(msgId: string, message: TMessage, headers: Record<string, string>, serial?: string): void {\n const parentId = headers[HEADER_PARENT] ?? undefined;\n const forkOf = headers[HEADER_FORK_OF] ?? undefined;\n\n const existing = this._nodeIndex.get(msgId);\n if (existing) {\n // Update in place — message content may have changed (e.g. streaming).\n // Only update headers if the new headers are non-empty (prevents\n // streaming updates from erasing canonical headers).\n existing.node.message = message;\n if (Object.keys(headers).length > 0) {\n existing.node.headers = { ...headers };\n }\n // Spec: AIT-CT13d\n // Promote serial: optimistic (null) → server-assigned on relay.\n if (serial && !existing.node.serial) {\n this._logger.debug('Tree.upsert(); promoting serial', { msgId, serial });\n existing.node.serial = serial;\n // Re-sort: remove from current position, re-insert at correct position.\n this._removeSorted(existing);\n this._insertSorted(existing);\n this._structuralVersion++;\n }\n this._emitter.emit('update');\n return;\n }\n\n this._logger.trace('Tree.upsert(); inserting new node', { msgId, parentId, forkOf });\n\n const node: MessageNode<TMessage> = {\n kind: 'message',\n message,\n msgId,\n parentId,\n forkOf,\n headers: { ...headers },\n serial,\n };\n\n const internal: InternalNode<TMessage> = { node, insertSeq: this._seqCounter++ };\n this._nodeIndex.set(msgId, internal);\n this._addToParentIndex(parentId, msgId);\n this._insertSorted(internal);\n this._structuralVersion++;\n this._emitter.emit('update');\n }\n\n delete(msgId: string): void {\n const entry = this._nodeIndex.get(msgId);\n if (!entry) return;\n\n this._logger.debug('Tree.delete();', { msgId });\n\n const { node } = entry;\n\n // Remove from parent index\n this._removeFromParentIndex(node.parentId, msgId);\n\n // Remove from sorted list\n this._removeSorted(entry);\n\n // Remove from primary index\n this._nodeIndex.delete(msgId);\n\n // Children are NOT deleted — they become unreachable in flattenNodes()\n // because their parent is no longer on the active path.\n this._structuralVersion++;\n this._emitter.emit('update');\n }\n\n // -------------------------------------------------------------------------\n // Events\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CT17\n getActiveTurnIds(): Map<string, Set<string>> {\n this._logger.trace('DefaultTree.getActiveTurnIds();');\n const result = new Map<string, Set<string>>();\n for (const [turnId, clientId] of this._turnClientIds) {\n let set = result.get(clientId);\n if (!set) {\n set = new Set<string>();\n result.set(clientId, set);\n }\n set.add(turnId);\n }\n return result;\n }\n\n // Spec: AIT-CT8b, AIT-CT8e\n on(event: 'update', handler: () => void): () => void;\n on(event: 'ably-message', handler: (msg: Ably.InboundMessage) => void): () => void;\n on(event: 'turn', handler: (event: TurnLifecycleEvent) => void): () => void;\n on(\n event: 'update' | 'ably-message' | 'turn',\n handler: (() => void) | ((msg: Ably.InboundMessage) => void) | ((event: TurnLifecycleEvent) => void),\n ): () => void {\n // CAST: overload signatures enforce correct handler types per event name.\n const cb = handler as (arg: TreeEventsMap[keyof TreeEventsMap]) => void;\n this._emitter.on(event, cb);\n return () => {\n this._emitter.off(event, cb);\n };\n }\n\n // -------------------------------------------------------------------------\n // Internal methods (called by the transport, not part of Tree interface)\n // -------------------------------------------------------------------------\n\n /**\n * Forward a raw Ably message event to tree subscribers.\n * @param msg - The raw Ably message to emit.\n */\n emitAblyMessage(msg: Ably.InboundMessage): void {\n this._logger.trace('DefaultTree.emitAblyMessage();');\n this._emitter.emit('ably-message', msg);\n }\n\n /**\n * Forward a turn lifecycle event to tree subscribers.\n * @param event - The turn lifecycle event to emit.\n */\n emitTurn(event: TurnLifecycleEvent): void {\n this._logger.trace('DefaultTree.emitTurn();', { turnId: event.turnId });\n this._emitter.emit('turn', event);\n }\n\n /**\n * Register an active turn.\n * @param turnId - The turn's unique identifier.\n * @param clientId - The client that owns the turn.\n */\n trackTurn(turnId: string, clientId: string): void {\n this._logger.trace('DefaultTree.trackTurn();', { turnId, clientId });\n this._turnClientIds.set(turnId, clientId);\n }\n\n /**\n * Unregister an active turn.\n * @param turnId - The turn to untrack.\n */\n untrackTurn(turnId: string): void {\n this._logger.trace('DefaultTree.untrackTurn();', { turnId });\n this._turnClientIds.delete(turnId);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a Tree that materializes branching history from a flat oplog.\n * @param logger - Logger for diagnostic output.\n * @returns A new {@link DefaultTree} instance. The transport uses DefaultTree\n * directly for internal methods (emitAblyMessage, emitTurn, trackTurn, untrackTurn).\n * Public consumers see the narrower {@link Tree} interface.\n */\nexport const createTree = <TMessage>(logger: Logger): DefaultTree<TMessage> => new DefaultTree(logger);\n","/**\n * decodeHistory — load conversation history from an Ably channel and\n * return decoded messages as a paginated HistoryPage result.\n *\n * Uses a fresh decoder (not shared with the live subscription) to avoid\n * state conflicts. Per-turn accumulators handle interleaved turns correctly.\n *\n * The `limit` option controls the number of **messages** returned,\n * not the number of Ably wire messages fetched. The implementation pages\n * back through Ably history until `limit` complete messages have\n * been assembled. Partial turns (incomplete at the page boundary) are\n * buffered internally and completed when `next()` fetches more pages.\n *\n * Only completed messages appear in `items`. A message is complete when\n * its terminal event (finish/abort/error) has been received.\n *\n * Because Ably history returns newest-first while the decoder requires\n * chronological order, all collected Ably messages are re-decoded from\n * oldest to newest at the point a result is built. This handles turns\n * that span page boundaries correctly. The fetch loop uses a cheap\n * header-based completion counter to decide when to stop paging, so the\n * full decode runs exactly once per traversal regardless of page count.\n */\n\nimport type * as Ably from 'ably';\n\nimport {\n HEADER_AMEND,\n HEADER_DISCRETE,\n HEADER_MSG_ID,\n HEADER_STATUS,\n HEADER_STREAM,\n HEADER_TURN_ID,\n} from '../../constants.js';\nimport type { Logger } from '../../logger.js';\nimport { getHeaders } from '../../utils.js';\nimport type { Codec, DecoderOutput, MessageAccumulator } from '../codec/types.js';\nimport type { HistoryPage, LoadHistoryOptions } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Shared state across pages within one history traversal\n// ---------------------------------------------------------------------------\n\ninterface HistoryState<TEvent, TMessage> {\n codec: Codec<TEvent, TMessage>;\n /** All raw Ably messages collected so far, in newest-first order (as received from Ably). */\n rawMessages: Ably.InboundMessage[];\n /** How many completed messages have been returned to the consumer so far. */\n returnedCount: number;\n /** How many raw Ably messages have been returned to the consumer so far. */\n returnedRawCount: number;\n /** The last Ably page cursor for continued pagination. */\n lastAblyPage: Ably.PaginatedResult<Ably.InboundMessage> | undefined;\n /**\n * Cached result of the last {@link decodeAll} call, reused while\n * `rawMessages` is unchanged. Invalidated implicitly by comparing\n * {@link cachedAtRawLength} against `rawMessages.length`; `rawMessages`\n * is append-only within a traversal so length is a sufficient key.\n */\n cachedDecode: DecodedItem<TMessage>[] | undefined;\n /** `rawMessages.length` at the time {@link cachedDecode} was produced. */\n cachedAtRawLength: number;\n /**\n * `x-ably-msg-id`s for which the decoder has something to produce output\n * from: any `message.create` / `message.update` / `message.append` with\n * `x-ably-stream: \"true\"` (establishes a tracker via create or\n * first-contact), or a `message.create` carrying `x-ably-discrete` (a\n * discrete message, created and terminated in one wire message).\n */\n startedMsgIds: Set<string>;\n /**\n * `x-ably-msg-id`s with a terminal wire signal: either `x-ably-discrete`\n * on a `message.create` (discrete message) or `x-ably-status: \"finished\"`\n * / `\"aborted\"` on any action (closed stream).\n */\n terminatedMsgIds: Set<string>;\n /**\n * `x-ably-msg-id`s that are both started AND terminated - ready to appear\n * in the decoded output. The fetch loop reads this set's size to decide\n * when to stop paging, avoiding a full decode per page. Maintained\n * incrementally by {@link countNewCompletions}. Grows monotonically.\n */\n completedMsgIds: Set<string>;\n logger: Logger;\n}\n\n/** A completed message paired with its canonical wire headers and serial. */\ninterface DecodedItem<TMessage> {\n message: TMessage;\n headers: Record<string, string>;\n /** Ably serial from the first Ably message for this domain message. */\n serial: string;\n}\n\n// ---------------------------------------------------------------------------\n// Decode all collected messages from scratch (chronological order)\n// ---------------------------------------------------------------------------\n\n/**\n * Re-decode all collected raw messages into completed domain messages.\n * @param state - The shared history traversal state.\n * @returns Completed messages in newest-first order.\n */\nconst decodeAll = <TEvent, TMessage>(state: HistoryState<TEvent, TMessage>): DecodedItem<TMessage>[] => {\n // Reverse to chronological (oldest first)\n const chronological = [...state.rawMessages].toReversed();\n\n // Fresh decoder and per-turn accumulators for each full re-decode.\n const decoder = state.codec.createDecoder();\n const turns = new Map<\n string,\n {\n accumulator: MessageAccumulator<TEvent, TMessage>;\n firstSeen: number;\n /** Headers from the first Ably message per x-ably-msg-id within this turn. */\n msgHeaders: Map<string, Record<string, string>>;\n /** Ably serial from the first Ably message per x-ably-msg-id within this turn. */\n msgSerials: Map<string, string>;\n }\n >();\n const defaultAccumulator = state.codec.createAccumulator();\n let orderCounter = 0;\n\n // Headers and serials for non-turn discrete messages, keyed by x-ably-msg-id.\n const discreteHeaders = new Map<string, Record<string, string>>();\n const discreteSerials = new Map<string, string>();\n // Track which msgId produced each non-turn discrete message output (in order).\n const discreteMsgIds: string[] = [];\n\n // Cross-turn event targets to complete after all events are processed.\n // Deferred so that finish/abort events that follow the update in serial\n // order can still process on the active message (e.g. applying messageMetadata).\n const deferredCompletions: { accumulator: MessageAccumulator<TEvent, TMessage>; messageId: string }[] = [];\n\n for (const msg of chronological) {\n const outputs: DecoderOutput<TEvent, TMessage>[] = decoder.decode(msg);\n const headers = getHeaders(msg);\n const turnId = headers[HEADER_TURN_ID];\n const msgId = headers[HEADER_MSG_ID];\n const serial = msg.serial;\n const amendTarget = headers[HEADER_AMEND];\n\n // Cross-turn events target an existing message from a different turn.\n // Route to the owning turn's accumulator via initMessage lifecycle.\n if (amendTarget) {\n for (const turn of turns.values()) {\n if (turn.msgHeaders.has(amendTarget)) {\n const headerKeys = [...turn.msgHeaders.keys()];\n const msgIndex = headerKeys.indexOf(amendTarget);\n const currentMsg = msgIndex === -1 ? undefined : turn.accumulator.messages[msgIndex];\n if (currentMsg) {\n turn.accumulator.initMessage(amendTarget, currentMsg);\n }\n turn.accumulator.processOutputs(outputs);\n deferredCompletions.push({ accumulator: turn.accumulator, messageId: amendTarget });\n break;\n }\n }\n continue;\n }\n\n if (turnId) {\n let turn = turns.get(turnId);\n if (!turn) {\n turn = {\n accumulator: state.codec.createAccumulator(),\n firstSeen: orderCounter++,\n msgHeaders: new Map(),\n msgSerials: new Map(),\n };\n turns.set(turnId, turn);\n }\n // Capture headers per msg-id within this turn. Update on later\n // messages too (e.g. closing append overrides status from\n // \"streaming\" to \"finished\"/\"aborted\"). Only merge when the\n // incoming message has non-empty headers.\n if (msgId) {\n const existing = turn.msgHeaders.get(msgId);\n if (!existing) {\n turn.msgHeaders.set(msgId, { ...headers });\n if (serial) turn.msgSerials.set(msgId, serial);\n } else if (Object.keys(headers).length > 0) {\n Object.assign(existing, headers);\n }\n }\n turn.accumulator.processOutputs(outputs);\n } else {\n defaultAccumulator.processOutputs(outputs);\n\n // Capture headers and serial for non-turn discrete messages by x-ably-msg-id.\n for (const output of outputs) {\n if (output.kind === 'message' && msgId) {\n discreteMsgIds.push(msgId);\n const existingDiscrete = discreteHeaders.get(msgId);\n if (!existingDiscrete) {\n discreteHeaders.set(msgId, { ...headers });\n if (serial) discreteSerials.set(msgId, serial);\n } else if (Object.keys(headers).length > 0) {\n Object.assign(existingDiscrete, headers);\n }\n }\n }\n }\n }\n\n // Complete any messages that were re-activated for cross-turn updates.\n // Idempotent — if finish already removed the message from active tracking,\n // completeMessage is a no-op.\n for (const { accumulator, messageId } of deferredCompletions) {\n accumulator.completeMessage(messageId);\n }\n\n // Collect completed messages in chronological order (oldest first) by turn.\n const completed: DecodedItem<TMessage>[] = [];\n\n // Default accumulator messages: pair with their discrete headers by position.\n for (const [i, msg] of defaultAccumulator.completedMessages.entries()) {\n const mid = discreteMsgIds[i];\n completed.push({\n message: msg,\n headers: mid ? (discreteHeaders.get(mid) ?? {}) : {},\n serial: mid ? (discreteSerials.get(mid) ?? '') : '',\n });\n }\n\n const sorted = [...turns.values()].toSorted((a, b) => a.firstSeen - b.firstSeen);\n for (const turn of sorted) {\n // Assign headers and serials to each completed message in this turn.\n // The turn's msgHeaders map is keyed by x-ably-msg-id and ordered by\n // first-seen. Completed messages are matched positionally.\n const headerEntries = [...turn.msgHeaders.entries()];\n let headerIdx = 0;\n\n for (const msg of turn.accumulator.completedMessages) {\n const entry = headerEntries[headerIdx];\n if (entry) {\n const [mid, hdrs] = entry;\n completed.push({\n message: msg,\n headers: hdrs,\n serial: turn.msgSerials.get(mid) ?? '',\n });\n headerIdx++;\n } else {\n completed.push({ message: msg, headers: {}, serial: '' });\n }\n }\n }\n\n // Reverse to newest-first. The consumer slices from the front for the\n // most recent page, and progressively deeper for older pages.\n return completed.toReversed();\n};\n\n/**\n * Cached wrapper around {@link decodeAll}. Returns the previous result when\n * `rawMessages` hasn't changed since the last decode; otherwise re-decodes\n * and updates the cache. The cache key is `rawMessages.length` because\n * `rawMessages` is append-only within a traversal.\n * @param state - The shared history traversal state.\n * @returns Completed messages in newest-first order.\n */\nconst decodeAllCached = <TEvent, TMessage>(state: HistoryState<TEvent, TMessage>): DecodedItem<TMessage>[] => {\n if (state.cachedDecode && state.cachedAtRawLength === state.rawMessages.length) {\n return state.cachedDecode;\n }\n const result = decodeAll(state);\n state.cachedDecode = result;\n state.cachedAtRawLength = state.rawMessages.length;\n return result;\n};\n\n// ---------------------------------------------------------------------------\n// Incremental completion counting (avoids full decode inside the fetch loop)\n// ---------------------------------------------------------------------------\n\n/**\n * Scan newly-added raw messages and track which `x-ably-msg-id`s have\n * become complete. Used by {@link fetchUntilLimit} to decide when enough\n * completed messages have been collected, without running the decoder.\n *\n * A msg-id is considered complete only when BOTH of these have been seen:\n * - a \"start\" signal: either `x-ably-discrete` on a `message.create`\n * (discrete messages are created and terminated by the same wire\n * message), OR any `message.create` / `message.update` / `message.append`\n * with `x-ably-stream: \"true\"` (the decoder establishes a tracker via\n * create or first-contact).\n * - a \"terminal\" signal: `x-ably-discrete` on the create, or\n * `x-ably-status: \"finished\"` / `\"aborted\"` on any later action.\n *\n * Why update and append count as starts: Ably history can compact a live\n * `create + append + ... + append{status:finished}` sequence into a single\n * `message.update` with `STREAM=true` and `STATUS=finished`. The decoder\n * handles that in {@link _decodeUpdate} via first-contact. Counting only\n * `message.create` as a start would cause the fetch loop to page past a\n * compacted turn without ever marking it complete.\n *\n * Requiring both halves matters when a streaming turn spans a page\n * boundary: the terminal arrives in the newer page (fetched first) while\n * the start sits in an older page. Counting the terminal alone would stop\n * the fetch loop prematurely - the decoder would have no stream state to\n * resolve, and the message wouldn't make it into the result.\n *\n * Messages skipped for counting:\n * - Missing `x-ably-msg-id`: lifecycle events not tied to a domain message.\n * - `x-ably-amend` set: amendments target an existing message, not a new\n * completion.\n * - `message.delete`: clears the tracker, doesn't produce output.\n *\n * Known edge case: if Ably history is truncated and a terminal survives\n * while every start signal for its msg-id has rolled off, the counter will\n * never mark that `msg-id` complete. The loop keeps fetching until it runs\n * out of pages, then returns whatever the decoder actually produced.\n * Matches the existing behaviour for the same truncation scenario.\n * @param state - The shared history traversal state.\n * @param newMessages - The Ably messages just pushed onto `state.rawMessages`.\n */\nconst countNewCompletions = <TEvent, TMessage>(\n state: HistoryState<TEvent, TMessage>,\n newMessages: readonly Ably.InboundMessage[],\n): void => {\n for (const msg of newMessages) {\n const headers = getHeaders(msg);\n const msgId = headers[HEADER_MSG_ID];\n if (!msgId) continue;\n // Amendments target an existing message, not a new completion.\n // Defensive: no current encoder path produces an amendment carrying\n // HEADER_STREAM=true, HEADER_STATUS, or HEADER_DISCRETE.\n if (headers[HEADER_AMEND]) continue;\n\n const action = msg.action;\n const isDiscreteCreate = action === 'message.create' && HEADER_DISCRETE in headers;\n // Any content-producing action on a streamed serial counts as a start:\n // the decoder uses create or first-contact (update/append) to establish\n // its tracker. Delete clears tracker state and emits nothing, so it\n // never counts as a start.\n const hasStreamContent =\n headers[HEADER_STREAM] === 'true' &&\n (action === 'message.create' || action === 'message.update' || action === 'message.append');\n const status = headers[HEADER_STATUS];\n const isTerminal = status === 'finished' || status === 'aborted';\n\n if (isDiscreteCreate || hasStreamContent) state.startedMsgIds.add(msgId);\n if (isDiscreteCreate || isTerminal) state.terminatedMsgIds.add(msgId);\n if (state.startedMsgIds.has(msgId) && state.terminatedMsgIds.has(msgId)) {\n state.completedMsgIds.add(msgId);\n }\n }\n};\n\n// ---------------------------------------------------------------------------\n// Fetch Ably pages until we have enough completed messages\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch Ably history pages until we have enough completed messages.\n *\n * The loop uses {@link countNewCompletions} to decide when to stop -\n * a cheap O(new messages) header scan - rather than running the full\n * decoder per page. The decoder runs exactly once later, in\n * {@link buildResult}, against the fully-collected `rawMessages`.\n * @param state - The shared history traversal state.\n * @param ablyPage - The current Ably paginated result to start from.\n * @param limit - Target number of completed messages beyond what has already been returned.\n */\nconst fetchUntilLimit = async <TEvent, TMessage>(\n state: HistoryState<TEvent, TMessage>,\n ablyPage: Ably.PaginatedResult<Ably.InboundMessage>,\n limit: number,\n): Promise<void> => {\n state.rawMessages.push(...ablyPage.items);\n state.lastAblyPage = ablyPage;\n countNewCompletions(state, ablyPage.items);\n\n const target = state.returnedCount + limit;\n while (state.completedMsgIds.size < target && ablyPage.hasNext()) {\n state.logger.debug('decodeHistory.fetchUntilLimit(); fetching next page', {\n collected: state.rawMessages.length,\n completed: state.completedMsgIds.size,\n });\n const nextPage = await ablyPage.next();\n if (!nextPage) break;\n ablyPage = nextPage;\n state.rawMessages.push(...nextPage.items);\n state.lastAblyPage = nextPage;\n countNewCompletions(state, nextPage.items);\n }\n};\n\n// ---------------------------------------------------------------------------\n// Build HistoryPage result from current state\n// ---------------------------------------------------------------------------\n\n/**\n * Build a HistoryPage from the current decode state.\n * @param state - The shared history traversal state.\n * @param limit - Max messages per page.\n * @returns A page of decoded history with a `next()` cursor.\n */\nconst buildResult = <TEvent, TMessage>(state: HistoryState<TEvent, TMessage>, limit: number): HistoryPage<TMessage> => {\n // allCompleted is newest-first. Slice from returnedCount for this page,\n // then reverse to chronological for display.\n const allCompleted = decodeAllCached(state);\n\n const pageSlice = allCompleted.slice(state.returnedCount, state.returnedCount + limit);\n const chronSlice = [...pageSlice].toReversed();\n state.returnedCount += pageSlice.length;\n\n const moreCompleted = allCompleted.length > state.returnedCount;\n const moreAblyPages = state.lastAblyPage?.hasNext() ?? false;\n\n // Raw Ably messages for this page in chronological order.\n const newRawCount = state.rawMessages.length - state.returnedRawCount;\n const rawSlice = newRawCount > 0 ? state.rawMessages.slice(state.returnedRawCount).toReversed() : [];\n state.returnedRawCount = state.rawMessages.length;\n\n return {\n items: chronSlice.map((d) => ({ message: d.message, headers: d.headers, serial: d.serial })),\n rawMessages: rawSlice,\n hasNext: () => moreCompleted || moreAblyPages,\n next: async () => {\n if (moreCompleted) {\n return buildResult(state, limit);\n }\n if (!moreAblyPages || !state.lastAblyPage) return;\n const nextAbly = await state.lastAblyPage.next();\n if (!nextAbly) return;\n await fetchUntilLimit(state, nextAbly, limit);\n return buildResult(state, limit);\n },\n };\n};\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Load conversation history from a channel and return decoded messages.\n *\n * Attaches the channel if not already attached, then calls\n * `channel.history({ untilAttach: true })` to guarantee no gap between\n * historical and live messages. The attach is idempotent.\n *\n * The `limit` option controls the number of complete messages\n * returned per page, not the number of Ably wire messages fetched.\n * @param channel - The Ably channel to load history from.\n * @param codec - The codec for decoding wire messages into domain messages.\n * @param options - Pagination options.\n * @param logger - Logger for diagnostic output.\n * @returns The first page of decoded history.\n */\n// Spec: AIT-CT11, AIT-CT11b\nexport const decodeHistory = async <TEvent, TMessage>(\n channel: Ably.RealtimeChannel,\n codec: Codec<TEvent, TMessage>,\n options: LoadHistoryOptions | undefined,\n logger: Logger,\n): Promise<HistoryPage<TMessage>> => {\n const limit = options?.limit ?? 100;\n const state: HistoryState<TEvent, TMessage> = {\n codec,\n rawMessages: [],\n returnedCount: 0,\n returnedRawCount: 0,\n lastAblyPage: undefined,\n cachedDecode: undefined,\n cachedAtRawLength: 0,\n startedMsgIds: new Set<string>(),\n terminatedMsgIds: new Set<string>(),\n completedMsgIds: new Set<string>(),\n logger,\n };\n\n logger.trace('decodeHistory();', { limit });\n\n // Request more Ably messages than the domain limit to account for\n // the many-to-one ratio (multiple wire messages per message).\n const wireLimit = limit * 10;\n\n await channel.attach();\n const ablyPage = await channel.history({ untilAttach: true, limit: wireLimit });\n await fetchUntilLimit(state, ablyPage, limit);\n return buildResult(state, limit);\n};\n","/**\n * DefaultView — a paginated, branch-aware projection over the Tree.\n *\n * Wraps a Tree and manages a pagination window that controls which nodes\n * are visible to the UI. New live messages appear immediately; older messages\n * are revealed progressively via `loadOlder()`.\n *\n * Each View owns its own branch selection state and pagination window,\n * allowing multiple independent Views over the same Tree.\n *\n * Events are scoped to the visible window — 'update' only fires when the\n * visible output changes, 'ably-message' only for messages corresponding to\n * visible nodes, and 'turn' only for turns with visible messages.\n */\n\nimport * as Ably from 'ably';\n\nimport { EVENT_TURN_END, EVENT_TURN_START, HEADER_MSG_ID, HEADER_TURN_ID } from '../../constants.js';\nimport { ErrorCode } from '../../errors.js';\nimport { EventEmitter } from '../../event-emitter.js';\nimport type { Logger } from '../../logger.js';\nimport { getHeaders } from '../../utils.js';\nimport type { Codec } from '../codec/types.js';\nimport { decodeHistory } from './decode-history.js';\nimport type { TreeInternal } from './tree.js';\nimport type {\n ActiveTurn,\n EventsNode,\n HistoryPage,\n MessageNode,\n SendOptions,\n TurnLifecycleEvent,\n View,\n} from './types.js';\n\n// ---------------------------------------------------------------------------\n// Events map\n// ---------------------------------------------------------------------------\n\ninterface ViewEventsMap {\n update: undefined;\n 'ably-message': Ably.InboundMessage;\n turn: TurnLifecycleEvent;\n}\n\n// ---------------------------------------------------------------------------\n// Send delegate\n// ---------------------------------------------------------------------------\n\n/**\n * Internal delegate function provided by the transport for executing sends.\n * The View pre-computes the visible branch history and passes it directly,\n * so the delegate has no back-reference to the View.\n * When `eventNodes` is provided, the transport includes them in the POST body\n * for the server to publish as cross-turn events.\n */\nexport type SendDelegate<TEvent, TMessage> = (\n input: TMessage | TMessage[],\n options: SendOptions | undefined,\n history: MessageNode<TMessage>[],\n eventNodes?: EventsNode<TEvent>[],\n) => Promise<ActiveTurn<TEvent>>;\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/** Options for creating a View. */\nexport interface ViewOptions<TEvent, TMessage> {\n /** The tree to project. */\n tree: TreeInternal<TMessage>;\n /** The Ably channel to load history from. */\n channel: Ably.RealtimeChannel;\n /** The codec for decoding history messages. */\n codec: Codec<TEvent, TMessage>;\n /** Delegate for executing sends through the transport. */\n sendDelegate: SendDelegate<TEvent, TMessage>;\n /** Logger for diagnostic output. */\n logger: Logger;\n /** Called when the view is closed, allowing the owner to clean up references. */\n onClose?: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Branch selection\n// ---------------------------------------------------------------------------\n\n/**\n * Tagged union representing why a branch was selected.\n * Stored per group root in the View's `_branchSelections` map.\n */\ntype BranchSelection =\n /** Explicit navigation via `select()`. */\n | { kind: 'user'; selectedId: string }\n /** This view initiated a fork (edit or regenerate) — auto-selected the result. */\n | { kind: 'auto'; selectedId: string }\n /** An external fork appeared — pinned to the currently-visible sibling to prevent drift. */\n | { kind: 'pinned'; selectedId: string }\n /** This view's `regenerate()` is in flight — select newest when turn's response arrives. */\n | { kind: 'pending'; turnId: string };\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nexport class DefaultView<TEvent, TMessage> implements View<TEvent, TMessage> {\n private readonly _tree: TreeInternal<TMessage>;\n private readonly _channel: Ably.RealtimeChannel;\n private readonly _codec: Codec<TEvent, TMessage>;\n private readonly _sendDelegate: SendDelegate<TEvent, TMessage>;\n private readonly _logger: Logger;\n private readonly _emitter: EventEmitter<ViewEventsMap>;\n private readonly _onClose?: () => void;\n\n /**\n * View-local branch selections: group root msgId → selection intent.\n * Fork points not present here default to the latest sibling.\n * Replaces the previous numeric-index _selections and _pendingForkSelections\n * with a single tagged-union map that carries the selected msgId (not index)\n * and the reason for the selection.\n */\n private readonly _branchSelections = new Map<string, BranchSelection>();\n\n /** Spec: AIT-CT11c — msg-ids loaded from history but not yet revealed to the UI. */\n private readonly _withheldMsgIds = new Set<string>();\n\n /** Snapshot of visible msgIds — used to detect structural changes and for selection pinning. */\n private _lastVisibleIds: string[] = [];\n\n /** Snapshot of visible message references — used to detect in-place content updates (streaming). */\n private _lastVisibleMessages: TMessage[] = [];\n\n /** Cached set of turn IDs present on the visible branch — avoids recomputing flattenNodes() on turn events. */\n private _lastVisibleTurnIds = new Set<string>();\n\n /** Whether there are more history pages to fetch from the channel. */\n private _hasMoreHistory = false;\n\n /** Internal state for continuing history pagination. */\n private _lastHistoryPage: HistoryPage<TMessage> | undefined;\n\n /** Buffer of withheld nodes, drained newest-first by successive loadOlder() calls. */\n private readonly _withheldBuffer: MessageNode<TMessage>[] = [];\n\n /** Unsubscribe functions for tree event subscriptions. */\n private readonly _unsubs: (() => void)[] = [];\n\n /**\n * Cached result of the last flattenNodes computation. Public `flattenNodes()`\n * returns this in O(1); internal callers use `_computeFlatNodes()` when a\n * fresh tree walk is needed (structural changes, selection changes, history reveal).\n */\n private _cachedNodes: MessageNode<TMessage>[] = [];\n\n /** Last seen tree structural version - used to distinguish content-only from structural updates. */\n private _lastStructuralVersion = -1;\n\n private _loadingOlder = false;\n private _processingHistory = false;\n private _closed = false;\n\n constructor(options: ViewOptions<TEvent, TMessage>) {\n this._tree = options.tree;\n this._channel = options.channel;\n this._codec = options.codec;\n this._sendDelegate = options.sendDelegate;\n this._onClose = options.onClose;\n this._logger = options.logger.withContext({ component: 'View' });\n this._logger.trace('DefaultView();');\n this._emitter = new EventEmitter<ViewEventsMap>(this._logger);\n\n // Compute initial cache and snapshot visible state\n this._cachedNodes = this._computeFlatNodes();\n this._lastStructuralVersion = this._tree.structuralVersion;\n this._updateVisibleSnapshot(this._cachedNodes);\n\n // Subscribe to tree events and re-emit scoped versions\n this._unsubs.push(\n this._tree.on('update', () => {\n this._onTreeUpdate();\n }),\n this._tree.on('ably-message', (msg) => {\n this._onTreeAblyMessage(msg);\n }),\n this._tree.on('turn', (event) => {\n this._onTreeTurn(event);\n }),\n );\n }\n\n // -------------------------------------------------------------------------\n // Public query methods\n // -------------------------------------------------------------------------\n\n getMessages(): TMessage[] {\n return this.flattenNodes().map((n) => n.message);\n }\n\n // Spec: AIT-CT9, AIT-CT11c\n flattenNodes(): MessageNode<TMessage>[] {\n return this._cachedNodes;\n }\n\n /**\n * Walk the tree and compute a fresh visible node list, applying branch\n * selections and withheld-message filtering. Use this instead of the\n * public `flattenNodes()` when the cache may be stale (structural\n * changes, selection changes, history reveal).\n * @returns A fresh array of visible nodes.\n */\n private _computeFlatNodes(): MessageNode<TMessage>[] {\n const nodes = this._tree.flattenNodes(this._resolveSelections());\n if (this._withheldMsgIds.size === 0) return nodes;\n return nodes.filter((n) => !this._withheldMsgIds.has(n.msgId));\n }\n\n hasOlder(): boolean {\n return this._withheldBuffer.length > 0 || this._hasMoreHistory;\n }\n\n async loadOlder(limit = 100): Promise<void> {\n if (this._closed || this._loadingOlder) return;\n this._loadingOlder = true;\n this._logger.trace('DefaultView.loadOlder();', { limit });\n\n try {\n // Drain withheld buffer first (older messages, released newest-first)\n if (this._withheldBuffer.length > 0) {\n const batch = this._withheldBuffer.splice(-limit, limit);\n this._releaseWithheld(batch);\n return;\n }\n\n // Buffer exhausted — load from channel history\n if (!this._hasMoreHistory && !this._lastHistoryPage) {\n // First load\n await this._loadFirstPage(limit);\n return;\n }\n\n if (!this._hasMoreHistory) return;\n\n // Continue from last page\n if (!this._lastHistoryPage?.hasNext()) {\n this._hasMoreHistory = false;\n return;\n }\n\n const nextPage = await this._lastHistoryPage.next();\n // Re-check: close() may be called during the await from another call stack\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- close() may be called during await\n if (this._closed || !nextPage) {\n if (!nextPage) this._hasMoreHistory = false;\n return;\n }\n\n await this._loadAndReveal(nextPage, limit);\n } catch (error) {\n this._logger.error('DefaultView.loadOlder(); failed', { error });\n throw error;\n } finally {\n this._loadingOlder = false;\n }\n }\n\n // -------------------------------------------------------------------------\n // Branch navigation\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CT13c\n select(msgId: string, index: number): void {\n this._logger.trace('DefaultView.select();', { msgId, index });\n const nodes = this._tree.getSiblingNodes(msgId);\n if (nodes.length <= 1) return;\n const groupRootId = this._tree.getGroupRoot(msgId);\n const clamped = Math.max(0, Math.min(index, nodes.length - 1));\n const selected = nodes[clamped];\n if (!selected) return; // unreachable: clamped is always in bounds\n this._branchSelections.set(groupRootId, { kind: 'user', selectedId: selected.msgId });\n this._logger.debug('DefaultView.select();', { msgId, index: clamped, selectedId: selected.msgId });\n this._cachedNodes = this._computeFlatNodes();\n this._updateVisibleSnapshot(this._cachedNodes);\n this._emitter.emit('update');\n }\n\n getSelectedIndex(msgId: string): number {\n this._logger.trace('DefaultView.getSelectedIndex();', { msgId });\n const nodes = this._tree.getSiblingNodes(msgId);\n if (nodes.length <= 1) return 0;\n const groupRootId = this._tree.getGroupRoot(msgId);\n const sel = this._branchSelections.get(groupRootId);\n if (!sel || sel.kind === 'pending') return nodes.length - 1; // default: latest\n const idx = nodes.findIndex((n) => n.msgId === sel.selectedId);\n if (idx === -1) return nodes.length - 1; // fallback if stale\n return idx;\n }\n\n getSiblings(msgId: string): TMessage[] {\n return this._tree.getSiblings(msgId);\n }\n\n hasSiblings(msgId: string): boolean {\n return this._tree.hasSiblings(msgId);\n }\n\n getNode(msgId: string): MessageNode<TMessage> | undefined {\n return this._tree.getNode(msgId);\n }\n\n // -------------------------------------------------------------------------\n // Write operations\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CT3, AIT-CT4\n async send(input: TMessage | TMessage[], options?: SendOptions): Promise<ActiveTurn<TEvent>> {\n this._logger.trace('DefaultView.send();');\n if (this._closed) {\n throw new Ably.ErrorInfo('unable to send; view is closed', ErrorCode.InvalidArgument, 400);\n }\n\n // Pre-compute visible branch history before the delegate call so the\n // transport has no back-reference to the View (one-way dependency).\n const history = this.flattenNodes();\n const result = await this._sendDelegate(input, options, history);\n\n // Spec: AIT-CT13e\n // Auto-select the new fork in this view when creating a fork.\n if (options?.forkOf) {\n const groupRoot = this._tree.getGroupRoot(options.forkOf);\n\n if (result.optimisticMsgIds.length > 0) {\n // The delegate optimistically inserted user messages (edit path).\n // Auto-select the last optimistic msgId — this is deterministic and\n // avoids the sibling-count race that exists when inferring from tree state.\n const lastMsgId = result.optimisticMsgIds.at(-1);\n if (lastMsgId) {\n this._branchSelections.set(groupRoot, { kind: 'auto', selectedId: lastMsgId });\n this._cachedNodes = this._computeFlatNodes();\n this._updateVisibleSnapshot(this._cachedNodes);\n this._emitter.emit('update');\n }\n } else {\n // No optimistic insert (e.g. regenerate sends no user messages). Defer\n // auto-selection until the server response creates the new sibling.\n // Store the group root (not the raw forkOf) so _pinBranchSelections\n // can match it regardless of which sibling is currently visible.\n this._branchSelections.set(groupRoot, { kind: 'pending', turnId: result.turnId });\n this._logger.debug('DefaultView.send(); deferring fork auto-selection', {\n forkOf: options.forkOf,\n groupRoot,\n turnId: result.turnId,\n });\n\n // Bound pending entry lifetime to the turn — clean up on turn-end.\n const turnUnsub = this._tree.on('turn', (evt) => {\n if (evt.type !== EVENT_TURN_END || evt.turnId !== result.turnId) return;\n const sel = this._branchSelections.get(groupRoot);\n if (sel?.kind === 'pending' && sel.turnId === result.turnId) {\n this._branchSelections.delete(groupRoot);\n }\n turnUnsub();\n const idx = this._unsubs.indexOf(turnUnsub);\n if (idx !== -1) this._unsubs.splice(idx, 1);\n });\n this._unsubs.push(turnUnsub);\n }\n }\n\n return result;\n }\n\n // Spec: AIT-CT5\n async regenerate(messageId: string, options?: SendOptions): Promise<ActiveTurn<TEvent>> {\n this._logger.trace('DefaultView.regenerate();', { messageId });\n\n const node = this._tree.getNode(messageId);\n if (!node) {\n throw new Ably.ErrorInfo(\n `unable to regenerate; message not found in tree: ${messageId}`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n const parentId = node.parentId;\n\n return this.send([], {\n ...options,\n body: {\n history: this._getHistoryBefore(messageId),\n ...options?.body,\n },\n forkOf: messageId,\n parent: parentId,\n });\n }\n\n // Spec: AIT-CT6\n async edit(\n messageId: string,\n newMessages: TMessage | TMessage[],\n options?: SendOptions,\n ): Promise<ActiveTurn<TEvent>> {\n this._logger.trace('DefaultView.edit();', { messageId });\n\n const node = this._tree.getNode(messageId);\n if (!node) {\n throw new Ably.ErrorInfo(\n `unable to edit; message not found in tree: ${messageId}`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n const parentId = node.parentId;\n\n return this.send(newMessages, {\n ...options,\n body: {\n history: this._getHistoryBefore(messageId),\n ...options?.body,\n },\n forkOf: messageId,\n parent: parentId,\n });\n }\n\n async update(msgId: string, events: TEvent[], options?: SendOptions): Promise<ActiveTurn<TEvent>> {\n if (this._closed) {\n throw new Ably.ErrorInfo('unable to update; view is closed', ErrorCode.InvalidArgument, 400);\n }\n this._logger.trace('DefaultView.update();', { msgId, eventCount: events.length });\n const eventNodes: EventsNode<TEvent>[] = [{ kind: 'event', msgId, events }];\n return this._sendDelegate([], options, this.flattenNodes(), eventNodes);\n }\n\n private _getHistoryBefore(messageId: string): MessageNode<TMessage>[] {\n this._logger.trace('DefaultView._getHistoryBefore();', { messageId });\n const all = this.flattenNodes();\n const idx = all.findIndex((n) => n.msgId === messageId);\n if (idx === -1) {\n this._logger.warn('DefaultView._getHistoryBefore(); target not in visible nodes, returning full list', {\n messageId,\n });\n return all;\n }\n return all.slice(0, idx);\n }\n\n // -------------------------------------------------------------------------\n // Observation\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CT17\n getActiveTurnIds(): Map<string, Set<string>> {\n this._logger.trace('DefaultView.getActiveTurnIds();');\n const allTurns = this._tree.getActiveTurnIds();\n if (this._withheldMsgIds.size === 0) return allTurns;\n\n // Filter to turns that have at least one visible message\n const result = new Map<string, Set<string>>();\n for (const [clientId, turnIds] of allTurns) {\n const filtered = new Set<string>();\n for (const turnId of turnIds) {\n if (this._lastVisibleTurnIds.has(turnId)) filtered.add(turnId);\n }\n if (filtered.size > 0) result.set(clientId, filtered);\n }\n return result;\n }\n\n // -------------------------------------------------------------------------\n // Event subscription\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CT8a, AIT-CT8b, AIT-CT8e\n on(event: 'update', handler: () => void): () => void;\n on(event: 'ably-message', handler: (msg: Ably.InboundMessage) => void): () => void;\n on(event: 'turn', handler: (event: TurnLifecycleEvent) => void): () => void;\n on(\n event: 'update' | 'ably-message' | 'turn',\n handler: (() => void) | ((msg: Ably.InboundMessage) => void) | ((event: TurnLifecycleEvent) => void),\n ): () => void {\n // CAST: overload signatures enforce correct handler types per event name.\n const cb = handler as (arg: ViewEventsMap[keyof ViewEventsMap]) => void;\n this._emitter.on(event, cb);\n return () => {\n this._emitter.off(event, cb);\n };\n }\n\n // -------------------------------------------------------------------------\n // Lifecycle\n // -------------------------------------------------------------------------\n\n /**\n * Tear down the view — unsubscribe from tree events.\n */\n close(): void {\n this._logger.info('DefaultView.close();');\n this._closed = true;\n this._loadingOlder = false;\n for (const unsub of this._unsubs) unsub();\n this._unsubs.length = 0;\n this._emitter.off();\n this._branchSelections.clear();\n this._withheldMsgIds.clear();\n this._withheldBuffer.length = 0;\n this._onClose?.();\n }\n\n // -------------------------------------------------------------------------\n // Private: history loading\n // -------------------------------------------------------------------------\n\n private async _loadFirstPage(limit: number): Promise<void> {\n // Snapshot before loading — everything already in the tree stays visible\n const beforeMsgIds = new Set(this._tree.flattenNodes(this._resolveSelections()).map((n) => n.msgId));\n\n const firstPage = await decodeHistory(this._channel, this._codec, { limit }, this._logger);\n if (this._closed) return;\n const { newVisible, lastPage } = await this._loadUntilVisible(firstPage, limit, beforeMsgIds);\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- close() may be called during await\n if (this._closed) return;\n\n this._lastHistoryPage = lastPage;\n this._hasMoreHistory = lastPage.hasNext();\n\n // Split into withheld (older, kept hidden) and released (newest, shown now).\n // Only add the actually-withheld messages to the set — adding all then\n // releasing would cause a spurious empty-list update if a tree event fires\n // between the two operations.\n const released = newVisible.slice(-limit);\n const withheld = newVisible.slice(0, -limit);\n for (const n of withheld) {\n this._withheldMsgIds.add(n.msgId);\n }\n this._withheldBuffer.push(...withheld);\n this._releaseWithheld(released);\n }\n\n private async _loadAndReveal(page: HistoryPage<TMessage>, limit: number): Promise<void> {\n // Everything currently in the tree is \"already known\"\n const alreadyKnown = new Set(this._tree.flattenNodes(this._resolveSelections()).map((n) => n.msgId));\n\n const { newVisible, lastPage } = await this._loadUntilVisible(page, limit, alreadyKnown);\n if (this._closed) return;\n this._lastHistoryPage = lastPage;\n this._hasMoreHistory = lastPage.hasNext();\n\n // Release the newest `limit` items; rest stays in buffer.\n // Only add actually-withheld messages to the set — adding all then\n // releasing would cause a spurious empty-list update if a tree event\n // fires between the two operations.\n const batch = newVisible.slice(-limit);\n const withheld = newVisible.slice(0, -limit);\n for (const n of withheld) {\n this._withheldMsgIds.add(n.msgId);\n }\n this._withheldBuffer.push(...withheld);\n this._releaseWithheld(batch);\n }\n\n private _processHistoryPage(page: HistoryPage<TMessage>): void {\n this._processingHistory = true;\n try {\n for (const item of page.items) {\n const msgId = item.headers[HEADER_MSG_ID];\n if (!msgId) continue;\n this._tree.upsert(msgId, item.message, item.headers, item.serial);\n }\n\n for (const msg of page.rawMessages) {\n this._tree.emitAblyMessage(msg);\n }\n } finally {\n this._processingHistory = false;\n }\n }\n\n private async _loadUntilVisible(\n firstPage: HistoryPage<TMessage>,\n target: number,\n beforeMsgIds: Set<string>,\n ): Promise<{ newVisible: MessageNode<TMessage>[]; lastPage: HistoryPage<TMessage> }> {\n this._processHistoryPage(firstPage);\n let page = firstPage;\n\n const newVisibleCount = (): number => {\n let count = 0;\n for (const n of this._tree.flattenNodes(this._resolveSelections())) {\n if (!beforeMsgIds.has(n.msgId)) count++;\n }\n return count;\n };\n\n while (newVisibleCount() < target && page.hasNext()) {\n const nextPage = await page.next();\n if (!nextPage || this._closed) break;\n this._processHistoryPage(nextPage);\n page = nextPage;\n }\n\n const newVisible = this._tree.flattenNodes(this._resolveSelections()).filter((n) => !beforeMsgIds.has(n.msgId));\n return { newVisible, lastPage: page };\n }\n\n // Spec: AIT-CT11a\n private _releaseWithheld(nodes: MessageNode<TMessage>[]): void {\n for (const n of nodes) {\n this._withheldMsgIds.delete(n.msgId);\n }\n if (nodes.length > 0) {\n this._cachedNodes = this._computeFlatNodes();\n this._updateVisibleSnapshot(this._cachedNodes);\n this._emitter.emit('update');\n }\n }\n\n // -------------------------------------------------------------------------\n // Private: scoped event forwarding\n // -------------------------------------------------------------------------\n\n private _updateVisibleSnapshot(nodes?: MessageNode<TMessage>[]): void {\n const resolved = nodes ?? this.flattenNodes();\n this._lastVisibleIds = resolved.map((n) => n.msgId);\n this._lastVisibleMessages = resolved.map((n) => n.message);\n this._lastVisibleTurnIds = new Set<string>();\n for (const n of resolved) {\n const turnId = n.headers[HEADER_TURN_ID];\n if (turnId) this._lastVisibleTurnIds.add(turnId);\n }\n }\n\n private _onTreeUpdate(): void {\n // Suppress update forwarding while processing history pages. During\n // _processHistoryPage, each tree.upsert() fires this handler synchronously\n // — but _withheldMsgIds hasn't been populated yet, so flattenNodes() would\n // return unfiltered history. Without this guard, subscribers briefly see all\n // history messages before the pagination window is applied. The final update\n // is emitted by _releaseWithheld after withholding is set up.\n // Scoped to _processingHistory (not _loadingOlder) so that live streaming\n // updates arriving during the async history fetch are still forwarded.\n if (this._processingHistory) return;\n\n const currentVersion = this._tree.structuralVersion;\n\n // Content-only fast path: the tree structure hasn't changed (no new\n // nodes, deletions, or serial reorders), so the cached node list is\n // still structurally valid. The tree mutated an existing node's\n // .message in place - check if any visible message reference changed.\n // JS single-threaded: structuralVersion cannot change between the\n // check and the response within this synchronous handler invocation.\n if (currentVersion === this._lastStructuralVersion) {\n const changed = this._cachedNodes.some((node, i) => node.message !== this._lastVisibleMessages[i]);\n if (changed) {\n this._lastVisibleMessages = this._cachedNodes.map((n) => n.message);\n this._cachedNodes = [...this._cachedNodes];\n this._emitter.emit('update');\n }\n return;\n }\n\n // Structural update: full re-walk required.\n this._lastStructuralVersion = currentVersion;\n\n // Pin selections for previously-visible nodes that now have siblings.\n // This prevents new forks (from other views' edits/regenerates) from\n // shifting this view to a branch the user didn't navigate to.\n this._pinBranchSelections();\n this._resolvePendingSelections();\n\n const nodes = this._computeFlatNodes();\n const newIds = nodes.map((n) => n.msgId);\n const newMessages = nodes.map((n) => n.message);\n if (this._visibleChanged(newIds, newMessages)) {\n this._cachedNodes = nodes;\n this._updateVisibleSnapshot(nodes);\n this._emitter.emit('update');\n }\n }\n\n /**\n * Build a resolved selections map from `_branchSelections` for passing\n * to `tree.flattenNodes()`. Pending entries (no sibling yet) are omitted,\n * causing the tree to use the default (latest sibling).\n * @returns Resolved map of groupRoot → selectedMsgId.\n */\n private _resolveSelections(): Map<string, string> {\n const resolved = new Map<string, string>();\n for (const [groupRoot, sel] of this._branchSelections) {\n if (sel.kind === 'pending') continue;\n resolved.set(groupRoot, sel.selectedId);\n }\n return resolved;\n }\n\n /**\n * For each previously-visible message that now has siblings but no\n * explicit selection, pin the selection to that message's msgId.\n * This preserves the current branch when new forks appear from\n * other views or external sources.\n *\n * Exception: if the fork was initiated by this view (tracked as a\n * `pending` BranchSelection), select the newest sibling instead of\n * pinning the old one. This handles regenerate, where no optimistic\n * insert was possible at send time.\n */\n private _pinBranchSelections(): void {\n for (const msgId of this._lastVisibleIds) {\n if (!this._tree.hasSiblings(msgId)) continue;\n const groupRoot = this._tree.getGroupRoot(msgId);\n const existing = this._branchSelections.get(groupRoot);\n\n // Spec: AIT-CT13e\n // Check if this fork was initiated by this view (e.g. regenerate).\n // If so, select the newest sibling — but only if it belongs to the\n // pending turn. Without this check, a sibling from another view's\n // concurrent fork would be incorrectly auto-selected.\n if (existing?.kind === 'pending') {\n const nodes = this._tree.getSiblingNodes(msgId);\n const newest = nodes.at(-1);\n if (newest && newest.msgId !== msgId) {\n const newestTurnId = newest.headers[HEADER_TURN_ID];\n if (newestTurnId === existing.turnId) {\n this._logger.debug('DefaultView._pinBranchSelections(); auto-selecting pending fork', {\n msgId,\n newestId: newest.msgId,\n turnId: existing.turnId,\n });\n this._branchSelections.set(groupRoot, { kind: 'auto', selectedId: newest.msgId });\n }\n }\n continue;\n }\n\n // Spec: AIT-CT13f\n // External fork — pin to the currently-visible sibling.\n if (existing) continue; // already have a selection\n this._branchSelections.set(groupRoot, { kind: 'pinned', selectedId: msgId });\n }\n }\n\n /**\n * Resolve pending selections that are no longer on the visible branch.\n * `_pinBranchSelections` only checks visible nodes, so if the user navigated\n * away before the server response arrived, the pending entry would linger.\n * This pass checks all pending entries against the tree directly.\n */\n private _resolvePendingSelections(): void {\n for (const [groupRoot, sel] of this._branchSelections) {\n if (sel.kind !== 'pending') continue;\n const nodes = this._tree.getSiblingNodes(groupRoot);\n if (nodes.length <= 1) continue;\n const newest = nodes.at(-1);\n if (!newest || newest.msgId === groupRoot) continue;\n const newestTurnId = newest.headers[HEADER_TURN_ID];\n if (newestTurnId === sel.turnId) {\n this._logger.debug('DefaultView._resolvePendingSelections(); resolving off-branch pending', {\n groupRoot,\n newestId: newest.msgId,\n turnId: sel.turnId,\n });\n this._branchSelections.set(groupRoot, { kind: 'auto', selectedId: newest.msgId });\n }\n }\n }\n\n private _onTreeAblyMessage(msg: Ably.InboundMessage): void {\n // Re-emit only if the message corresponds to a visible node\n const headers = getHeaders(msg);\n const msgId = headers[HEADER_MSG_ID];\n if (!msgId) {\n // Non-message events (turn-start, turn-end, cancel) — always forward\n this._emitter.emit('ably-message', msg);\n return;\n }\n // Check that msgId is on the visible branch and not withheld\n if (this._lastVisibleIds.includes(msgId)) {\n this._emitter.emit('ably-message', msg);\n }\n }\n\n private _onTreeTurn(event: TurnLifecycleEvent): void {\n // Check if any messages for this turn are already on the visible branch.\n if (this._lastVisibleTurnIds.has(event.turnId)) {\n this._emitter.emit('turn', event);\n return;\n }\n\n // For turn-start, use branch metadata to predict visibility before\n // messages arrive. Own turns have optimistic inserts (caught above).\n // Remote turns carry parent/forkOf from the server.\n if (event.type === EVENT_TURN_START && this._isTurnStartVisible(event)) {\n // Track the predicted turnId so the corresponding turn-end is not\n // dropped if it arrives before messages update the snapshot.\n this._lastVisibleTurnIds.add(event.turnId);\n this._emitter.emit('turn', event);\n }\n }\n\n /**\n * Predict whether a turn-start's messages will be visible on this view's branch\n * using the parent/forkOf metadata from the event.\n * @param event - The turn-start lifecycle event with optional branch metadata.\n * @returns True if the turn's messages are expected to be visible on this view's branch.\n */\n private _isTurnStartVisible(event: TurnLifecycleEvent & { type: typeof EVENT_TURN_START }): boolean {\n const { parent } = event;\n\n // No parent metadata — can't determine branch, forward as default.\n // This covers root turns (parent omitted) and backward compat.\n if (parent === undefined) return true;\n\n // Check if the parent is on the visible branch\n return this._lastVisibleIds.includes(parent);\n }\n\n private _visibleChanged(newIds: string[], newMessages: TMessage[]): boolean {\n if (newIds.length !== this._lastVisibleIds.length) return true;\n for (const [i, newId] of newIds.entries()) {\n if (newId !== this._lastVisibleIds[i]) return true;\n }\n // Also detect in-place content updates (e.g. streaming) via reference comparison\n for (const [i, msg] of newMessages.entries()) {\n if (msg !== this._lastVisibleMessages[i]) return true;\n }\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a View that projects a paginated window over a Tree.\n * @param options - The tree, channel, codec, and logger to use.\n * @returns A new {@link DefaultView} instance.\n */\nexport const createView = <TEvent, TMessage>(options: ViewOptions<TEvent, TMessage>): DefaultView<TEvent, TMessage> =>\n new DefaultView(options);\n","/**\n * Core client-side transport, parameterized by codec.\n *\n * Composes StreamRouter and Tree to handle the full client-side\n * lifecycle. Subscribes to the Ably channel on construction. The same\n * subscription, decoder, and channel are reused across turns.\n *\n * The client never publishes user messages directly. Instead, it sends them\n * to the server via HTTP POST. The server publishes user messages and turn\n * lifecycle events (turn-start, turn-end) on behalf of the client.\n */\n\nimport * as Ably from 'ably';\n\nimport {\n EVENT_CANCEL,\n EVENT_TURN_END,\n EVENT_TURN_START,\n HEADER_AMEND,\n HEADER_CANCEL_ALL,\n HEADER_CANCEL_CLIENT_ID,\n HEADER_CANCEL_OWN,\n HEADER_CANCEL_TURN_ID,\n HEADER_FORK_OF,\n HEADER_MSG_ID,\n HEADER_PARENT,\n HEADER_TURN_CLIENT_ID,\n HEADER_TURN_ID,\n HEADER_TURN_REASON,\n} from '../../constants.js';\nimport { ErrorCode } from '../../errors.js';\nimport { EventEmitter } from '../../event-emitter.js';\nimport type { Logger } from '../../logger.js';\nimport { LogLevel, makeLogger } from '../../logger.js';\nimport { getHeaders } from '../../utils.js';\nimport type { DecoderOutput, MessageAccumulator, StreamDecoder } from '../codec/types.js';\nimport { buildTransportHeaders } from './headers.js';\nimport type { StreamRouter } from './stream-router.js';\nimport { createStreamRouter } from './stream-router.js';\nimport type { DefaultTree } from './tree.js';\nimport { createTree } from './tree.js';\nimport type {\n ActiveTurn,\n CancelFilter,\n ClientTransport,\n ClientTransportOptions,\n CloseOptions,\n EventsNode,\n MessageNode,\n SendOptions,\n Tree,\n TurnEndReason,\n TurnLifecycleEvent,\n View,\n} from './types.js';\nimport { createView, type DefaultView } from './view.js';\n\n/**\n * Returned from `on()` when the transport is already closed — the subscription\n * is silently ignored since no further events will fire.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-function -- intentional no-op\nconst noopUnsubscribe = (): void => {};\n\n// ---------------------------------------------------------------------------\n// Internal state machine\n// ---------------------------------------------------------------------------\n\nenum ClientTransportState {\n READY = 'ready',\n CLOSED = 'closed',\n}\n\n// ---------------------------------------------------------------------------\n// Event map for the transport's typed EventEmitter\n// ---------------------------------------------------------------------------\n\ninterface ClientTransportEventsMap {\n error: Ably.ErrorInfo;\n}\n\n// ---------------------------------------------------------------------------\n// Per-turn observer state — consolidated to avoid parallel-map bookkeeping\n// ---------------------------------------------------------------------------\n\ninterface TurnObserverState<TEvent, TMessage> {\n headers: Record<string, string>;\n serial: string | undefined;\n accumulator: MessageAccumulator<TEvent, TMessage>;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n// Spec: AIT-CT1\nclass DefaultClientTransport<TEvent, TMessage> implements ClientTransport<TEvent, TMessage> {\n private readonly _channel: Ably.RealtimeChannel;\n private readonly _codec: ClientTransportOptions<TEvent, TMessage>['codec'];\n private readonly _clientId: string | undefined;\n private readonly _api: string;\n private readonly _credentials: RequestCredentials | undefined;\n private readonly _headersFn: (() => Record<string, string>) | undefined;\n private readonly _bodyFn: (() => Record<string, unknown>) | undefined;\n private readonly _fetchFn: typeof globalThis.fetch;\n private readonly _logger: Logger;\n\n // Typed event emitter — only 'error' remains on the transport\n private readonly _emitter: EventEmitter<ClientTransportEventsMap>;\n\n // Relay detection — tracks msg-ids of optimistic inserts for reconciliation\n private readonly _ownMsgIds = new Set<string>();\n private readonly _ownTurnIds = new Set<string>();\n\n // Track msgIds per turn for cleanup on turn-end\n private readonly _turnMsgIds = new Map<string, Set<string>>();\n\n // Per-turn observer state: headers, serial, and accumulator in one map.\n // A single .delete(turnId) cleans up all three.\n private readonly _turnObservers = new Map<string, TurnObserverState<TEvent, TMessage>>();\n\n // Callbacks to resolve pending waitForTurn promises on close, preventing leaked subscriptions.\n private readonly _closeResolvers: (() => void)[] = [];\n\n // Sub-components\n private readonly _tree: DefaultTree<TMessage>;\n private readonly _view: DefaultView<TEvent, TMessage>;\n private readonly _views = new Set<DefaultView<TEvent, TMessage>>();\n private readonly _router: StreamRouter<TEvent>;\n private readonly _decoder: StreamDecoder<TEvent, TMessage>;\n\n // Spec: AIT-CT10, AIT-CT10a\n readonly tree: Tree<TMessage>;\n readonly view: View<TEvent, TMessage>;\n\n // Channel subscription — subscribe() returns a Promise that resolves when the channel attaches\n private readonly _attachPromise: Promise<unknown>;\n private readonly _onMessage: (msg: Ably.InboundMessage) => void;\n\n private _state = ClientTransportState.READY;\n private _hasAttachedOnce: boolean;\n private readonly _onChannelStateChange: Ably.channelEventCallback;\n\n // Events staged locally via stageEvents(). Flushed into the eventNodes\n // parameter of _internalSend on the next send operation.\n private _pendingLocalEvents: EventsNode<TEvent>[] = [];\n\n constructor(options: ClientTransportOptions<TEvent, TMessage>) {\n this._channel = options.channel;\n this._codec = options.codec;\n this._clientId = options.clientId;\n this._api = options.api;\n this._credentials = options.credentials;\n // CAST: TS can't narrow options.headers/body inside a closure because the outer\n // object is mutable. The truthiness check on the preceding line guarantees non-nullish.\n this._headersFn =\n typeof options.headers === 'function'\n ? options.headers\n : options.headers\n ? () => options.headers as Record<string, string>\n : undefined;\n this._bodyFn =\n typeof options.body === 'function'\n ? options.body\n : options.body\n ? () => options.body as Record<string, unknown>\n : undefined;\n this._fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);\n this._logger = (options.logger ?? makeLogger({ logLevel: LogLevel.Silent })).withContext({\n component: 'ClientTransport',\n });\n\n this._emitter = new EventEmitter<ClientTransportEventsMap>(this._logger);\n this._hasAttachedOnce = this._channel.state === 'attached';\n\n // Compose sub-components\n this._tree = createTree<TMessage>(this._logger);\n this._view = createView<TEvent, TMessage>({\n tree: this._tree,\n channel: this._channel,\n codec: this._codec,\n sendDelegate: this._internalSend.bind(this),\n logger: this._logger,\n onClose: () => this._views.delete(this._view),\n });\n this._router = createStreamRouter<TEvent>(this._codec.isTerminal.bind(this._codec), this._logger);\n this._decoder = this._codec.createDecoder();\n\n this._views.add(this._view);\n\n // Public accessors (typed as narrow interfaces)\n this.tree = this._tree;\n this.view = this._view;\n\n // Seed tree with initial messages — transport assigns its own msgId\n if (options.messages) {\n let prevMsgId: string | undefined;\n for (const msg of options.messages) {\n const msgId = crypto.randomUUID();\n const seedHeaders: Record<string, string> = { [HEADER_MSG_ID]: msgId };\n if (prevMsgId) seedHeaders[HEADER_PARENT] = prevMsgId;\n this._tree.upsert(msgId, msg, seedHeaders);\n prevMsgId = msgId;\n }\n }\n\n // Spec: AIT-CT2\n // Subscribe before attach (RTL7g)\n this._onMessage = (ablyMessage: Ably.InboundMessage) => {\n this._handleMessage(ablyMessage);\n };\n this._attachPromise = this._channel.subscribe(this._onMessage);\n\n // Listen for channel state changes that break message continuity.\n // _hasAttachedOnce is seeded from the channel's current state so that\n // pre-attached channels are handled correctly. It distinguishes the\n // initial attach (expected) from a genuine discontinuity.\n this._onChannelStateChange = (stateChange: Ably.ChannelStateChange) => {\n this._handleChannelStateChange(stateChange);\n };\n this._channel.on(this._onChannelStateChange);\n }\n\n // ---------------------------------------------------------------------------\n // Message subscription handler\n // ---------------------------------------------------------------------------\n\n private _handleMessage(ablyMessage: Ably.InboundMessage): void {\n if (this._state === ClientTransportState.CLOSED) return;\n\n try {\n // Spec: AIT-CT16a\n // --- Turn lifecycle events from the server ---\n if (ablyMessage.name === EVENT_TURN_START) {\n const headers = getHeaders(ablyMessage);\n const turnId = headers[HEADER_TURN_ID];\n const turnCid = headers[HEADER_TURN_CLIENT_ID] ?? '';\n if (turnId) {\n this._tree.trackTurn(turnId, turnCid);\n const parentRaw = headers[HEADER_PARENT];\n const forkOf = headers[HEADER_FORK_OF];\n this._tree.emitTurn({\n type: EVENT_TURN_START,\n turnId,\n clientId: turnCid,\n ...(parentRaw !== undefined && { parent: parentRaw }),\n ...(forkOf !== undefined && { forkOf }),\n });\n }\n this._tree.emitAblyMessage(ablyMessage);\n return;\n }\n\n if (ablyMessage.name === EVENT_TURN_END) {\n const headers = getHeaders(ablyMessage);\n const turnId = headers[HEADER_TURN_ID];\n const turnCid = headers[HEADER_TURN_CLIENT_ID] ?? '';\n // CAST: server always writes a valid TurnEndReason; default to 'complete' for robustness\n const reason = (headers[HEADER_TURN_REASON] ?? 'complete') as TurnEndReason;\n if (turnId) {\n this._router.closeStream(turnId);\n this._turnObservers.delete(turnId);\n this._tree.untrackTurn(turnId);\n // Clean up per-turn relay-detection state\n const msgIds = this._turnMsgIds.get(turnId);\n if (msgIds) {\n for (const mid of msgIds) this._ownMsgIds.delete(mid);\n this._turnMsgIds.delete(turnId);\n }\n this._ownTurnIds.delete(turnId);\n this._tree.emitTurn({ type: EVENT_TURN_END, turnId, clientId: turnCid, reason });\n }\n this._tree.emitAblyMessage(ablyMessage);\n return;\n }\n\n // --- Codec-decoded messages ---\n const outputs = this._decoder.decode(ablyMessage);\n const headers = getHeaders(ablyMessage);\n const serial = ablyMessage.serial;\n\n // Cross-turn events target an existing message from a prior turn,\n // bypassing the current turn's accumulator.\n const amendTarget = headers[HEADER_AMEND];\n if (amendTarget) {\n for (const output of outputs) {\n if (output.kind === 'event') {\n this._handleAmendmentEvent(amendTarget, output);\n }\n }\n return;\n }\n\n // Always update observer headers, even when the decoder produces no outputs.\n // This ensures header transitions (e.g. x-ably-status: streaming → aborted)\n // are captured for events that the decoder suppresses (AIT-CD8: aborted\n // stream appends emit no events but still carry the updated status header).\n const turnId = headers[HEADER_TURN_ID];\n if (turnId) {\n this._updateTurnObserverHeaders(turnId, headers, serial);\n }\n\n for (const output of outputs) {\n if (output.kind === 'message') {\n this._handleMessageOutput(output.message, headers, serial, ablyMessage.action);\n } else {\n this._handleEventOutput(output, headers);\n }\n }\n\n // Emit ably-message AFTER decode/upsert so that View subscribers can\n // find the node in _lastVisibleIds (which is refreshed by tree 'update'\n // events triggered during upsert).\n this._tree.emitAblyMessage(ablyMessage);\n } catch (error) {\n const cause = error instanceof Ably.ErrorInfo ? error : undefined;\n this._emitter.emit(\n 'error',\n new Ably.ErrorInfo(\n `unable to process channel message; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.TransportSubscriptionError,\n 500,\n cause,\n ),\n );\n }\n }\n\n /**\n * Handle a decoded domain message (user message create or relayed own message).\n * @param message - The decoded domain message.\n * @param headers - Ably headers from the wire message.\n * @param serial - Ably serial for tree ordering.\n * @param action - Ably message action (e.g. 'message.create').\n */\n private _handleMessageOutput(\n message: TMessage,\n headers: Record<string, string>,\n serial: string | undefined,\n action: string | undefined,\n ): void {\n // Spec: AIT-CT15\n const msgId = headers[HEADER_MSG_ID];\n if (msgId && this._ownMsgIds.has(msgId)) {\n // Relayed own message — reconcile optimistic entry with server-assigned fields\n this._upsertAndNotify(message, headers, serial);\n return;\n }\n\n if (action === 'message.create') {\n this._upsertAndNotify(message, headers, serial);\n }\n }\n\n /**\n * Handle a decoded streaming event: route to own-turn stream or accumulate for observer.\n * @param output - The decoded event output from the codec.\n * @param headers - Ably headers from the wire message.\n */\n private _handleEventOutput(output: DecoderOutput<TEvent, TMessage>, headers: Record<string, string>): void {\n if (output.kind !== 'event') return;\n const event = output.event;\n const turnId = headers[HEADER_TURN_ID];\n if (!turnId) return;\n\n // Observer headers are already updated in _handleMessage (before outputs\n // are iterated) so that header transitions are captured even when the\n // decoder produces no outputs (e.g. aborted stream appends per AIT-CD8).\n\n // Active own turn — route to the ReadableStream\n if (this._router.route(turnId, event)) {\n this._accumulateAndEmit(turnId, output);\n if (this._codec.isTerminal(event)) this._turnObservers.delete(turnId);\n return;\n }\n\n // Completed own turn — late arrival, skip\n if (this._ownTurnIds.has(turnId) && !this._turnObservers.has(turnId)) return;\n\n // Spec: AIT-CT16\n // Observer turn — accumulate and emit\n this._accumulateAndEmit(turnId, output);\n if (this._codec.isTerminal(event)) this._turnObservers.delete(turnId);\n }\n\n /**\n * Handle a cross-turn event targeting an existing message from a prior turn.\n * Creates a temporary accumulator, seeds it with the existing message,\n * processes the event, and upserts the updated message into the tree.\n * @param targetMsgId - The x-ably-msg-id of the message to update.\n * @param output - The decoded event output to apply.\n */\n private _handleAmendmentEvent(targetMsgId: string, output: DecoderOutput<TEvent, TMessage>): void {\n this._logger.trace('ClientTransport._handleAmendmentEvent();', { targetMsgId });\n\n const existingNode = this._tree.getNode(targetMsgId);\n if (!existingNode) {\n this._logger.debug('ClientTransport._handleAmendmentEvent(); target not found, dropping', { targetMsgId });\n return;\n }\n\n const accumulator = this._codec.createAccumulator();\n accumulator.initMessage(targetMsgId, existingNode.message);\n accumulator.processOutputs([output]);\n\n const updatedMsg = accumulator.messages.at(-1);\n if (updatedMsg) {\n this._tree.upsert(targetMsgId, updatedMsg, existingNode.headers, existingNode.serial);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Channel state change handler\n // ---------------------------------------------------------------------------\n\n // Spec: AIT-CT19, AIT-CT19a\n private _handleChannelStateChange(stateChange: Ably.ChannelStateChange): void {\n if (this._state === ClientTransportState.CLOSED) return;\n\n const { current, resumed } = stateChange;\n\n // Track the initial attach so we don't treat it as a discontinuity\n if (current === 'attached' && !this._hasAttachedOnce) {\n this._hasAttachedOnce = true;\n return;\n }\n\n // Continuity-breaking states:\n // - FAILED, SUSPENDED, DETACHED: no more messages expected (or gap)\n // - ATTACHED with resumed: false (UPDATE): messages were lost\n const continuityLost =\n current === 'failed' || current === 'suspended' || current === 'detached' || (current === 'attached' && !resumed);\n\n if (!continuityLost) return;\n\n this._logger.error('ClientTransport._handleChannelStateChange(); channel continuity lost', {\n current,\n resumed,\n previous: stateChange.previous,\n });\n\n const err = new Ably.ErrorInfo(\n `unable to deliver events; channel continuity lost (${current}${current === 'attached' ? ', resumed: false' : ''})`,\n ErrorCode.ChannelContinuityLost,\n 500,\n stateChange.reason,\n );\n\n // As with cancellation (_closeMatchingTurnStreams), do not clear\n // _ownTurnIds or _turnObservers here — late events must still accumulate\n // into the tree. The turn-end handler cleans up observers.\n for (const turnId of this._ownTurnIds) {\n this._router.errorStream(turnId, err);\n }\n\n this._emitter.emit('error', err);\n }\n\n // ---------------------------------------------------------------------------\n // Tree mutation + notification helpers\n // ---------------------------------------------------------------------------\n\n /**\n * Upsert a message into the tree and notify subscribers.\n * @param message - The domain message to insert or update.\n * @param headers - Ably headers for the message.\n * @param serial - Ably serial for tree ordering.\n */\n private _upsertAndNotify(message: TMessage, headers: Record<string, string>, serial?: string): void {\n const msgId = headers[HEADER_MSG_ID];\n if (!msgId) return;\n this._tree.upsert(msgId, message, headers, serial);\n }\n\n // ---------------------------------------------------------------------------\n // Observer accumulation\n // ---------------------------------------------------------------------------\n\n /**\n * Ensure a TurnObserverState exists for turnId, updating headers and serial as new events arrive.\n * @param turnId - The turn to track.\n * @param headers - Headers from the current event.\n * @param serial - Ably serial from the current event.\n */\n private _updateTurnObserverHeaders(\n turnId: string,\n headers: Record<string, string>,\n serial: string | undefined,\n ): void {\n const existing = this._turnObservers.get(turnId);\n if (existing) {\n if (Object.keys(headers).length > 0) {\n Object.assign(existing.headers, headers);\n }\n // Always advance the serial so the tree node sorts after all\n // earlier messages in the turn (e.g. user-message relays that\n // arrive before the assistant response).\n if (serial !== undefined) {\n existing.serial = serial;\n }\n } else {\n this._turnObservers.set(turnId, {\n headers: { ...headers },\n serial,\n accumulator: this._codec.createAccumulator(),\n });\n }\n }\n\n /**\n * Process a streaming event through the turn's accumulator and emit the latest message.\n * @param turnId - The turn this event belongs to.\n * @param output - The decoded event output to accumulate.\n */\n private _accumulateAndEmit(turnId: string, output: DecoderOutput<TEvent, TMessage>): void {\n const observer = this._turnObservers.get(turnId);\n if (!observer) return;\n\n // Sync the accumulator with the tree before processing. If the message\n // was updated externally (via cross-turn events), initMessage syncs the\n // accumulator's state so the update isn't lost when processing\n // late turn events like finish-step/finish.\n const msgId = observer.headers[HEADER_MSG_ID];\n if (msgId) {\n const treeNode = this._tree.getNode(msgId);\n if (treeNode) {\n observer.accumulator.initMessage(msgId, treeNode.message);\n }\n }\n\n observer.accumulator.processOutputs([output]);\n\n const messages = observer.accumulator.messages;\n if (messages.length === 0) return;\n\n let message: TMessage | undefined;\n try {\n message = structuredClone(messages.at(-1));\n } catch {\n // CAST: structuredClone can fail if the message contains non-cloneable\n // values (e.g. functions). Fall back to the reference — the tree upsert\n // below copies headers independently, so shared message state is the\n // only risk. Accumulator messages are replaced on each event, so\n // mutation between events is not a practical concern.\n message = messages.at(-1);\n }\n\n if (message) {\n const msgId = observer.headers[HEADER_MSG_ID];\n if (msgId) {\n this._tree.upsert(msgId, message, { ...observer.headers }, observer.serial);\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Cancel helpers\n // ---------------------------------------------------------------------------\n\n private async _publishCancel(filter: CancelFilter): Promise<void> {\n this._logger.trace('ClientTransport._publishCancel();', { filter });\n\n const headers: Record<string, string> = {};\n if (filter.turnId) {\n headers[HEADER_CANCEL_TURN_ID] = filter.turnId;\n } else if (filter.own) {\n headers[HEADER_CANCEL_OWN] = 'true';\n } else if (filter.clientId) {\n headers[HEADER_CANCEL_CLIENT_ID] = filter.clientId;\n } else if (filter.all) {\n headers[HEADER_CANCEL_ALL] = 'true';\n }\n\n await this._channel.publish({\n name: EVENT_CANCEL,\n extras: { headers },\n });\n }\n\n private _closeMatchingTurnStreams(filter: CancelFilter): void {\n // Only close the router streams here — do NOT clear _turnObservers.\n // The observer must remain alive so that late server events (e.g. abort,\n // x-ably-status: aborted) arriving before turn-end are still accumulated\n // into the message store. The turn-end handler cleans up observers.\n for (const turnId of this._getMatchingTurnIds(filter)) {\n this._router.closeStream(turnId);\n }\n }\n\n private _getMatchingTurnIds(filter: CancelFilter): Set<string> {\n const matched = new Set<string>();\n const activeTurns = this._tree.getActiveTurnIds();\n\n if (filter.all) {\n for (const turnIds of activeTurns.values()) {\n for (const turnId of turnIds) matched.add(turnId);\n }\n } else if (filter.own) {\n const ownTurns = activeTurns.get(this._clientId ?? '');\n if (ownTurns) {\n for (const turnId of ownTurns) matched.add(turnId);\n }\n } else if (filter.clientId) {\n const clientTurns = activeTurns.get(filter.clientId);\n if (clientTurns) {\n for (const turnId of clientTurns) matched.add(turnId);\n }\n } else if (filter.turnId) {\n // Check if the turnId exists in any client's turns\n for (const turnIds of activeTurns.values()) {\n if (turnIds.has(filter.turnId)) {\n matched.add(filter.turnId);\n break;\n }\n }\n }\n return matched;\n }\n\n // ---------------------------------------------------------------------------\n // Input message helpers\n // ---------------------------------------------------------------------------\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n // Spec: AIT-CT10b\n createView(): View<TEvent, TMessage> {\n if (this._state === ClientTransportState.CLOSED) {\n throw new Ably.ErrorInfo('unable to create view; transport is closed', ErrorCode.TransportClosed, 400);\n }\n this._logger.trace('DefaultClientTransport.createView();');\n const view = createView<TEvent, TMessage>({\n tree: this._tree,\n channel: this._channel,\n codec: this._codec,\n sendDelegate: this._internalSend.bind(this),\n logger: this._logger,\n onClose: () => this._views.delete(view),\n });\n this._views.add(view);\n return view;\n }\n\n // Spec: AIT-CT3, AIT-CT4\n private async _internalSend(\n input: TMessage | TMessage[],\n sendOptions: SendOptions | undefined,\n history: MessageNode<TMessage>[],\n eventNodes?: EventsNode<TEvent>[],\n ): Promise<ActiveTurn<TEvent>> {\n if (this._state === ClientTransportState.CLOSED) {\n throw new Ably.ErrorInfo('unable to send; transport is closed', ErrorCode.TransportClosed, 400);\n }\n await this._attachPromise;\n // CAST: re-check after await — close() may have been called while waiting for attach.\n // TypeScript's control flow narrows _state after the first check, but the\n // await yields and close() can mutate _state concurrently.\n if ((this._state as ClientTransportState) === ClientTransportState.CLOSED) {\n throw new Ably.ErrorInfo('unable to send; transport is closed', ErrorCode.TransportClosed, 400);\n }\n\n // Spec: AIT-CT20\n const state = this._channel.state;\n if (state !== 'attached' && state !== 'attaching') {\n throw new Ably.ErrorInfo(`unable to send; channel is ${state}`, ErrorCode.ChannelNotReady, 400);\n }\n\n this._logger.trace('ClientTransport._internalSend();');\n\n const msgs = Array.isArray(input) ? input : [input];\n const turnId = crypto.randomUUID();\n this._ownTurnIds.add(turnId);\n this._tree.trackTurn(turnId, this._clientId ?? '');\n\n // Flush any events staged via stageEvents() since the last send. They\n // have already been applied to the tree, so merge them into the POST\n // body without re-applying. External eventNodes (e.g. from view.update)\n // have NOT been applied yet and need the optimistic tree update below.\n const flushedStaged = this._pendingLocalEvents;\n this._pendingLocalEvents = [];\n\n // Optimistic tree updates for external cross-turn events — must happen\n // before capturing history so the POST body includes the updated\n // message state.\n if (eventNodes && eventNodes.length > 0) {\n this._applyEventsToTree(eventNodes);\n }\n\n const allEventNodes: EventsNode<TEvent>[] = [...flushedStaged, ...(eventNodes ?? [])];\n\n const msgIds = new Set<string>();\n const postMessages: MessageNode<TMessage>[] = [];\n\n // The View pre-computed the visible branch before calling this delegate,\n // so preInsertHistory reflects the state before any optimistic inserts.\n const preInsertHistory = history;\n\n // Spec: AIT-CT3d\n // Auto-compute parent from the current thread if not explicitly provided\n let autoParent: string | undefined;\n if (sendOptions?.parent === undefined && !sendOptions?.forkOf) {\n const lastNode = preInsertHistory.at(-1);\n if (lastNode) {\n autoParent = lastNode.msgId;\n }\n }\n\n // Capture the first parent for the POST body before the loop advances it.\n const postParent = sendOptions?.parent === undefined ? autoParent : sendOptions.parent;\n\n for (const message of msgs) {\n const msgId = crypto.randomUUID();\n this._ownMsgIds.add(msgId);\n msgIds.add(msgId);\n\n const resolvedParent = sendOptions?.parent === undefined ? autoParent : sendOptions.parent;\n\n const optimisticHeaders = buildTransportHeaders({\n role: 'user',\n turnId,\n msgId,\n turnClientId: this._clientId,\n parent: resolvedParent,\n forkOf: sendOptions?.forkOf,\n });\n // Spec: AIT-CT3c\n // Optimistically insert each user message into the tree\n this._upsertAndNotify(message, optimisticHeaders);\n\n // Build MessageNode for the POST body\n postMessages.push({\n kind: 'message',\n message,\n msgId,\n parentId: resolvedParent,\n forkOf: sendOptions?.forkOf,\n headers: optimisticHeaders,\n serial: undefined,\n });\n\n // Spec: AIT-CT3e\n // Chain: each subsequent message in the batch parents off the previous\n // one, forming a linear conversation thread rather than siblings.\n if (sendOptions?.parent === undefined && !sendOptions?.forkOf) {\n autoParent = msgId;\n }\n }\n\n this._turnMsgIds.set(turnId, msgIds);\n\n // Create ReadableStream via router\n const stream = this._router.createStream(turnId);\n\n // Resolve headers and body\n const resolvedHeaders = this._headersFn?.() ?? {};\n const resolvedBody = this._bodyFn?.() ?? {};\n\n const postBody: Record<string, unknown> = {\n ...resolvedBody,\n history: preInsertHistory,\n ...sendOptions?.body,\n turnId,\n clientId: this._clientId,\n messages: postMessages,\n ...(sendOptions?.forkOf !== undefined && { forkOf: sendOptions.forkOf }),\n ...(postParent !== undefined && { parent: postParent }),\n ...(allEventNodes.length > 0 && { events: allEventNodes }),\n };\n\n const postHeaders: Record<string, string> = {\n ...resolvedHeaders,\n ...sendOptions?.headers,\n };\n\n // Spec: AIT-CT3a, AIT-CT3b\n // Fire-and-forget: POST must not block the stream return to the caller.\n // .catch() is intentional — async/await would delay stream availability.\n this._fetchFn(this._api, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...postHeaders,\n },\n body: JSON.stringify(postBody),\n ...(this._credentials ? { credentials: this._credentials } : {}),\n })\n .then((response) => {\n if (!response.ok) {\n const err = new Ably.ErrorInfo(\n `unable to send; HTTP POST to ${this._api} returned ${String(response.status)} ${response.statusText}`,\n ErrorCode.TransportSendFailed,\n response.status,\n );\n this._emitter.emit('error', err);\n this._router.errorStream(turnId, err);\n }\n })\n .catch((error: unknown) => {\n const cause = error instanceof Ably.ErrorInfo ? error : undefined;\n const err = new Ably.ErrorInfo(\n `unable to send; HTTP POST to ${this._api} failed: ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.TransportSendFailed,\n 500,\n cause,\n );\n this._emitter.emit('error', err);\n this._router.errorStream(turnId, err);\n });\n\n return {\n stream,\n turnId,\n cancel: async () => this.cancel({ turnId }),\n optimisticMsgIds: [...msgIds],\n };\n }\n\n // Spec: AIT-CT7, AIT-CT7a\n async cancel(filter?: CancelFilter): Promise<void> {\n if (this._state === ClientTransportState.CLOSED) return;\n const resolved = filter ?? { own: true };\n this._logger.debug('ClientTransport.cancel();', { filter: resolved });\n await this._publishCancel(resolved);\n this._closeMatchingTurnStreams(resolved);\n }\n\n stageEvents(msgId: string, events: TEvent[]): void {\n this._logger.trace('ClientTransport.stageEvents();', { msgId, eventCount: events.length });\n if (this._state === ClientTransportState.CLOSED) {\n this._logger.warn('ClientTransport.stageEvents(); transport is closed', { msgId });\n return;\n }\n if (!this._tree.getNode(msgId)) {\n this._logger.warn('ClientTransport.stageEvents(); msgId not found in tree', { msgId });\n return;\n }\n if (events.length === 0) return;\n const node: EventsNode<TEvent> = { kind: 'event', msgId, events };\n // Apply immediately so any subsequent useMessageSync / tree observer\n // sees the merged state — no window where the staged event can be\n // clobbered by an interleaved observer turn update.\n this._applyEventsToTree([node]);\n this._pendingLocalEvents.push(node);\n }\n\n stageMessage(msgId: string, message: TMessage): void {\n this._logger.trace('ClientTransport.stageMessage();', { msgId });\n if (this._state === ClientTransportState.CLOSED) {\n this._logger.warn('ClientTransport.stageMessage(); transport is closed', { msgId });\n return;\n }\n const existing = this._tree.getNode(msgId);\n if (!existing) {\n this._logger.warn('ClientTransport.stageMessage(); msgId not found in tree', { msgId });\n return;\n }\n // Preserve structural metadata; only the message body changes.\n this._tree.upsert(msgId, message, existing.headers, existing.serial);\n }\n\n // Apply events to the tree using the codec's accumulator. Shared by\n // stageEvents (local staging) and _internalSend (external eventNodes\n // arriving via view.update).\n private _applyEventsToTree(eventNodes: EventsNode<TEvent>[]): void {\n for (const node of eventNodes) {\n const existingNode = this._tree.getNode(node.msgId);\n if (!existingNode) continue;\n const outputs = node.events.map((event) => ({\n kind: 'event' as const,\n event,\n messageId: node.msgId,\n }));\n const accumulator = this._codec.createAccumulator();\n accumulator.initMessage(node.msgId, existingNode.message);\n accumulator.processOutputs(outputs);\n const updatedMsg = accumulator.messages.at(-1);\n if (updatedMsg) {\n this._tree.upsert(node.msgId, updatedMsg, existingNode.headers, existingNode.serial);\n }\n }\n }\n\n // Spec: AIT-CT18\n async waitForTurn(filter?: CancelFilter): Promise<void> {\n if (this._state === ClientTransportState.CLOSED) return;\n const resolved = filter ?? { own: true };\n const remaining = this._getMatchingTurnIds(resolved);\n if (remaining.size === 0) return;\n\n this._logger.debug('ClientTransport.waitForTurn();', { turnIds: [...remaining] });\n\n return new Promise<void>((resolve) => {\n let resolved = false;\n const done = (): void => {\n if (resolved) return;\n resolved = true;\n unsub();\n const idx = this._closeResolvers.indexOf(done);\n if (idx !== -1) this._closeResolvers.splice(idx, 1);\n resolve();\n };\n\n const unsub = this._tree.on('turn', (event: TurnLifecycleEvent) => {\n if (event.type !== EVENT_TURN_END) return;\n remaining.delete(event.turnId);\n if (remaining.size === 0) done();\n });\n\n // Resolve on transport close to prevent leaked subscriptions\n this._closeResolvers.push(done);\n });\n }\n\n // Spec: AIT-CT8, AIT-CT8c, AIT-CT8d\n on(event: 'error', handler: (error: Ably.ErrorInfo) => void): () => void {\n if (this._state === ClientTransportState.CLOSED) return noopUnsubscribe;\n // CAST: the overload signature enforces the correct handler type.\n const cb = handler as (arg: ClientTransportEventsMap[keyof ClientTransportEventsMap]) => void;\n this._emitter.on(event, cb);\n return () => {\n this._emitter.off(event, cb);\n };\n }\n\n // Spec: AIT-CT12, AIT-CT12a, AIT-CT12b, AIT-CT10c\n async close(options?: CloseOptions): Promise<void> {\n if (this._state === ClientTransportState.CLOSED) return;\n this._state = ClientTransportState.CLOSED;\n this._logger.info('ClientTransport.close();');\n\n // Best-effort cancel publish before tearing down local state\n if (options?.cancel) {\n try {\n await this._publishCancel(options.cancel);\n } catch {\n // Swallow: cancel is best-effort during teardown\n }\n this._closeMatchingTurnStreams(options.cancel);\n }\n\n this._channel.unsubscribe(this._onMessage);\n this._channel.off(this._onChannelStateChange);\n\n // Close any remaining active streams\n for (const turnId of this._ownTurnIds) {\n this._router.closeStream(turnId);\n }\n\n this._turnObservers.clear();\n this._emitter.off();\n for (const v of this._views) v.close();\n this._views.clear();\n for (const resolve of this._closeResolvers) resolve();\n this._closeResolvers.length = 0;\n this._ownTurnIds.clear();\n this._ownMsgIds.clear();\n this._turnMsgIds.clear();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a client-side transport that manages conversation state over an Ably channel.\n *\n * Subscribes to the channel immediately (before attach per RTL7g). The caller should\n * ensure the channel is attached or will be attached shortly after creation.\n * @param options - Configuration for the client transport.\n * @returns A new {@link ClientTransport} instance.\n */\nexport const createClientTransport = <TEvent, TMessage>(\n options: ClientTransportOptions<TEvent, TMessage>,\n): ClientTransport<TEvent, TMessage> => new DefaultClientTransport(options);\n","/**\n * Decoder core — action dispatch and serial tracking machinery.\n *\n * Handles the Ably message action patterns (create, append, update, delete)\n * and delegates to domain-specific hooks for event building and discrete\n * event decoding.\n *\n * Domain decoders call `createDecoderCore(hooks, options)` and provide hooks\n * for stream classification, event building, and discrete decoding.\n */\n\nimport type * as Ably from 'ably';\n\nimport { HEADER_MSG_ID, HEADER_STATUS, HEADER_STREAM, HEADER_STREAM_ID } from '../../constants.js';\nimport type { Logger } from '../../logger.js';\nimport { getHeaders } from '../../utils.js';\nimport type { DecoderOutput, MessagePayload, StreamTrackerState } from './types.js';\n\n/**\n * Wrap a domain event as a single-element decoder output array.\n * @param event - The domain event to wrap.\n * @returns A single-element array containing the event as a decoder output.\n */\nexport const eventOutput = <TEvent, TMessage>(event: TEvent): DecoderOutput<TEvent, TMessage>[] => [\n { kind: 'event', event },\n];\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/** Options for creating a decoder core. */\nexport interface DecoderCoreOptions {\n /** Called when a tracked stream is replaced (non-prefix update). Receives the tracker with updated state. */\n onStreamUpdate?: (tracker: StreamTrackerState) => void;\n /** Called when a message is deleted. Receives the serial and tracker (if one exists). */\n onStreamDelete?: (serial: string, tracker: StreamTrackerState | undefined) => void;\n /** Logger instance for diagnostic output. */\n logger?: Logger;\n}\n\n// ---------------------------------------------------------------------------\n// Domain hooks\n// ---------------------------------------------------------------------------\n\n/** Hooks that a domain codec provides to the decoder core for stream classification and event building. */\nexport interface DecoderCoreHooks<TEvent, TMessage> {\n /**\n * Build domain events emitted when a new stream starts. May return multiple\n * events (e.g. a start event and a start-step event).\n */\n buildStartEvents(tracker: StreamTrackerState): DecoderOutput<TEvent, TMessage>[];\n\n /** Build domain events for a text delta received on a stream. */\n buildDeltaEvents(tracker: StreamTrackerState, delta: string): DecoderOutput<TEvent, TMessage>[];\n\n /**\n * Build domain events emitted when a stream finishes (x-ably-status:finished).\n * Not called for aborted streams. The closing headers may differ from\n * tracker.headers if the closing append carried updated headers.\n */\n buildEndEvents(\n tracker: StreamTrackerState,\n closingHeaders: Record<string, string>,\n ): DecoderOutput<TEvent, TMessage>[];\n\n /**\n * Decode a discrete message (message.create where x-ably-stream is \"false\",\n * or a non-streamable first-contact update). Handles user messages, lifecycle\n * events, tool lifecycle, data-*, etc.\n */\n decodeDiscrete(input: MessagePayload): DecoderOutput<TEvent, TMessage>[];\n}\n\n// ---------------------------------------------------------------------------\n// Interface\n// ---------------------------------------------------------------------------\n\n/** The decoder core returned by {@link createDecoderCore}. */\nexport interface DecoderCore<TEvent, TMessage> {\n /** Decode a single Ably message into zero or more domain outputs. */\n decode(message: Ably.InboundMessage): DecoderOutput<TEvent, TMessage>[];\n}\n\n// ---------------------------------------------------------------------------\n// Default implementation\n// ---------------------------------------------------------------------------\n\n// Spec: AIT-CD7\nclass DefaultDecoderCore<TEvent, TMessage> implements DecoderCore<TEvent, TMessage> {\n private readonly _hooks: DecoderCoreHooks<TEvent, TMessage>;\n private readonly _logger: Logger | undefined;\n private readonly _onStreamUpdate: ((tracker: StreamTrackerState) => void) | undefined;\n private readonly _onStreamDelete: ((serial: string, tracker: StreamTrackerState | undefined) => void) | undefined;\n private readonly _serialState = new Map<string, StreamTrackerState>();\n\n constructor(hooks: DecoderCoreHooks<TEvent, TMessage>, options: DecoderCoreOptions = {}) {\n this._hooks = hooks;\n this._onStreamUpdate = options.onStreamUpdate;\n this._onStreamDelete = options.onStreamDelete;\n this._logger = options.logger?.withContext({ component: 'DecoderCore' });\n }\n\n decode(message: Ably.InboundMessage): DecoderOutput<TEvent, TMessage>[] {\n const action = message.action;\n\n this._logger?.trace('DefaultDecoderCore.decode();', { action, serial: message.serial, name: message.name });\n\n let outputs: DecoderOutput<TEvent, TMessage>[];\n\n switch (action) {\n // Spec: AIT-CD7a\n case 'message.create': {\n const payload = this._toPayload(message);\n\n outputs =\n payload.headers?.[HEADER_STREAM] === 'true'\n ? this._decodeStreamedCreate(payload, message.serial)\n : this._hooks.decodeDiscrete(payload);\n break;\n }\n\n case 'message.append': {\n outputs = this._decodeAppend(message);\n break;\n }\n\n case 'message.update': {\n outputs = this._decodeUpdate(message);\n break;\n }\n\n case 'message.delete': {\n outputs = this._decodeDelete(message);\n break;\n }\n\n default: {\n return [];\n }\n }\n\n // Tag all event outputs with the message ID from x-ably-msg-id for accumulator correlation.\n const messageId = getHeaders(message)[HEADER_MSG_ID];\n if (messageId) {\n for (const output of outputs) {\n if (output.kind === 'event') {\n output.messageId = messageId;\n }\n }\n }\n\n return outputs;\n }\n\n // -------------------------------------------------------------------------\n // Private: extract MessagePayload\n // -------------------------------------------------------------------------\n\n private _toPayload(message: Ably.InboundMessage): MessagePayload {\n return {\n name: message.name ?? '',\n // CAST: Ably SDK types `data` as `any`; cast to unknown is the safe boundary type.\n data: message.data as unknown,\n headers: getHeaders(message),\n };\n }\n\n /**\n * Extract string data from an Ably message, for stream accumulation paths.\n * @param message - The Ably message to extract string data from.\n * @returns The string data, or empty string if data is not a string.\n */\n private _stringData(message: Ably.InboundMessage): string {\n return typeof message.data === 'string' ? message.data : '';\n }\n\n // -------------------------------------------------------------------------\n // Private: safe callback invocation\n // -------------------------------------------------------------------------\n\n private _invokeOnStreamUpdate(tracker: StreamTrackerState): void {\n if (!this._onStreamUpdate) return;\n try {\n this._onStreamUpdate(tracker);\n } catch (error) {\n this._logger?.error('DefaultDecoderCore._invokeOnStreamUpdate(); callback threw', { error });\n }\n }\n\n private _invokeOnStreamDelete(serial: string, tracker: StreamTrackerState | undefined): void {\n if (!this._onStreamDelete) return;\n try {\n this._onStreamDelete(serial, tracker);\n } catch (error) {\n this._logger?.error('DefaultDecoderCore._invokeOnStreamDelete(); callback threw', { error });\n }\n }\n\n // -------------------------------------------------------------------------\n // Private: streamed message create\n // -------------------------------------------------------------------------\n\n private _decodeStreamedCreate(\n payload: MessagePayload,\n serial: string | undefined,\n ): DecoderOutput<TEvent, TMessage>[] {\n if (!serial) return [];\n\n const streamId = payload.headers?.[HEADER_STREAM_ID] ?? '';\n const h = payload.headers ?? {};\n\n const tracker: StreamTrackerState = {\n name: payload.name,\n streamId,\n accumulated: '',\n headers: { ...h },\n closed: false,\n };\n this._serialState.set(serial, tracker);\n\n this._logger?.debug('DefaultDecoderCore._decodeStreamedCreate(); new stream', {\n name: payload.name,\n streamId,\n serial,\n });\n\n return this._hooks.buildStartEvents(tracker);\n }\n\n // -------------------------------------------------------------------------\n // Private: append handling\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CD8\n private _decodeAppend(message: Ably.InboundMessage): DecoderOutput<TEvent, TMessage>[] {\n const serial = message.serial;\n if (!serial) return [];\n\n const tracker = this._serialState.get(serial);\n if (!tracker) {\n // Unknown serial on append — treat as first-contact update\n return this._decodeUpdate(message);\n }\n\n const h = getHeaders(message);\n const delta = typeof message.data === 'string' ? message.data : '';\n const status = h[HEADER_STATUS];\n const outputs: DecoderOutput<TEvent, TMessage>[] = [];\n\n if (delta.length > 0) {\n tracker.accumulated += delta;\n outputs.push(...this._hooks.buildDeltaEvents(tracker, delta));\n }\n\n if (status === 'finished' && !tracker.closed) {\n tracker.closed = true;\n outputs.push(...this._hooks.buildEndEvents(tracker, h));\n this._logger?.debug('DefaultDecoderCore._decodeAppend(); stream finished', { streamId: tracker.streamId });\n } else if (status === 'aborted' && !tracker.closed) {\n tracker.closed = true;\n this._logger?.debug('DefaultDecoderCore._decodeAppend(); stream aborted', { streamId: tracker.streamId });\n }\n\n return outputs;\n }\n\n // -------------------------------------------------------------------------\n // Private: update handling (first-contact, prefix-match, replacement)\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CD9\n private _decodeUpdate(message: Ably.InboundMessage): DecoderOutput<TEvent, TMessage>[] {\n const serial = message.serial;\n if (!serial) return [];\n\n const payload = this._toPayload(message);\n const h = payload.headers ?? {};\n const isStreamed = h[HEADER_STREAM] === 'true';\n const status = h[HEADER_STATUS];\n\n const tracker = this._serialState.get(serial);\n\n if (!tracker) {\n return this._decodeFirstContact(payload, isStreamed, status, serial);\n }\n\n // Updates to tracked streams use string data for prefix-match accumulation\n const data = this._stringData(message);\n\n // --- Tracker exists: prefix-match or replacement ---\n if (data.startsWith(tracker.accumulated)) {\n const delta = data.slice(tracker.accumulated.length);\n const outputs: DecoderOutput<TEvent, TMessage>[] = [];\n\n if (delta.length > 0) {\n tracker.accumulated = data;\n outputs.push(...this._hooks.buildDeltaEvents(tracker, delta));\n }\n\n if (status === 'finished' && !tracker.closed) {\n tracker.closed = true;\n outputs.push(...this._hooks.buildEndEvents(tracker, h));\n } else if (status === 'aborted' && !tracker.closed) {\n tracker.closed = true;\n }\n\n return outputs;\n }\n\n // --- Replacement (NOT a prefix match) ---\n tracker.accumulated = data;\n tracker.headers = { ...h };\n\n this._invokeOnStreamUpdate(tracker);\n\n return [];\n }\n\n private _decodeFirstContact(\n payload: MessagePayload,\n isStreamed: boolean,\n status: string | undefined,\n serial: string,\n ): DecoderOutput<TEvent, TMessage>[] {\n // Non-streamed messages are discrete\n if (!isStreamed) {\n return this._hooks.decodeDiscrete(payload);\n }\n\n const streamId = payload.headers?.[HEADER_STREAM_ID] ?? '';\n const h = payload.headers ?? {};\n const data = typeof payload.data === 'string' ? payload.data : '';\n\n this._logger?.debug('DefaultDecoderCore._decodeFirstContact(); first-contact stream', {\n name: payload.name,\n streamId,\n serial,\n });\n\n // Create tracker\n const newTracker: StreamTrackerState = {\n name: payload.name,\n streamId,\n accumulated: data,\n headers: { ...h },\n closed: status === 'finished' || status === 'aborted',\n };\n this._serialState.set(serial, newTracker);\n\n // Emit start + delta (if any) + end (if finished)\n const outputs = this._hooks.buildStartEvents(newTracker);\n\n if (data.length > 0) {\n outputs.push(...this._hooks.buildDeltaEvents(newTracker, data));\n }\n\n if (status === 'finished') {\n outputs.push(...this._hooks.buildEndEvents(newTracker, h));\n }\n\n return outputs;\n }\n\n // -------------------------------------------------------------------------\n // Private: delete handling\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CD10\n private _decodeDelete(message: Ably.InboundMessage): DecoderOutput<TEvent, TMessage>[] {\n const serial = message.serial;\n if (!serial) return [];\n\n const tracker = this._serialState.get(serial);\n\n this._invokeOnStreamDelete(serial, tracker);\n\n if (tracker) {\n tracker.accumulated = '';\n tracker.closed = true;\n }\n\n this._logger?.debug('DefaultDecoderCore._decodeDelete();', { serial });\n\n return [];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a decoder core with the given domain hooks.\n * @param hooks - Domain-specific hooks for stream classification, event building, and discrete decoding.\n * @param options - Decoder configuration (callbacks, logger).\n * @returns A new {@link DecoderCore} instance.\n */\nexport const createDecoderCore = <TEvent, TMessage>(\n hooks: DecoderCoreHooks<TEvent, TMessage>,\n options: DecoderCoreOptions = {},\n): DecoderCore<TEvent, TMessage> => new DefaultDecoderCore(hooks, options);\n","/**\n * Encoder core — message append lifecycle machinery.\n *\n * Provides Ably primitives (publish, append, close, abort, flush) that\n * domain-specific encoders wire their event types to.\n *\n * Domain encoders call `createEncoderCore(writer, options)` and use the\n * returned core to map domain events to Ably operations without\n * reimplementing the message append lifecycle.\n */\n\nimport * as Ably from 'ably';\n\nimport { HEADER_DISCRETE, HEADER_MSG_ID, HEADER_STATUS, HEADER_STREAM, HEADER_STREAM_ID } from '../../constants.js';\nimport { ErrorCode } from '../../errors.js';\nimport type { Logger } from '../../logger.js';\nimport { mergeHeaders } from '../../utils.js';\nimport type { ChannelWriter, EncoderOptions, Extras, MessagePayload, StreamPayload, WriteOptions } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/** Options for creating an encoder core. Extends {@link EncoderOptions} with a logger. */\nexport interface EncoderCoreOptions extends EncoderOptions {\n /** Logger instance for diagnostic output. */\n logger?: Logger;\n}\n\n// ---------------------------------------------------------------------------\n// Stream tracker (internal)\n// ---------------------------------------------------------------------------\n\ninterface StreamState {\n serial: string;\n name: string;\n streamId: string;\n accumulated: string;\n persistentHeaders: Record<string, string>;\n aborted: boolean;\n}\n\ninterface PendingAppend {\n promise: Promise<Ably.UpdateDeleteResult>;\n streamId: string;\n}\n\n// ---------------------------------------------------------------------------\n// Encoder core interface\n// ---------------------------------------------------------------------------\n\n/** The core encoder primitives that domain codec encoders delegate to. */\nexport interface EncoderCore {\n /** Publish a single discrete (non-streaming) message described by a payload. */\n publishDiscrete(payload: MessagePayload, opts?: WriteOptions): Promise<Ably.PublishResult>;\n\n /** Publish multiple discrete messages atomically in a single channel publish. */\n publishDiscreteBatch(payloads: MessagePayload[], opts?: WriteOptions): Promise<Ably.PublishResult>;\n\n /** Start a streamed message with x-ably-status:streaming. */\n startStream(streamId: string, payload: StreamPayload, opts?: WriteOptions): Promise<void>;\n\n /**\n * Append data to an in-flight streamed message. Fire-and-forget: errors are\n * collected internally and surfaced by {@link closeStream} or {@link close}.\n */\n appendStream(streamId: string, data: string): void;\n\n /**\n * Close a streamed message with x-ably-status:finished. Flushes all pending\n * appends for recovery before returning. Repeats persistent and payload headers.\n */\n closeStream(streamId: string, payload: StreamPayload): Promise<void>;\n\n /**\n * Abort a single in-progress stream (x-ably-status:aborted) and flush all\n * pending appends for recovery before returning.\n */\n abortStream(streamId: string, opts?: WriteOptions): Promise<void>;\n\n /**\n * Abort all in-progress streams (x-ably-status:aborted) and flush all\n * pending appends for recovery before returning.\n */\n abortAllStreams(opts?: WriteOptions): Promise<void>;\n\n /** Flush + clear trackers. Idempotent. */\n close(): Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// Default implementation\n// ---------------------------------------------------------------------------\n\n// Spec: AIT-CD1\nclass DefaultEncoderCore implements EncoderCore {\n private readonly _writer: ChannelWriter;\n private readonly _defaultClientId: string | undefined;\n private readonly _defaultExtras: Extras | undefined;\n private readonly _onMessageHook: (message: Ably.Message) => void;\n private readonly _logger: Logger | undefined;\n private readonly _trackers = new Map<string, StreamState>();\n private _pending: PendingAppend[] = [];\n private _flushPromise: Promise<void> | undefined;\n private _closed = false;\n\n constructor(writer: ChannelWriter, options: EncoderCoreOptions = {}) {\n this._writer = writer;\n this._defaultClientId = options.clientId;\n this._defaultExtras = options.extras;\n this._onMessageHook =\n options.onMessage ??\n (() => {\n /* noop */\n });\n this._logger = options.logger?.withContext({ component: 'EncoderCore' });\n }\n\n // Spec: AIT-CD11\n async publishDiscrete(payload: MessagePayload, opts?: WriteOptions): Promise<Ably.PublishResult> {\n this._assertNotClosed();\n this._logger?.trace('DefaultEncoderCore.publishDiscrete();', { name: payload.name });\n const msg = this._buildDiscreteMessage(payload, opts);\n return this._writer.publish(msg);\n }\n\n // Spec: AIT-CD11a\n async publishDiscreteBatch(payloads: MessagePayload[], opts?: WriteOptions): Promise<Ably.PublishResult> {\n this._assertNotClosed();\n this._logger?.trace('DefaultEncoderCore.publishDiscreteBatch();', { count: payloads.length });\n const msgs = payloads.map((p) => this._buildDiscreteMessage(p, opts));\n // Mark batch-published payloads as discrete message parts (from writeMessages).\n // The decoder relies on this header to distinguish message parts from lifecycle\n // events that also happen to be discrete (x-ably-stream: false).\n for (const msg of msgs) {\n // CAST: extras is built by _buildDiscreteMessage with a known { headers } shape.\n (msg.extras as { headers: Record<string, string> }).headers[HEADER_DISCRETE] = 'true';\n }\n return this._writer.publish(msgs);\n }\n\n // Spec: AIT-CD2\n async startStream(streamId: string, payload: StreamPayload, opts?: WriteOptions): Promise<void> {\n this._assertNotClosed();\n this._logger?.trace('DefaultEncoderCore.startStream();', { name: payload.name, streamId });\n\n const allHeaders = this._buildHeaders(payload.headers ?? {}, opts);\n allHeaders[HEADER_STREAM] = 'true';\n allHeaders[HEADER_STATUS] = 'streaming';\n allHeaders[HEADER_STREAM_ID] = streamId;\n\n const clientId = this._resolveClientId(opts);\n const msg: Ably.Message = {\n name: payload.name,\n data: payload.data,\n extras: { headers: allHeaders },\n ...(clientId ? { clientId } : {}),\n };\n\n this._invokeOnMessage(msg);\n const result = await this._writer.publish(msg);\n const serial = result.serials[0];\n\n // Spec: AIT-CD2a\n if (!serial) {\n throw new Ably.ErrorInfo(\n `unable to start stream; no serial returned for stream '${payload.name}' (streamId: ${streamId})`,\n ErrorCode.BadRequest,\n 400,\n );\n }\n\n this._trackers.set(streamId, {\n serial,\n name: payload.name,\n streamId,\n accumulated: payload.data,\n persistentHeaders: allHeaders,\n aborted: false,\n });\n\n this._logger?.debug('DefaultEncoderCore.startStream(); stream started', {\n name: payload.name,\n streamId,\n serial,\n });\n }\n\n // Spec: AIT-CD3\n appendStream(streamId: string, data: string): void {\n this._assertNotClosed();\n // Spec: AIT-CD3a\n const tracker = this._trackers.get(streamId);\n if (!tracker) {\n throw new Ably.ErrorInfo(\n `unable to append to stream; no active stream for streamId '${streamId}'`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n\n tracker.accumulated += data;\n\n const appendMsg: Ably.Message = {\n serial: tracker.serial,\n data,\n extras: { headers: { ...tracker.persistentHeaders } },\n };\n\n this._invokeOnMessage(appendMsg);\n const p = this._writer.appendMessage(appendMsg);\n this._pending.push({ promise: p, streamId });\n }\n\n // Spec: AIT-CD4, AIT-CD4a\n async closeStream(streamId: string, payload: StreamPayload): Promise<void> {\n this._assertNotClosed();\n this._logger?.trace('DefaultEncoderCore.closeStream();', { streamId });\n\n const tracker = this._trackers.get(streamId);\n if (!tracker) {\n throw new Ably.ErrorInfo(\n `unable to close stream; no active stream for streamId '${streamId}'`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n\n // Accumulate closing data so recovery has the full content\n tracker.accumulated += payload.data;\n\n const allHeaders = this._buildClosingHeaders(tracker, payload.headers ?? {});\n allHeaders[HEADER_STATUS] = 'finished';\n\n const msg: Ably.Message = {\n serial: tracker.serial,\n data: payload.data,\n extras: { headers: allHeaders },\n };\n\n this._invokeOnMessage(msg);\n const p = this._writer.appendMessage(msg);\n this._pending.push({ promise: p, streamId });\n\n await this._flushPending();\n\n this._logger?.debug('DefaultEncoderCore.closeStream(); stream closed', { streamId });\n }\n\n // Spec: AIT-CD5, AIT-CD5b\n async abortStream(streamId: string, opts?: WriteOptions): Promise<void> {\n this._assertNotClosed();\n this._logger?.trace('DefaultEncoderCore.abortStream();', { streamId });\n\n const tracker = this._trackers.get(streamId);\n if (!tracker) {\n throw new Ably.ErrorInfo(\n `unable to abort stream; no active stream for streamId '${streamId}'`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n\n tracker.aborted = true;\n\n const allHeaders = this._buildClosingHeaders(tracker, {}, opts);\n allHeaders[HEADER_STATUS] = 'aborted';\n\n const msg: Ably.Message = {\n serial: tracker.serial,\n data: '',\n extras: { headers: allHeaders },\n };\n\n this._invokeOnMessage(msg);\n const p = this._writer.appendMessage(msg);\n this._pending.push({ promise: p, streamId });\n\n await this._flushPending();\n\n this._logger?.debug('DefaultEncoderCore.abortStream(); stream aborted', { streamId });\n }\n\n // Spec: AIT-CD5a\n async abortAllStreams(opts?: WriteOptions): Promise<void> {\n this._assertNotClosed();\n this._logger?.trace('DefaultEncoderCore.abortAllStreams();', { streamCount: this._trackers.size });\n\n for (const tracker of this._trackers.values()) {\n tracker.aborted = true;\n\n const allHeaders = this._buildClosingHeaders(tracker, {}, opts);\n allHeaders[HEADER_STATUS] = 'aborted';\n\n const msg: Ably.Message = {\n serial: tracker.serial,\n data: '',\n extras: { headers: allHeaders },\n };\n\n this._invokeOnMessage(msg);\n const p = this._writer.appendMessage(msg);\n this._pending.push({ promise: p, streamId: tracker.streamId });\n }\n\n await this._flushPending();\n }\n\n // Spec: AIT-CD6\n private async _flushPending(): Promise<void> {\n // Re-entrancy guard: if a flush is already in progress, await it instead of starting a new one.\n if (this._flushPromise) {\n return this._flushPromise;\n }\n\n const snapshot = this._pending;\n this._pending = [];\n\n if (snapshot.length === 0) return;\n\n this._logger?.trace('DefaultEncoderCore._flushPending();', { count: snapshot.length });\n\n this._flushPromise = this._doFlush(snapshot);\n try {\n await this._flushPromise;\n } finally {\n this._flushPromise = undefined;\n }\n }\n\n private async _doFlush(snapshot: PendingAppend[]): Promise<void> {\n const results = await Promise.allSettled(snapshot.map(async (p) => p.promise));\n const failures = new Set<string>();\n\n for (const [i, result] of results.entries()) {\n const entry = snapshot[i];\n if (entry && result.status === 'rejected') {\n failures.add(entry.streamId);\n }\n }\n\n if (failures.size === 0) {\n this._logger?.debug('DefaultEncoderCore._flushPending(); all appends succeeded');\n return;\n }\n\n this._logger?.warn('DefaultEncoderCore._flushPending(); recovering failed appends', {\n failedStreams: [...failures],\n });\n\n const recoveryErrors: { streamId: string; error: unknown }[] = [];\n\n for (const streamId of failures) {\n const tracker = this._trackers.get(streamId);\n if (!tracker) continue;\n\n const recoveryStatus = tracker.aborted ? 'aborted' : 'finished';\n const msg: Ably.Message = {\n serial: tracker.serial,\n data: tracker.accumulated,\n extras: { headers: { ...tracker.persistentHeaders, [HEADER_STATUS]: recoveryStatus } },\n };\n\n try {\n await this._writer.updateMessage(msg);\n } catch (error) {\n recoveryErrors.push({ streamId, error });\n }\n }\n\n if (recoveryErrors.length > 0) {\n const ids = recoveryErrors.map((e) => e.streamId).join(', ');\n this._logger?.error('DefaultEncoderCore._flushPending(); recovery failed', { failedStreams: ids });\n throw new Ably.ErrorInfo(\n `unable to flush pending appends; recovery failed for stream(s): ${ids}`,\n ErrorCode.EncoderRecoveryFailed,\n 500,\n );\n }\n }\n\n // Spec: AIT-CD12\n async close(): Promise<void> {\n if (this._closed) return;\n this._logger?.trace('DefaultEncoderCore.close();');\n this._closed = true;\n try {\n await this._flushPending();\n } finally {\n this._trackers.clear();\n }\n this._logger?.debug('DefaultEncoderCore.close(); encoder closed');\n }\n\n // -------------------------------------------------------------------------\n // Private helpers\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CD14\n private _invokeOnMessage(msg: Ably.Message): void {\n try {\n this._onMessageHook(msg);\n } catch (error) {\n this._logger?.error('DefaultEncoderCore._invokeOnMessage(); hook threw', { error });\n }\n }\n\n private _assertNotClosed(): void {\n if (this._closed) {\n throw new Ably.ErrorInfo('unable to write to encoder; encoder has been closed', ErrorCode.InvalidArgument, 400);\n }\n }\n\n private _resolveClientId(opts?: WriteOptions): string | undefined {\n return opts?.clientId ?? this._defaultClientId;\n }\n\n private _buildHeaders(codecHeaders: Record<string, string>, opts?: WriteOptions): Record<string, string> {\n const callerHeaders = mergeHeaders(this._defaultExtras?.headers, opts?.extras?.headers);\n const merged = { ...callerHeaders, ...codecHeaders };\n if (opts?.messageId !== undefined) {\n merged[HEADER_MSG_ID] = opts.messageId;\n }\n return merged;\n }\n\n private _buildDiscreteMessage(payload: MessagePayload, opts?: WriteOptions): Ably.Message {\n const headers = this._buildHeaders(payload.headers ?? {}, opts);\n headers[HEADER_STREAM] = 'false';\n const clientId = this._resolveClientId(opts);\n\n const msg: Ably.Message = {\n name: payload.name,\n data: payload.data,\n extras: {\n headers,\n ...(payload.ephemeral ? { ephemeral: true } : {}),\n },\n ...(clientId ? { clientId } : {}),\n };\n\n this._invokeOnMessage(msg);\n return msg;\n }\n\n /**\n * Build headers for a closing append. Closing appends must repeat ALL\n * persistent headers (Ably replaces the entire extras object on append).\n * Then layer caller and codec overrides.\n * @param tracker - The stream tracker with persistent headers.\n * @param codecHeaders - Codec-layer headers to merge.\n * @param opts - Optional per-write overrides.\n * @returns Merged headers for the closing append.\n */\n private _buildClosingHeaders(\n tracker: StreamState,\n codecHeaders: Record<string, string>,\n opts?: WriteOptions,\n ): Record<string, string> {\n const h = { ...tracker.persistentHeaders };\n const callerHeaders = mergeHeaders(this._defaultExtras?.headers, opts?.extras?.headers);\n Object.assign(h, callerHeaders);\n Object.assign(h, codecHeaders);\n return h;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create an encoder core bound to the given channel writer.\n * @param writer - The channel writer to publish messages through.\n * @param options - Encoder configuration (clientId, extras, hooks, logger).\n * @returns A new {@link EncoderCore} instance.\n */\nexport const createEncoderCore = (writer: ChannelWriter, options: EncoderCoreOptions = {}): EncoderCore =>\n new DefaultEncoderCore(writer, options);\n","/**\n * Generic lifecycle tracker for codec decoders.\n *\n * Manages per-scope (typically per-turn) tracking of lifecycle phases that\n * must be emitted before content events. When a phase has not been emitted\n * (e.g. mid-stream join), the tracker synthesizes the missing events using\n * codec-provided build functions.\n *\n * Codecs configure the tracker with an ordered list of phases, then compose\n * it into their decoder hooks. The tracker is independent of any specific\n * codec or event type.\n */\n\n// ---------------------------------------------------------------------------\n// Phase configuration\n// ---------------------------------------------------------------------------\n\n/**\n * Configuration for a single lifecycle phase that may need to be\n * synthesized when missing from the wire stream.\n */\nexport interface PhaseConfig<TEvent> {\n /** Unique key identifying this phase (e.g. \"start\", \"start-step\"). */\n key: string;\n /**\n * Build the synthetic event(s) for this phase. Called with a context\n * record that codecs populate at the call site — the tracker passes\n * it through without interpreting it.\n * @param context - Key-value pairs from the call site (e.g. headers).\n * @returns One or more synthetic events to emit for this phase.\n */\n build(context: Record<string, string | undefined>): TEvent[];\n}\n\n// ---------------------------------------------------------------------------\n// Tracker interface\n// ---------------------------------------------------------------------------\n\n/**\n * Per-scope lifecycle tracker that ensures required phases are emitted\n * before content events, synthesizing missing ones for mid-stream joins.\n *\n * Scoped by an arbitrary string key (typically a turn ID). Each scope\n * tracks independently which phases have been emitted.\n */\nexport interface LifecycleTracker<TEvent> {\n /**\n * Ensure all configured phases have been emitted for the given scope.\n * Returns synthetic events for any phases not yet marked as emitted,\n * then marks them. Returns an empty array if all phases are current.\n * @param scopeId - The scope to check (e.g. turn ID).\n * @param context - Key-value pairs passed through to phase build functions.\n * @returns Synthetic events for missing phases, in configuration order.\n */\n ensurePhases(scopeId: string, context: Record<string, string | undefined>): TEvent[];\n\n /**\n * Mark a phase as emitted from the wire (not synthetic). Call this\n * when the real event arrives so the tracker does not re-synthesize it.\n * @param scopeId - The scope (e.g. turn ID).\n * @param phaseKey - The phase key to mark.\n */\n markEmitted(scopeId: string, phaseKey: string): void;\n\n /**\n * Reset a phase so it will be re-synthesized on the next\n * {@link ensurePhases} call. Used for repeating phases (e.g. \"start-step\"\n * resets after \"finish-step\").\n * @param scopeId - The scope (e.g. turn ID).\n * @param phaseKey - The phase key to reset.\n */\n resetPhase(scopeId: string, phaseKey: string): void;\n\n /**\n * Remove all tracking state for a scope. Call on turn completion\n * (finish, abort) to free memory.\n * @param scopeId - The scope to clear.\n */\n clearScope(scopeId: string): void;\n}\n\n// ---------------------------------------------------------------------------\n// Default implementation\n// ---------------------------------------------------------------------------\n\n// Spec: AIT-CD13\nclass DefaultLifecycleTracker<TEvent> implements LifecycleTracker<TEvent> {\n private readonly _phases: PhaseConfig<TEvent>[];\n private readonly _emitted = new Map<string, Set<string>>();\n\n constructor(phases: PhaseConfig<TEvent>[]) {\n this._phases = phases;\n }\n\n ensurePhases(scopeId: string, context: Record<string, string | undefined>): TEvent[] {\n const emitted = this._getOrCreate(scopeId);\n const events: TEvent[] = [];\n for (const phase of this._phases) {\n if (!emitted.has(phase.key)) {\n emitted.add(phase.key);\n events.push(...phase.build(context));\n }\n }\n return events;\n }\n\n markEmitted(scopeId: string, phaseKey: string): void {\n this._getOrCreate(scopeId).add(phaseKey);\n }\n\n resetPhase(scopeId: string, phaseKey: string): void {\n this._emitted.get(scopeId)?.delete(phaseKey);\n }\n\n clearScope(scopeId: string): void {\n this._emitted.delete(scopeId);\n }\n\n private _getOrCreate(scopeId: string): Set<string> {\n let set = this._emitted.get(scopeId);\n if (!set) {\n set = new Set();\n this._emitted.set(scopeId, set);\n }\n return set;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a lifecycle tracker configured with the given phases.\n * Phases are checked and synthesized in array order.\n * @param phases - Ordered phase configurations.\n * @returns A new {@link LifecycleTracker} instance.\n */\nexport const createLifecycleTracker = <TEvent>(phases: PhaseConfig<TEvent>[]): LifecycleTracker<TEvent> =>\n new DefaultLifecycleTracker(phases);\n"],"mappings":"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"}
1
+ {"version":3,"file":"ably-ai-transport.umd.cjs","names":[],"sources":["../src/core/transport/invocation.ts","../src/constants.ts","../src/errors.ts","../src/utils.ts","../src/version.ts","../src/core/agent.ts","../src/core/transport/headers.ts","../src/core/transport/internal/bounded-map.ts","../src/core/transport/branch-chain.ts","../src/core/transport/decode-fold.ts","../src/core/transport/load-conversation.ts","../src/core/transport/pipe-stream.ts","../src/core/transport/run-manager.ts","../src/core/transport/agent-session.ts","../src/event-emitter.ts","../src/logger.ts","../src/core/transport/tree.ts","../src/core/transport/load-history.ts","../src/core/transport/view.ts","../src/core/transport/client-session.ts","../src/core/codec/encoder.ts","../src/core/codec/decoder.ts","../src/core/codec/lifecycle-tracker.ts"],"sourcesContent":["/**\n * Invocation — value object wrapping the JSON body that a client sends to\n * an agent's HTTP endpoint to start a run.\n *\n * The data shape is the wire contract; the {@link Invocation} class is a\n * runtime view of that data with the same fields. {@link Invocation.fromJSON}\n * is the entry point used by agent handlers:\n *\n * ```ts\n * const data = (await req.json()) as InvocationData;\n * const invocation = Invocation.fromJSON(data);\n * const run = session.createRun(invocation, { signal: req.signal });\n * await run.start();\n * await run.loadProjection(); // fetch run projection from the channel\n * const messages = run.messages;\n * ```\n *\n * The body carries only what the agent needs out-of-band before the channel\n * is observable: the session/channel name and the `inputEventId` that triggered\n * the invocation. The agent mints the `invocationId` itself (one per HTTP\n * request) and returns it on the HTTP response, so it is not a body field. Run\n * identity also lives on the channel: the agent mints the `runId` for a fresh\n * run and reads the existing `runId` off the triggering input event for a\n * continuation — so the body carries no run-id either. Per-message metadata —\n * `clientId`, `parent`, `forkOf`, continuation status — likewise lives on the\n * channel and is resolved by the agent from the triggering input event, not\n * from the body. The `inputClientId` the agent re-stamps on its own publishes\n * comes from the publisher's Ably `clientId` on the matched input event, not\n * from a body field.\n */\n\n// ---------------------------------------------------------------------------\n// Wire shape\n// ---------------------------------------------------------------------------\n\n/**\n * Wire shape of a single invocation — the JSON body sent from the client\n * transport's HTTP POST to the agent endpoint.\n */\nexport interface InvocationData {\n /**\n * Identifier for the specific input event on the channel that triggered\n * this invocation. The agent locates the event via the `event-id`\n * header. Its wire headers carry the run-id for a continuation (absent for\n * a fresh run), so run identity is resolved from the channel, not the body.\n */\n inputEventId: string;\n /** Logical name of the session (chat) — used as the Ably channel name. */\n sessionName: string;\n}\n\n// ---------------------------------------------------------------------------\n// Runtime view\n// ---------------------------------------------------------------------------\n\n/**\n * Runtime view of an {@link InvocationData}. Constructed via\n * {@link Invocation.fromJSON}. Read-only; carries no behaviour beyond\n * exposing its fields.\n */\n// Spec: AIT-ST13\nexport class Invocation {\n /**\n * Identifier for the specific input event on the channel that triggered\n * this invocation. Run identity is resolved from that event's wire headers\n * (or minted), not from the body.\n */\n readonly inputEventId: string;\n /** Logical name of the session (chat). Used as the Ably channel name. */\n readonly sessionName: string;\n\n private constructor(data: InvocationData) {\n this.inputEventId = data.inputEventId;\n this.sessionName = data.sessionName;\n }\n\n /**\n * Build an Invocation from its JSON wire shape.\n * @param data - Parsed JSON body matching {@link InvocationData}.\n * @returns A new Invocation exposing the same fields.\n */\n static fromJSON(data: InvocationData): Invocation {\n return new Invocation(data);\n }\n\n /**\n * Serialise this invocation to its JSON wire shape — the body a client\n * POSTs to the agent's endpoint to wake a run. Round-trips through\n * {@link Invocation.fromJSON}.\n * @returns The {@link InvocationData} carrying this invocation's identity.\n */\n toJSON(): InvocationData {\n return {\n inputEventId: this.inputEventId,\n sessionName: this.sessionName,\n };\n }\n}\n","/**\n * Shared constants used by both codec and transport layers.\n *\n * Header constants define the transport wire header names. Message and event\n * name constants define the session lifecycle signals on the channel.\n *\n * These live at the top level (not in codec/ or transport/) because both\n * layers need them — the codec core reads/writes stream and status headers,\n * while the transport layer reads/writes run, cancel, and role headers.\n */\n\n// ---------------------------------------------------------------------------\n// Stream headers (used by codec encoder/decoder core)\n// ---------------------------------------------------------------------------\n\n/** Header: whether this Ably message uses streaming (message appends) or is discrete. Always \"true\" or \"false\". */\nexport const HEADER_STREAM = 'stream';\n\n/** Header: lifecycle status of a streamed message. Only set when stream is \"true\". One of \"streaming\", \"complete\", or \"cancelled\". */\nexport const HEADER_STATUS = 'status';\n\n/** Header: stream identity. Set by the encoder on every streamed message; read by the decoder to correlate streams. */\nexport const HEADER_STREAM_ID = 'stream-id';\n\n/** Header: marks a message as a discrete message part (from writeMessages). Set by publishDiscreteBatch; not set on lifecycle events from publishDiscrete. */\nexport const HEADER_DISCRETE = 'discrete';\n\n// ---------------------------------------------------------------------------\n// Identity headers (used by transport for run correlation)\n// ---------------------------------------------------------------------------\n\n/** Header: run correlation ID. Set on every agent-published message and on continuation client inputs, but omitted from the originating fresh client input (the agent mints the run-id at run-start). */\nexport const HEADER_RUN_ID = 'run-id';\n\n/** Header: invocation correlation ID; identifies a specific invocation under a run. Agent-minted and stamped by the agent on every event it publishes for the invocation — run lifecycle (run-start/resume/suspend/end) and assistant outputs. Never set by the client on its input. */\nexport const HEADER_INVOCATION_ID = 'invocation-id';\n\n/**\n * Header: per-event identifier stamped by the client on every\n * client-published event in a send — user-message events AND amend\n * events (tool-approval responses, client tool outputs). Distinct from\n * `codec-message-id` so it survives edits/retries that reuse the same\n * codec-message-id, and so amend events that target an existing message can\n * carry their own per-send identity. The invocation body lists every\n * inputEventId the agent must observe on the channel before starting LLM\n * work — see `Run.start()`'s input-event lookup.\n */\nexport const HEADER_EVENT_ID = 'event-id';\n\n/** Header: message identity. Assigned per message (user or assistant). Used for optimistic reconciliation on the client. */\nexport const HEADER_CODEC_MESSAGE_ID = 'codec-message-id';\n\n/** Header: clientId of the user who initiated the run. Stamped by the client on its user input and re-stamped by the agent on the run's lifecycle and stream messages. */\nexport const HEADER_RUN_CLIENT_ID = 'run-client-id';\n\n/**\n * Header: clientId of the input event (the `ai-input`) that drove the\n * current invocation. The agent reads the publisher's Ably-level `clientId`\n * from the triggering input event on the channel and re-stamps it as\n * `input-client-id` on every event it publishes for that invocation\n * (run lifecycle and assistant outputs). May differ from\n * `run-client-id` on continuation invocations driven by an input\n * from a non-owner (e.g. a tool-result publish from a different client).\n * Not stamped on `ai-input` events themselves — the wire publisher's\n * Ably `clientId` already conveys that.\n */\nexport const HEADER_INPUT_CLIENT_ID = 'input-client-id';\n\n/** Header: message role (e.g. \"user\", \"assistant\"). */\nexport const HEADER_ROLE = 'role';\n\n// ---------------------------------------------------------------------------\n// Fork / branching headers\n// ---------------------------------------------------------------------------\n\n/** Header: the codec-message-id of the immediately preceding message in this branch. */\nexport const HEADER_PARENT = 'parent';\n\n/** Header: the codec-message-id of the message this one replaces (creates a fork). */\nexport const HEADER_FORK_OF = 'fork-of';\n\n/**\n * Header: the codec-message-id of the assistant message this run regenerates.\n *\n * Stamped on the regenerate wire (and echoed on `run-start`) when the\n * client requested a regeneration. A regenerate run parents at the SAME input\n * node as the reply it regenerates, so it joins that input's reply runs as a\n * same-parent sibling (no fork-of). The View consults this header to resolve\n * the message-level sibling group and to drop the regenerated message from\n * earlier Runs in the visible chain (Spec: AIT-CT13d).\n */\nexport const HEADER_MSG_REGENERATE = 'msg-regenerate';\n\n// ---------------------------------------------------------------------------\n// Run lifecycle headers\n// ---------------------------------------------------------------------------\n\n/** Header: reason a run ended (on ai-run-end messages). */\nexport const HEADER_RUN_REASON = 'run-reason';\n\n/**\n * Header: the `codec-message-id` of the input event that triggered the run.\n * The triggering input is the one whose `event-id` matches the invocation's\n * `inputEventId` (the last input of the originating send). The agent\n * re-stamps it on every event it publishes for the invocation (run\n * lifecycle + assistant outputs), mirroring `input-client-id`. This is the\n * codec-message-id the client owns at send time, so it lets the client\n * correlate any of those events back to the originating input without\n * depending on a client-minted run-id or invocation-id.\n */\nexport const HEADER_INPUT_CODEC_MESSAGE_ID = 'input-codec-message-id';\n\n// ---------------------------------------------------------------------------\n// Run-end error headers (set on `ai-run-end` when `run-reason: error`)\n// ---------------------------------------------------------------------------\n\n/** Header: numeric error code accompanying an `ai-run-end` with reason `error`. */\nexport const HEADER_ERROR_CODE = 'error-code';\n\n/** Header: human-readable error message accompanying an `ai-run-end` with reason `error`. */\nexport const HEADER_ERROR_MESSAGE = 'error-message';\n\n// ---------------------------------------------------------------------------\n// Message / event names\n// ---------------------------------------------------------------------------\n\n/**\n * Message name: client->agent cancel intent. Targets a run by `run-id` (a\n * continuation, whose run-id the client already knows) and/or by\n * `input-codec-message-id` (a fresh send, whose run-id the agent mints at\n * run-start — so the client can only key the cancel by the triggering input's\n * codec-message-id it owns at send time). The agent resolves whichever is\n * present to the registered run; a cancel that arrives before the run is known\n * (the input-event lookup hasn't resolved the input id to a run yet) is\n * buffered by `input-codec-message-id` and honoured when the run resolves it.\n * Also carries an `event-id` so channel rewind redelivers it to a per-request /\n * serverless agent that attaches after the cancel was published.\n */\nexport const EVENT_CANCEL = 'ai-cancel';\n\n/** Message name: server publishes this to signal a run has started. */\nexport const EVENT_RUN_START = 'ai-run-start';\n\n/**\n * Message name: server publishes this to signal a run has suspended — paused\n * awaiting participant input (e.g. a client tool result or approval) without\n * ending. The run stays live and may be resumed under the same `runId`.\n * Distinct from `ai-run-end`, which is terminal.\n */\nexport const EVENT_RUN_SUSPEND = 'ai-run-suspend';\n\n/**\n * Message name: server publishes this when a subsequent invocation re-enters an\n * already-started run (e.g. a tool-result follow-up under the same `runId`).\n * A pure re-entry signal: unlike `ai-run-start` it carries no `parent` / `fork-of`\n * (the original `ai-run-start` already established the run's structure).\n */\nexport const EVENT_RUN_RESUME = 'ai-run-resume';\n\n/** Message name: server publishes this to signal a run has ended. */\nexport const EVENT_RUN_END = 'ai-run-end';\n\n/**\n * Message name: every agent-published codec event (text, reasoning, tool calls,\n * tool outputs, lifecycle helpers, file / source parts, data-* chunks) rides\n * this single wire name. The codec event's own `type` is carried in the\n * codec-level `type` header so the decoder can dispatch.\n */\nexport const EVENT_AI_OUTPUT = 'ai-output';\n\n/**\n * Message name: every client-published codec event (user-message parts,\n * tool-approval responses, regenerate signals) rides this single wire\n * name. The codec event's own kind is carried in the codec-level `type`\n * header so the decoder can dispatch.\n */\nexport const EVENT_AI_INPUT = 'ai-input';\n","import * as Ably from 'ably';\n\n/**\n * Error codes for the AI Transport SDK.\n */\nexport enum ErrorCode {\n /**\n * The request was invalid.\n */\n BadRequest = 40000,\n\n /**\n * Invalid argument provided.\n */\n InvalidArgument = 40003,\n\n /**\n * Operation not permitted with the provided capability (Ably 40160).\n * Used when the Ably channel rejects a publish for a capability reason.\n */\n InsufficientCapability = 40160,\n\n // 104000 - 104999 are reserved for AI Transport SDK errors\n\n /**\n * Encoder recovery failed during flush — one or more updateMessage calls\n * could not recover a failed append pipeline.\n */\n EncoderRecoveryFailed = 104000,\n\n /**\n * A session-level channel subscription callback threw unexpectedly.\n */\n SessionSubscriptionError = 104001,\n\n /**\n * Cancel listener or onCancel hook threw while processing a cancel message.\n */\n CancelListenerError = 104002,\n\n /**\n * A publish within a run failed (lifecycle event, message, or event).\n */\n RunLifecycleError = 104003,\n\n /**\n * An operation was attempted on a session that has already been closed.\n */\n SessionClosed = 104004,\n\n /**\n * The HTTP POST to the agent endpoint failed (network error or non-2xx response).\n */\n SessionSendFailed = 104005,\n\n /**\n * The Ably channel lost message continuity — the channel entered FAILED,\n * SUSPENDED, or DETACHED, or re-attached with `resumed: false`. Active\n * streams can no longer be guaranteed to receive all events.\n */\n ChannelContinuityLost = 104006,\n\n /**\n * An operation was attempted but the channel is not in a usable state\n * (not ATTACHED or ATTACHING).\n */\n ChannelNotReady = 104007,\n\n /**\n * An error occurred while piping a response stream to the channel — either\n * the source event stream threw (e.g. LLM provider rate limit, model error,\n * network failure) or an underlying publish failed mid-stream.\n */\n StreamError = 104008,\n\n /**\n * The agent attached to the channel and waited for the input event(s) the\n * invocation points at (rewind + live wait) but `inputEventLookupTimeoutMs`\n * lapsed without seeing them.\n */\n InputEventNotFound = 104010,\n}\n\n/**\n * Returns true if the {@link Ably.ErrorInfo} code matches the provided ErrorCode value.\n * @param errorInfo The error info to check.\n * @param error The error code to compare against.\n * @returns true if the error code matches, false otherwise.\n */\n// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison\nexport const errorInfoIs = (errorInfo: Ably.ErrorInfo, error: ErrorCode): boolean => errorInfo.code === error;\n","/**\n * Shared utilities for working with Ably messages.\n *\n * These are general-purpose helpers used by both the codec and transport\n * layers. They live at the top level to avoid either layer depending on\n * the other.\n */\n\nimport type * as Ably from 'ably';\n\n/**\n * Read one tier of the SDK's `extras.ai` namespace from an Ably message.\n * `extras.ai` is the SDK's reserved corner of the message envelope, split into\n * a `transport` tier (generic transport headers) and a `codec` tier (codec\n * headers). The application's own `extras.headers` is deliberately left\n * untouched.\n * @param message - The Ably message to read from.\n * @param tier - Which `extras.ai` sub-namespace to read.\n * @returns The tier's headers record, or an empty object if absent.\n */\nconst getAiTier = (message: Ably.InboundMessage, tier: 'transport' | 'codec'): Record<string, string> => {\n // CAST: Ably SDK types `extras` as `any`; runtime checks below guard access.\n const extras = message.extras as unknown;\n if (!extras || typeof extras !== 'object') return {};\n const ai = (extras as { ai?: unknown }).ai;\n if (!ai || typeof ai !== 'object') return {};\n const sub = (ai as Record<string, unknown>)[tier];\n if (!sub || typeof sub !== 'object') return {};\n // CAST: Ably wire protocol guarantees the tier is Record<string, string>\n // when present, verified by the runtime guards above.\n return sub as Record<string, string>;\n};\n\n/**\n * Extract the transport-tier headers (`extras.ai.transport`) from an Ably\n * InboundMessage. These are the generic transport headers (run/stream/identity/\n * branching), set and read by the transport layer.\n * @param message - The Ably message to extract headers from.\n * @returns The transport headers record, or an empty object if absent.\n */\nexport const getTransportHeaders = (message: Ably.InboundMessage): Record<string, string> =>\n getAiTier(message, 'transport');\n\n/**\n * Extract the codec-tier headers (`extras.ai.codec`) from an Ably\n * InboundMessage. These are the codec's own headers, with no prefix — the\n * tier isolates them from transport headers.\n * @param message - The Ably message to extract headers from.\n * @returns The codec headers record, or an empty object if absent.\n */\nexport const getCodecHeaders = (message: Ably.InboundMessage): Record<string, string> => getAiTier(message, 'codec');\n\n/**\n * Parse a JSON string, returning undefined on failure.\n * @param value - The JSON string to parse.\n * @returns The parsed value, or undefined if parsing fails.\n */\nexport const parseJson = (value: string | undefined): unknown => {\n if (value === undefined) return undefined;\n try {\n return JSON.parse(value) as unknown;\n } catch {\n return undefined;\n }\n};\n\n/**\n * Set a header value if defined, skipping undefined and null. Strings are set directly,\n * booleans and numbers are stringified, objects are JSON-serialized.\n * @param headers - The headers object to mutate.\n * @param key - The header key.\n * @param value - The value to set.\n */\nexport const setIfPresent = (headers: Record<string, string>, key: string, value: unknown): void => {\n if (value === undefined || value === null) return;\n if (typeof value === 'string') {\n headers[key] = value;\n } else if (typeof value === 'boolean' || typeof value === 'number') {\n headers[key] = String(value);\n } else if (typeof value === 'object') {\n headers[key] = JSON.stringify(value);\n }\n};\n\n/**\n * Merge two header records into a new object. Later values override earlier ones.\n * Undefined inputs are treated as empty.\n * @param base - Base headers (lower priority).\n * @param overrides - Override headers (higher priority).\n * @returns A new merged headers object.\n */\nexport const mergeHeaders = (\n base: Record<string, string> | undefined,\n overrides: Record<string, string> | undefined,\n): Record<string, string> => ({\n ...base,\n ...overrides,\n});\n\n/**\n * Parse a boolean header (\"true\"/\"false\"), returning undefined if absent.\n * @param value - The header string to parse.\n * @returns True if \"true\", false for any other string, or undefined if absent.\n */\nexport const parseBool = (value: string | undefined): boolean | undefined => {\n if (value === undefined) return undefined;\n return value === 'true';\n};\n\n/** A record carrying an optional Ably `serial`, orderable by {@link compareBySerial}. */\ninterface HasSerial {\n /** Ably serial, or undefined if the server has not yet assigned one. */\n readonly serial?: string;\n}\n\n/**\n * Comparator that orders records by their Ably `serial` ascending\n * (chronological). Serials are lexicographically comparable; records whose\n * serial is undefined sort last. Pass directly to `Array.prototype.sort`.\n * @param a - First record to compare.\n * @param b - Second record to compare.\n * @returns Negative if `a` precedes `b`, positive if `a` follows `b`, 0 if equal.\n */\nexport const compareBySerial = (a: HasSerial, b: HasSerial): number => {\n if (a.serial === undefined && b.serial === undefined) return 0;\n if (a.serial === undefined) return 1;\n if (b.serial === undefined) return -1;\n if (a.serial < b.serial) return -1;\n if (a.serial > b.serial) return 1;\n return 0;\n};\n\n/**\n * Read a domain header value from a codec-tier headers record.\n * @param headers - The codec headers record to read from.\n * @param key - The domain key (e.g. `'toolCallId'`).\n * @returns The header value, or undefined if absent.\n */\nexport const getDomainHeader = (headers: Record<string, string>, key: string): string | undefined => headers[key];\n\n/**\n * Mapped type that converts properties whose type includes `undefined`\n * into optional properties with `undefined` excluded from the value.\n * Properties typed as `unknown` are kept required (since `undefined extends unknown`\n * is always true, but `unknown` fields are intentionally broad, not optional).\n */\nexport type Stripped<T> = {\n [K in keyof T as undefined extends T[K] ? (unknown extends T[K] ? K : never) : K]: T[K];\n} & {\n [K in keyof T as undefined extends T[K] ? (unknown extends T[K] ? never : K) : never]?: Exclude<T[K], undefined>;\n};\n\n/**\n * Remove all keys whose value is `undefined` from a shallow object.\n * Returns a new object — the input is not mutated. Useful for building\n * chunk literals with optional fields without conditional spread noise.\n *\n * The return type converts `{ foo: T | undefined }` to `{ foo?: T }`,\n * matching the optional-field pattern used by the AI SDK chunk types.\n * @param obj - The object to strip undefined values from.\n * @returns A shallow copy with undefined-valued keys removed.\n */\nexport const stripUndefined = <T extends Record<string, unknown>>(obj: T): Stripped<T> => {\n const result = {} as Record<string, unknown>;\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key) && obj[key] !== undefined) {\n result[key] = obj[key];\n }\n }\n // CAST: The runtime strip guarantees the Stripped<T> contract —\n // required keys are always present, optional keys are absent when undefined.\n return result as Stripped<T>;\n};\n\n// ---------------------------------------------------------------------------\n// DomainHeaderReader — typed accessors for domain headers\n// ---------------------------------------------------------------------------\n\n/**\n * Typed accessor wrapper around a headers record for reading domain headers.\n * Reduces repetitive `getDomainHeader` + `parseBool` / `parseJson` chains.\n */\nexport interface DomainHeaderReader {\n /** Read a domain header as a string, or undefined if absent. */\n str(key: string): string | undefined;\n /** Read a domain header as a string, falling back to a default if absent. */\n strOr(key: string, fallback: string): string;\n /** Read a domain header as a boolean: `true` only for the exact string \"true\", `false` for any other present value, or undefined if absent. */\n bool(key: string): boolean | undefined;\n /** Read a domain header as parsed JSON, or undefined if absent or invalid. */\n json(key: string): unknown;\n}\n\n/**\n * Create a {@link DomainHeaderReader} over a headers record.\n * @param headers - The raw headers record to read domain headers from.\n * @returns A typed accessor for domain header values.\n */\nexport const headerReader = (headers: Record<string, string>): DomainHeaderReader => ({\n str: (key: string) => getDomainHeader(headers, key),\n strOr: (key: string, fallback: string) => getDomainHeader(headers, key) ?? fallback,\n bool: (key: string) => parseBool(getDomainHeader(headers, key)),\n json: (key: string) => parseJson(getDomainHeader(headers, key)),\n});\n\n// ---------------------------------------------------------------------------\n// DomainHeaderWriter — typed builder for domain headers\n// ---------------------------------------------------------------------------\n\n/**\n * Fluent builder for constructing domain header records with typed setters.\n * Mirrors {@link DomainHeaderReader} with the same method names for symmetry.\n * Undefined values are silently skipped on all setters.\n */\nexport interface DomainHeaderWriter {\n /** Set a string domain header. Skips if value is undefined. */\n str(key: string, value: string | undefined): DomainHeaderWriter;\n /** Set a boolean domain header (serialized as \"true\"/\"false\"). Skips if value is undefined. */\n bool(key: string, value: boolean | undefined): DomainHeaderWriter;\n /** Set a JSON-serialized domain header. Skips if value is undefined or null. */\n json(key: string, value: unknown): DomainHeaderWriter;\n /** Return the accumulated headers record. */\n build(): Record<string, string>;\n}\n\n/**\n * Create a {@link DomainHeaderWriter} for building a codec-tier headers record.\n * @returns A fluent builder that accumulates codec headers under their bare keys.\n */\nexport const headerWriter = (): DomainHeaderWriter => {\n const h: Record<string, string> = {};\n const writer: DomainHeaderWriter = {\n str: (key: string, value: string | undefined) => {\n if (value !== undefined) h[key] = value;\n return writer;\n },\n bool: (key: string, value: boolean | undefined) => {\n if (value !== undefined) h[key] = String(value);\n return writer;\n },\n json: (key: string, value: unknown) => {\n if (value !== undefined && value !== null) h[key] = JSON.stringify(value);\n return writer;\n },\n build: () => h,\n };\n return writer;\n};\n","/** SDK version. Kept in sync with `package.json` by the `/release` workflow. */\nexport const VERSION = '0.2.0';\n","/**\n * Wraps the two paths chat-js uses (see ChatClient._addAgent): the\n * `options.agents` mutation (read by ably-js when opening the initial\n * WebSocket) and the `params.agent` channel option (sent on ATTACH so\n * an already-open connection still carries the identifier).\n *\n * `options.agents` is a private API on the Realtime client — no public\n * typed accessor exists in the `ably` package — so this module casts to a\n * `RealtimeWithOptions` shape to write it.\n */\n\nimport type * as Ably from 'ably';\n\nimport { VERSION } from '../version.js';\n\ninterface RealtimeWithOptions extends Ably.Realtime {\n options: { agents?: Record<string, string | undefined> };\n}\n\nconst SDK_NAME = 'ai-transport-js';\n\n/** Internal shape a codec may carry to opt into Ably-Agent header registration. */\ninterface AdapterTagHolder {\n readonly adapterTag?: string;\n}\n\n/**\n * Merge `agents` into `client.options.agents` and return the space-separated\n * `params.agent` string for channel ATTACH.\n * @param client - The Ably Realtime client to mutate.\n * @param agents - Map of agent-name to version strings to register.\n * @returns Channel options containing `params.agent` for `channels.get`.\n */\nconst injectAgents = (\n client: Ably.Realtime,\n // CAST: Ably.Realtime's public type omits `options.agents`, but the SDK\n // does carry it at runtime. ably-chat-js relies on the same shape — see\n // ChatClient._addAgent in https://github.com/ably/ably-chat-js.\n agents: Record<string, string>,\n): { params: { agent: string } } => {\n const realtime = client as RealtimeWithOptions;\n realtime.options.agents = { ...realtime.options.agents, ...agents };\n const agentString = Object.entries(agents)\n .map(([name, version]) => `${name}/${version}`)\n .join(' ');\n return { params: { agent: agentString } };\n};\n\n/**\n * Register this SDK (and optionally a codec) on the supplied Realtime client\n * and return the channel options the caller should pass to\n * `client.channels.get(...)` so the agent is also carried on channel ATTACH.\n * Sets `options.agents['ai-transport-js'] = VERSION`. When the codec carries\n * an internal `adapterTag` field (via {@link AdapterTagHolder}), also sets\n * `options.agents[adapterTag] = VERSION`.\n * Idempotent — repeated calls with the same client and codec produce the same keys/values.\n * Spec: AIT-CT1a, AIT-CT1a2, AIT-CT1a3, AIT-ST1a, AIT-ST1a2, AIT-ST1a3.\n * @param client - The Ably Realtime client to register on.\n * @param codec - The codec instance; cast to {@link AdapterTagHolder} to detect an optional identifier.\n * @returns Channel options containing `params.agent` for `channels.get`.\n */\nexport const registerAgent = (client: Ably.Realtime, codec?: unknown): { params: { agent: string } } => {\n // CAST: AdapterTagHolder is an internal opt-in shape — not part of the public Codec interface.\n const adapterTag = (codec as AdapterTagHolder | undefined)?.adapterTag;\n const agents: Record<string, string> = { [SDK_NAME]: VERSION };\n if (adapterTag) agents[adapterTag] = VERSION;\n return injectAgents(client, agents);\n};\n","/**\n * Transport header builder.\n *\n * Single source of truth for which transport headers every transport\n * message carries. Used by the agent session (pipe, addEvents) and by\n * the client session (optimistic message stamping).\n */\n\nimport {\n EVENT_RUN_END,\n EVENT_RUN_RESUME,\n EVENT_RUN_START,\n EVENT_RUN_SUSPEND,\n HEADER_CODEC_MESSAGE_ID,\n HEADER_EVENT_ID,\n HEADER_FORK_OF,\n HEADER_INPUT_CLIENT_ID,\n HEADER_INPUT_CODEC_MESSAGE_ID,\n HEADER_INVOCATION_ID,\n HEADER_MSG_REGENERATE,\n HEADER_PARENT,\n HEADER_ROLE,\n HEADER_RUN_CLIENT_ID,\n HEADER_RUN_ID,\n HEADER_RUN_REASON,\n} from '../../constants.js';\nimport type { RunEndReason, RunLifecycleEvent } from './types.js';\n\n/**\n * Build the standard transport header set for a message.\n * @param opts - The header values to include.\n * @param opts.role - Message role (e.g. \"user\", \"assistant\").\n * @param opts.runId - Run correlation ID, or `undefined` for a fresh client\n * input (the agent mints run-ids, so it is not known synchronously). Omitted\n * from the headers when undefined; a continuation still carries the known run-id.\n * @param opts.codecMessageId - Message identity — the wire `codec-message-id` for this message.\n * @param opts.runClientId - ClientId of the run initiator.\n * @param opts.parent - Preceding message's codec-message-id (for branching).\n * @param opts.forkOf - Forked user-prompt's codec-message-id (for edits — creates a Run-level fork sibling).\n * @param opts.regenerates - Assistant codec-message-id this run regenerates. Stamps\n * `msg-regenerate`. Distinct from `forkOf`: regenerate is a\n * continuation of the prior run (no Run-level fork), with the message\n * replacement resolved at projection extraction time.\n * @param opts.invocationId - Agent-minted invocation id. Stamped by the agent on every event it publishes for the invocation (run lifecycle + outputs) so the client can observe it; not set by the client on the input.\n * @param opts.inputClientId - ClientId of the input event (the `ai-input`) that\n * drove the current invocation. The agent reads it from the publisher's\n * Ably-level `clientId` on the matched input event and re-stamps it on its\n * own publishes (run lifecycle + outputs). Differs from `runClientId` on\n * continuation invocations driven by an input from a non-owner.\n * @param opts.inputEventId - Per-event identifier. Set on each client-published user-prompt message; the invocation body's `inputEventIds` lists the ids the agent should look up.\n * @param opts.inputCodecMessageId - The codec-message-id of the input event that\n * triggered the current invocation (the one whose `event-id` matched the\n * invocation's `inputEventId`). The agent re-stamps it on every event it\n * publishes for the invocation (run lifecycle + outputs), mirroring\n * `inputClientId`, so the client can correlate any of those events back to\n * the originating input by the id it owned at send time.\n * @returns A headers record with the transport headers set.\n */\nexport const buildTransportHeaders = (opts: {\n role: string;\n runId?: string;\n codecMessageId: string;\n runClientId?: string;\n parent?: string;\n forkOf?: string;\n regenerates?: string;\n invocationId?: string;\n inputClientId?: string;\n inputCodecMessageId?: string;\n inputEventId?: string;\n}): Record<string, string> => {\n const h: Record<string, string> = {\n [HEADER_ROLE]: opts.role,\n [HEADER_CODEC_MESSAGE_ID]: opts.codecMessageId,\n };\n if (opts.runId !== undefined) h[HEADER_RUN_ID] = opts.runId;\n if (opts.runClientId !== undefined) h[HEADER_RUN_CLIENT_ID] = opts.runClientId;\n if (opts.parent) h[HEADER_PARENT] = opts.parent;\n if (opts.forkOf) h[HEADER_FORK_OF] = opts.forkOf;\n if (opts.regenerates) h[HEADER_MSG_REGENERATE] = opts.regenerates;\n if (opts.invocationId) h[HEADER_INVOCATION_ID] = opts.invocationId;\n if (opts.inputClientId !== undefined) h[HEADER_INPUT_CLIENT_ID] = opts.inputClientId;\n if (opts.inputCodecMessageId !== undefined) h[HEADER_INPUT_CODEC_MESSAGE_ID] = opts.inputCodecMessageId;\n if (opts.inputEventId) h[HEADER_EVENT_ID] = opts.inputEventId;\n return h;\n};\n\n/**\n * Build the transport header set for a run-lifecycle event (run-start,\n * run-resume, run-suspend, run-end). Single source of truth for lifecycle\n * header stamping, mirroring {@link buildTransportHeaders} for the\n * message-carrier path. Every field except `runId`/`runClientId` is optional\n * and omitted when not provided.\n *\n * A resume suppresses the structural `parent` / `forkOf` / `regenerates`\n * headers — the caller passes them only for a fresh run-start. `reason` is\n * stamped only on run-end.\n * @param opts - The lifecycle header values to include.\n * @param opts.runId - The run's id.\n * @param opts.runClientId - ClientId of the run initiator (empty string when unknown).\n * @param opts.parent - Structural parent codec-message-id (fresh run-start only).\n * @param opts.forkOf - Forked user-prompt codec-message-id (fresh run-start only).\n * @param opts.regenerates - Regenerated assistant codec-message-id (fresh run-start only).\n * @param opts.invocationId - Agent-minted invocation id; carried on every lifecycle event.\n * @param opts.inputClientId - ClientId of the triggering input event.\n * @param opts.inputCodecMessageId - Codec-message-id of the triggering input event.\n * @param opts.reason - Terminal reason; stamped on run-end only.\n * @returns A headers record with the lifecycle headers set.\n */\nexport const buildLifecycleHeaders = (opts: {\n runId: string;\n runClientId: string;\n parent?: string;\n forkOf?: string;\n regenerates?: string;\n invocationId?: string;\n inputClientId?: string;\n inputCodecMessageId?: string;\n reason?: RunEndReason;\n}): Record<string, string> => {\n const h: Record<string, string> = {\n [HEADER_RUN_ID]: opts.runId,\n [HEADER_RUN_CLIENT_ID]: opts.runClientId,\n };\n if (opts.reason !== undefined) h[HEADER_RUN_REASON] = opts.reason;\n if (opts.parent !== undefined) h[HEADER_PARENT] = opts.parent;\n if (opts.forkOf !== undefined) h[HEADER_FORK_OF] = opts.forkOf;\n if (opts.regenerates !== undefined) h[HEADER_MSG_REGENERATE] = opts.regenerates;\n if (opts.invocationId !== undefined) h[HEADER_INVOCATION_ID] = opts.invocationId;\n if (opts.inputClientId !== undefined) h[HEADER_INPUT_CLIENT_ID] = opts.inputClientId;\n if (opts.inputCodecMessageId !== undefined) h[HEADER_INPUT_CODEC_MESSAGE_ID] = opts.inputCodecMessageId;\n return h;\n};\n\n/** The four run-lifecycle Ably message names. */\ntype RunLifecycleName =\n | typeof EVENT_RUN_START\n | typeof EVENT_RUN_SUSPEND\n | typeof EVENT_RUN_RESUME\n | typeof EVENT_RUN_END;\n\n/**\n * Whether an Ably message `name` is one of the run-lifecycle event names\n * (run-start / run-suspend / run-resume / run-end). Single source of truth for\n * the classification both decode loops and the agent's history scan use to\n * route lifecycle wires away from the codec decoder. Narrows `name` to a\n * lifecycle name so callers can pass it straight to {@link parseRunLifecycle}.\n * @param name - The inbound Ably message `name`, or undefined.\n * @returns True when `name` is a run-lifecycle event name.\n */\nexport const isRunLifecycleName = (name: string | undefined): name is RunLifecycleName =>\n name === EVENT_RUN_START || name === EVENT_RUN_SUSPEND || name === EVENT_RUN_RESUME || name === EVENT_RUN_END;\n\n/**\n * Parse an inbound run-lifecycle Ably message into a {@link RunLifecycleEvent}.\n *\n * Single source of truth for turning the wire run-lifecycle message `name`,\n * transport headers, and channel serial into the structured lifecycle event\n * the Tree consumes. Used by the client decode loop (live) and the View's\n * history replay so both build the event identically.\n * @param name - The inbound Ably message `name`.\n * @param headers - Transport headers from the inbound Ably message.\n * @param serial - Ably channel serial of the message, or `undefined` for an\n * optimistic local event. Stamped onto the returned event.\n * @returns The lifecycle event, or `undefined` when `name` is not a\n * run-lifecycle event name or the message carries no `run-id`.\n */\nexport const parseRunLifecycle = (\n name: string,\n headers: Record<string, string>,\n serial: string | undefined,\n): RunLifecycleEvent | undefined => {\n const runId = headers[HEADER_RUN_ID];\n if (!runId) return undefined;\n\n const clientId = headers[HEADER_RUN_CLIENT_ID] ?? '';\n\n if (name === EVENT_RUN_START) {\n const parent = headers[HEADER_PARENT];\n const forkOf = headers[HEADER_FORK_OF];\n const regenerates = headers[HEADER_MSG_REGENERATE];\n return {\n type: 'start',\n runId,\n clientId,\n serial,\n invocationId: headers[HEADER_INVOCATION_ID] ?? '',\n ...(parent !== undefined && { parent }),\n ...(forkOf !== undefined && { forkOf }),\n ...(regenerates !== undefined && { regenerates }),\n };\n }\n\n if (name === EVENT_RUN_SUSPEND) {\n return { type: 'suspend', runId, clientId, serial, invocationId: headers[HEADER_INVOCATION_ID] ?? '' };\n }\n\n if (name === EVENT_RUN_RESUME) {\n return { type: 'resume', runId, clientId, serial, invocationId: headers[HEADER_INVOCATION_ID] ?? '' };\n }\n\n if (name === EVENT_RUN_END) {\n // CAST: agent always writes a valid RunEndReason; default to 'complete' for robustness.\n const reason = (headers[HEADER_RUN_REASON] ?? 'complete') as RunEndReason;\n return { type: 'end', runId, clientId, serial, invocationId: headers[HEADER_INVOCATION_ID] ?? '', reason };\n }\n\n return undefined;\n};\n","/**\n * Helpers for FIFO-bounded `Map`s — Maps used as capacity-limited buffers that\n * must evict their oldest entry when full.\n */\n\n/**\n * Make room for a new key in a FIFO-bounded map: if `map` is at (or over)\n * `limit` and does not already contain `key`, evict the oldest entry (insertion\n * order) and return its key so the caller can log the eviction. Returns\n * `undefined` when nothing was evicted (the key already exists, the map is\n * below the limit, or it is empty).\n *\n * The caller performs the actual set/append afterwards — this only frees a\n * slot — so it works for maps whose values are replaced and for maps whose\n * values are appended-to lists.\n * @param map - The bounded map to evict from.\n * @param key - The key about to be added; an existing key never evicts.\n * @param limit - The maximum number of entries the map may hold.\n * @returns The evicted key, or `undefined` if nothing was evicted.\n */\nexport const evictOldestIfFull = <K, V>(map: Map<K, V>, key: K, limit: number): K | undefined => {\n if (map.has(key) || map.size < limit) return undefined;\n const oldest = map.keys().next().value;\n if (oldest === undefined) return undefined;\n map.delete(oldest);\n return oldest;\n};\n","/**\n * buildBranchChain — order a single conversation branch by walking\n * codec-message-id parent links upward from an anchor node to the root.\n *\n * This is the shared ordering spine of the agent's conversation\n * reconstruction and of history decode: both need the same root→anchor\n * sequence of nodes before folding each node's projection. Keeping the walk\n * here — pure, with no codec, no I/O, no logger — lets it be proven in\n * isolation and reused by both engines without drift.\n *\n * Branch selection is implicit: a node reaches only its own ancestors via\n * `parentCodecMessageId`, so sibling branches (edits / regenerates that the\n * anchor did not descend from) are never visited. There is no separate\n * fork/regenerate filtering step — the un-taken sibling is simply unreachable.\n */\n\n/**\n * The single field {@link buildBranchChain} reads from a node. Richer node-meta\n * shapes (carrying run-id, fork-of, regenerates, …) satisfy this structurally,\n * so callers can pass their full index map directly.\n */\nexport interface BranchChainNode {\n /**\n * Codec-message-id of this node's structural parent — the node it hangs off\n * — or `undefined` for a root node. This is the only edge the walk follows.\n */\n parentCodecMessageId: string | undefined;\n}\n\n/**\n * Walk `parentCodecMessageId` links upward from `anchorCodecMessageId` and\n * return the branch it sits on, ordered root-first (oldest) to anchor (newest,\n * last). The anchor is always the final element.\n *\n * The walk stops at the root (a node with no parent), at a dangling parent\n * (a parent id absent from `nodeMeta` is still included as the chain head,\n * then the walk ends), or on revisiting a node (a cycle in malformed data is\n * broken best-effort rather than looping forever).\n * @param nodeMeta - Lookup from codec-message-id to its node meta. Need not\n * contain the anchor or every ancestor; missing entries simply end the walk.\n * @param anchorCodecMessageId - The codec-message-id to start the walk from\n * (the newest node on the branch; included in the result).\n * @returns The branch's codec-message-ids ordered root-first to anchor-last.\n */\nexport const buildBranchChain = (\n nodeMeta: ReadonlyMap<string, BranchChainNode>,\n anchorCodecMessageId: string,\n): string[] => {\n const chain: string[] = [];\n const seen = new Set<string>();\n let current: string | undefined = anchorCodecMessageId;\n while (current !== undefined && !seen.has(current)) {\n seen.add(current);\n chain.push(current);\n current = nodeMeta.get(current)?.parentCodecMessageId;\n }\n return chain.toReversed();\n};\n","/**\n * Shared wire decode-and-apply engine.\n *\n * The client's live decode loop and the View's history replay both reconstruct\n * the conversation Tree from the same raw Ably wire log. This module is the one\n * place that classifies a wire message (run-lifecycle vs codec-decoded), parses\n * or decodes it, and applies it to the Tree — so the two paths can never drift.\n */\n\nimport type * as Ably from 'ably';\n\nimport { HEADER_RUN_ID } from '../../constants.js';\nimport { getTransportHeaders } from '../../utils.js';\nimport type { Codec, CodecInputEvent, CodecOutputEvent, Decoder } from '../codec/types.js';\nimport { isRunLifecycleName, parseRunLifecycle } from './headers.js';\nimport type { TreeInternal } from './tree.js';\nimport type { RunLifecycleEvent } from './types.js';\n\n/**\n * Apply one inbound wire message to the tree.\n *\n * Run-lifecycle messages are turned into a {@link RunLifecycleEvent} via\n * {@link parseRunLifecycle} and applied with `applyRunLifecycle`; everything\n * else is decoded with `decoder` and applied with `applyMessage`, skipping\n * wire-only carriers that decode to no events and carry no run-id (the eventual\n * reply run is created later by its run-start).\n *\n * Does NOT emit the tree's `ably-message` event — the caller owns that, because\n * the live loop emits per message while history replay emits in a batch once\n * the whole page is applied. Returns the parsed lifecycle event so a live\n * caller can run its own side-effects (resolving a pending run-start,\n * surfacing an agent error); returns `undefined` for a codec-decoded message\n * or a lifecycle message that carried no run-id.\n * @param tree - The tree to apply the message to.\n * @param decoder - The codec decoder used for non-lifecycle messages.\n * @param rawMsg - The inbound Ably wire message.\n * @returns The parsed run-lifecycle event, or `undefined`.\n */\nexport const applyWireMessage = <TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection>(\n tree: TreeInternal<TInput, TOutput, TProjection>,\n decoder: Decoder<TInput, TOutput>,\n rawMsg: Ably.InboundMessage,\n): RunLifecycleEvent | undefined => {\n const headers = getTransportHeaders(rawMsg);\n const serial = rawMsg.serial;\n\n if (isRunLifecycleName(rawMsg.name)) {\n const event = parseRunLifecycle(rawMsg.name, headers, serial);\n if (event) tree.applyRunLifecycle(event);\n return event;\n }\n\n const { inputs, outputs } = decoder.decode(rawMsg);\n if (inputs.length > 0 || outputs.length > 0 || headers[HEADER_RUN_ID]) {\n tree.applyMessage({ inputs, outputs }, headers, serial);\n }\n return undefined;\n};\n\n/**\n * Decode one wire message with `decoder` and fold its events into `projection`,\n * returning the updated projection. Unlike {@link applyWireMessage} this builds\n * a standalone projection rather than applying to a tree — used by the agent's\n * conversation reconstruction. The caller owns the decoder so its streaming\n * state can span the messages of a run, and chooses the reducer routing key.\n * @param codec - The codec whose inherited Reducer `fold` method folds each decoded event into the projection.\n * @param decoder - The caller-owned codec decoder (reused across a run's wires).\n * @param projection - The projection to fold the message's events into.\n * @param rawMsg - The wire message to decode and fold.\n * @param messageId - The reducer routing key (codec-message-id) for this message.\n * @returns The projection after folding all of the message's decoded events.\n */\nexport const foldMessageInto = <\n TInput extends CodecInputEvent,\n TOutput extends CodecOutputEvent,\n TProjection,\n TMessage,\n>(\n codec: Codec<TInput, TOutput, TProjection, TMessage>,\n decoder: Decoder<TInput, TOutput>,\n projection: TProjection,\n rawMsg: Ably.InboundMessage,\n messageId: string,\n): TProjection => {\n const { inputs, outputs } = decoder.decode(rawMsg);\n let next = projection;\n for (const event of [...inputs, ...outputs]) {\n next = codec.fold(next, event, { serial: rawMsg.serial ?? '', messageId });\n }\n return next;\n};\n","/**\n * Agent-side conversation reconstruction from the channel wire log.\n *\n * When an agent wakes (or resumes) it has no in-memory tree — it rebuilds the\n * state it needs by paging channel history and folding the wires through the\n * codec. Two entry points:\n *\n * - {@link loadRunProjection} — fold a single run's wires into one projection\n * (used to resume a suspended run with its client tool-output amends).\n * - {@link loadConversation} — walk the structural parent chain from the\n * current run's input node to the root and fold each node, producing the full\n * multi-turn message history along the taken branch.\n *\n * Both reuse {@link foldMessageInto} (the shared per-message fold primitive) and\n * {@link buildBranchChain} (the shared parent-chain walk), so the agent's\n * reconstruction can't drift from the client/View decode paths.\n */\n\nimport * as Ably from 'ably';\n\nimport { EVENT_RUN_START, HEADER_CODEC_MESSAGE_ID, HEADER_PARENT, HEADER_RUN_ID } from '../../constants.js';\nimport { ErrorCode } from '../../errors.js';\nimport type { Logger } from '../../logger.js';\nimport { compareBySerial, getTransportHeaders } from '../../utils.js';\nimport type { Codec, CodecInputEvent, CodecOutputEvent } from '../codec/types.js';\nimport type { BranchChainNode } from './branch-chain.js';\nimport { buildBranchChain } from './branch-chain.js';\nimport { foldMessageInto } from './decode-fold.js';\nimport { isRunLifecycleName } from './headers.js';\n\n// ---------------------------------------------------------------------------\n// History collection + dedup\n// ---------------------------------------------------------------------------\n\n/**\n * Merge messages observed live (e.g. by the input-event lookup) into a set of\n * collected history messages, dedup by serial, and sort chronologically.\n *\n * History messages take priority in deduplication (history serial wins if the\n * same message appears in both). Messages without a serial are dropped because\n * they cannot be reliably ordered.\n * @param collected - Raw messages from channel.history (any order).\n * @param live - Messages observed live (e.g. by the input-event lookup); may be undefined.\n * @returns Deduplicated, chronologically sorted messages.\n */\nexport const withLiveMessages = (\n collected: readonly Ably.InboundMessage[],\n live?: readonly Ably.InboundMessage[],\n): Ably.InboundMessage[] => {\n const seen = new Set<string>();\n const result: Ably.InboundMessage[] = [];\n for (const msg of collected) {\n if (msg.serial !== undefined && !seen.has(msg.serial)) {\n seen.add(msg.serial);\n result.push(msg);\n }\n }\n if (live !== undefined) {\n for (const msg of live) {\n if (msg.serial !== undefined && !seen.has(msg.serial)) {\n seen.add(msg.serial);\n result.push(msg);\n }\n }\n }\n return result.toSorted(compareBySerial);\n};\n\n/**\n * Page through a channel's history and collect raw messages, bounded so a\n * long-lived channel can't exhaust memory. No `untilAttach` — callers need\n * messages published after the channel first attached (e.g. client tool-output\n * amends on a suspended run).\n * @param channel - The Ably channel to read history from.\n * @param pageLimit - Messages requested per history page.\n * @param maxMessages - Stop paging once this many messages are collected.\n * @returns The collected messages in history order (newest first per Ably).\n */\nconst collectHistory = async (\n channel: Ably.RealtimeChannel,\n pageLimit: number,\n maxMessages: number,\n): Promise<Ably.InboundMessage[]> => {\n const collected: Ably.InboundMessage[] = [];\n let page = await channel.history({ limit: pageLimit });\n collected.push(...page.items);\n while (page.hasNext() && collected.length < maxMessages) {\n const nextPage: Ably.PaginatedResult<Ably.InboundMessage> | null = await page.next();\n if (!nextPage) break;\n collected.push(...nextPage.items);\n page = nextPage;\n }\n return collected;\n};\n\n// ---------------------------------------------------------------------------\n// Per-node folds\n// ---------------------------------------------------------------------------\n\n/**\n * Fold a pre-sorted array of wire messages for a single run into a projection.\n *\n * Skips lifecycle events (they carry no codec content) and stops before the\n * message whose `codec-message-id` equals `truncateAt` (exclusive — that\n * message is not folded). Used by both {@link loadRunProjection} (no\n * truncation) and {@link loadConversation} (per-ancestor folding).\n * @param codec - Codec used to decode and fold events.\n * @param sortedMessages - Chronologically ordered wire messages (all runs).\n * @param runId - Only messages stamped with this run-id are folded.\n * @param truncateAt - Stop before this codec-message-id; omit to fold all messages.\n * @returns The projection and the count of messages that were folded.\n */\nexport const foldRunMessages = <\n TInput extends CodecInputEvent,\n TOutput extends CodecOutputEvent,\n TProjection,\n TMessage,\n>(\n codec: Codec<TInput, TOutput, TProjection, TMessage>,\n sortedMessages: readonly Ably.InboundMessage[],\n runId: string,\n truncateAt?: string,\n): { projection: TProjection; folded: number } => {\n const decoder = codec.createDecoder();\n let projection = codec.init();\n let folded = 0;\n for (const msg of sortedMessages) {\n const h = getTransportHeaders(msg);\n if (h[HEADER_RUN_ID] !== runId) continue;\n if (isRunLifecycleName(msg.name)) continue;\n const codecMsgId = h[HEADER_CODEC_MESSAGE_ID];\n if (truncateAt !== undefined && codecMsgId === truncateAt) break;\n projection = foldMessageInto(codec, decoder, projection, msg, codecMsgId ?? '');\n folded++;\n }\n return { projection, folded };\n};\n\n/**\n * Fold a single run-less INPUT node's events into a fresh projection: every\n * wire stamped with `codecMessageId` and NO run-id (the user prompt the client\n * published before the agent minted a run-id). The two-node analogue of\n * {@link foldRunMessages} for the user-input side of the conversation chain.\n * @param codec - Codec used to decode and fold events.\n * @param sortedMessages - Chronologically ordered wire messages (all runs).\n * @param codecMessageId - The input node's codec-message-id.\n * @returns The folded projection for that input node.\n */\nexport const foldInputMessages = <\n TInput extends CodecInputEvent,\n TOutput extends CodecOutputEvent,\n TProjection,\n TMessage,\n>(\n codec: Codec<TInput, TOutput, TProjection, TMessage>,\n sortedMessages: readonly Ably.InboundMessage[],\n codecMessageId: string,\n): TProjection => {\n const decoder = codec.createDecoder();\n let projection = codec.init();\n for (const msg of sortedMessages) {\n const h = getTransportHeaders(msg);\n if (h[HEADER_RUN_ID] !== undefined) continue;\n if (h[HEADER_CODEC_MESSAGE_ID] !== codecMessageId) continue;\n projection = foldMessageInto(codec, decoder, projection, msg, codecMessageId);\n }\n return projection;\n};\n\n// ---------------------------------------------------------------------------\n// Run-state reconstruction\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch all messages on the channel that belong to `runId`, decode them\n * through the codec, and fold them into a single projection. Used by the agent\n * to reconstruct a run's full state — including client-published tool-output\n * amends — when resuming a suspended run in a fresh agent session.\n *\n * Doesn't require channel rewind: an explicit `channel.history()` call returns\n * the same data even if the channel is already attached from a prior session.\n * @param opts - Load parameters.\n * @param opts.channel - The Ably channel to read history from.\n * @param opts.codec - Codec used to decode and fold events.\n * @param opts.runId - Run identifier whose events should be folded.\n * @param opts.signal - AbortSignal checked once at entry: if already aborted the call throws immediately and no history is fetched. It does not interrupt an in-flight load.\n * @param opts.logger - Optional logger for diagnostic output.\n * @param opts.liveMessages - Raw Ably messages already observed live (e.g. by\n * the input-event lookup). Folded alongside the history fetch so just-published\n * client wires don't depend on Ably's history-indexing window.\n * @returns The projection produced by folding all run events in serial order.\n * @throws {Ably.ErrorInfo} with code ErrorCode.InvalidArgument if `signal` is already aborted at entry (the run was cancelled before loading began).\n */\nexport const loadRunProjection = async <\n TInput extends CodecInputEvent,\n TOutput extends CodecOutputEvent,\n TProjection,\n TMessage,\n>(opts: {\n channel: Ably.RealtimeChannel;\n codec: Codec<TInput, TOutput, TProjection, TMessage>;\n runId: string;\n signal: AbortSignal;\n logger: Logger | undefined;\n liveMessages?: readonly Ably.InboundMessage[];\n}): Promise<TProjection> => {\n const { channel, codec, runId, signal, logger, liveMessages } = opts;\n\n if (signal.aborted) {\n throw new Ably.ErrorInfo(\n `unable to load run projection; run ${runId} was cancelled`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n\n await channel.attach();\n\n // 2000 wire messages is generously more than any single run could produce.\n const collected = await collectHistory(channel, 200, 2000);\n\n const sorted = withLiveMessages(collected, liveMessages);\n const { projection, folded } = foldRunMessages(codec, sorted, runId);\n\n logger?.debug('loadRunProjection(); folded run events', { runId, folded });\n return projection;\n};\n\n/** A node in the reconstruction index — {@link BranchChainNode} plus its run-id. */\ninterface NodeMeta extends BranchChainNode {\n /** The run-id this node belongs to, or undefined for a run-less input node. */\n runId: string | undefined;\n}\n\n/**\n * Reconstruct the full multi-turn conversation history along the branch the\n * current run sits on.\n *\n * Pages channel history (merging live lookup messages), indexes each\n * codec-message-id's structural parent and run-id with sticky identity (the\n * first wire wins; later amends can't poison it), backfills a reply run's\n * parent from its `ai-run-start` when the output wire wasn't indexed, then\n * walks the structural parent chain from the current run's input node\n * (`assistantParentFallback`) to the root and folds each node in chain order.\n * The current run is folded once, wholesale, at the tail.\n * @param opts - Reconstruction parameters.\n * @param opts.channel - The Ably channel to read history from.\n * @param opts.codec - Codec used to decode and fold events.\n * @param opts.runId - The current run's id.\n * @param opts.signal - AbortSignal checked once at entry; if already aborted the call throws before any history is fetched. It does not interrupt an in-flight load.\n * @param opts.logger - Optional logger for diagnostic output.\n * @param opts.liveMessages - Wires already observed live, merged into history.\n * @param opts.assistantParentFallback - The current run's input node\n * (codec-message-id) — the anchor the parent-chain walk starts from. When\n * undefined, only the current run is folded.\n * @param opts.pageLimit - Messages requested per history page.\n * @param opts.maxMessages - Stop paging once this many messages are collected.\n * @returns The branch's messages (root-first) and the current run's projection.\n * @throws {Ably.ErrorInfo} with code ErrorCode.InvalidArgument when `signal` is already aborted at entry.\n */\nexport const loadConversation = async <\n TInput extends CodecInputEvent,\n TOutput extends CodecOutputEvent,\n TProjection,\n TMessage,\n>(opts: {\n channel: Ably.RealtimeChannel;\n codec: Codec<TInput, TOutput, TProjection, TMessage>;\n runId: string;\n signal: AbortSignal;\n logger: Logger | undefined;\n liveMessages: readonly Ably.InboundMessage[] | undefined;\n assistantParentFallback: string | undefined;\n pageLimit: number;\n maxMessages: number;\n}): Promise<{ messages: TMessage[]; projection: TProjection }> => {\n const { channel, codec, runId, signal, logger, liveMessages, assistantParentFallback, pageLimit, maxMessages } = opts;\n\n if (signal.aborted) {\n throw new Ably.ErrorInfo(`unable to load conversation; run ${runId} was cancelled`, ErrorCode.InvalidArgument, 400);\n }\n\n // Single channel.history() fetch for all runs. Live lookup messages are\n // merged in so the current run's just-published client wires don't depend on\n // Ably's history-indexing window. Deduped by serial (history wins), sorted.\n const collected = await collectHistory(channel, pageLimit, maxMessages);\n const sortedMessages = withLiveMessages(collected, liveMessages);\n\n // Index pass — node metadata per codec-message-id from the serial-sorted\n // history, with sticky identity (the first wire for a codec-message-id wins\n // for the structural parent; a later amend can't poison it). Run-bearing\n // wires record their runId; run-less user inputs are input nodes (runId\n // undefined).\n const nodeMeta = new Map<string, NodeMeta>();\n const runIdToCodecMessageId = new Map<string, string>();\n for (const msg of sortedMessages) {\n if (isRunLifecycleName(msg.name)) continue;\n const h = getTransportHeaders(msg);\n const cid = h[HEADER_CODEC_MESSAGE_ID];\n if (cid === undefined) continue;\n const msgRunId = h[HEADER_RUN_ID];\n if (msgRunId !== undefined) runIdToCodecMessageId.set(msgRunId, cid);\n if (!nodeMeta.has(cid)) {\n nodeMeta.set(cid, { runId: msgRunId, parentCodecMessageId: h[HEADER_PARENT] });\n }\n }\n // Backfill a reply run's structural parent from ai-run-start when its output\n // wire wasn't indexed (rare history lag). Keyed by runId → codec-message-id.\n for (const msg of sortedMessages) {\n if (msg.name !== EVENT_RUN_START) continue;\n const h = getTransportHeaders(msg);\n const msgRunId = h[HEADER_RUN_ID];\n if (msgRunId === undefined) continue;\n const cid = runIdToCodecMessageId.get(msgRunId);\n if (cid === undefined) continue;\n const meta = nodeMeta.get(cid);\n if (meta && meta.parentCodecMessageId === undefined) meta.parentCodecMessageId = h[HEADER_PARENT];\n }\n\n // Walk the structural parent chain from the current run's input node up to\n // the conversation root, then fold each node in chain order. The upward walk\n // naturally excludes un-taken branch siblings (an edit's alternate prompt, a\n // regenerate's superseded reply), so no per-ancestor truncation is needed.\n // (Open caveat, deferred with a golden test: regenerating a non-trailing\n // message of a multi-message reply — the node walk can't slice inside one\n // run's projection.)\n const messages: TMessage[] = [];\n let chainLength = 0;\n if (assistantParentFallback !== undefined) {\n const chain = buildBranchChain(nodeMeta, assistantParentFallback);\n chainLength = chain.length;\n for (const cid of chain) {\n const meta = nodeMeta.get(cid);\n // Skip any chain node belonging to the CURRENT run — it is folded once,\n // wholesale, at the tail below. For a continuation the run-id is reused\n // and `assistantParentFallback` points at a message INSIDE the current\n // run, so it would otherwise be folded twice and emit duplicate tool_use\n // ids.\n if (meta?.runId === runId) continue;\n const projection =\n meta?.runId === undefined\n ? foldInputMessages(codec, sortedMessages, cid)\n : foldRunMessages(codec, sortedMessages, meta.runId).projection;\n messages.push(...codec.getMessages(projection).map((m) => m.message));\n }\n }\n\n // Current run — folded from the same sorted messages, appended at the chain\n // tail (the chain ended at this run's input node).\n const { projection, folded } = foldRunMessages(codec, sortedMessages, runId);\n messages.push(...codec.getMessages(projection).map((m) => m.message));\n\n logger?.debug('loadConversation(); built', { runId, chainLength, totalMessages: messages.length, folded });\n return { messages, projection };\n};\n","/**\n * Pure stream piping function.\n *\n * Reads outputs from a ReadableStream, writes them to an encoder via\n * `publishOutput`, and handles cancel/error. No dependencies on run\n * state or session internals.\n */\n\nimport type { Logger } from '../../logger.js';\nimport type { CodecInputEvent, CodecOutputEvent, Encoder, WriteOptions } from '../codec/types.js';\nimport type { StreamResult } from './types.js';\n\n/**\n * Adapt an AbortSignal into a promise that resolves once the signal aborts,\n * paired with a cleanup that detaches the listener. With no signal the promise\n * never resolves (there is no cancellation path); an already-aborted signal\n * resolves immediately. `cleanup` is a no-op unless a listener was attached.\n * @param signal - The AbortSignal to watch, or undefined for no cancellation.\n * @returns The abort promise and a cleanup to call when racing is done.\n */\nconst abortSignalToPromise = (signal: AbortSignal | undefined): { promise: Promise<void>; cleanup: () => void } => {\n let listener: (() => void) | undefined;\n const promise =\n signal === undefined\n ? // eslint-disable-next-line @typescript-eslint/no-empty-function -- never-resolving promise: no signal means no cancellation path\n new Promise<void>(() => {})\n : signal.aborted\n ? Promise.resolve()\n : new Promise<void>((resolve) => {\n listener = () => {\n resolve();\n };\n signal.addEventListener('abort', listener, { once: true });\n });\n const cleanup = (): void => {\n if (listener && signal) signal.removeEventListener('abort', listener);\n };\n return { promise, cleanup };\n};\n\n/**\n * Pipe an output stream through an encoder to the channel.\n *\n * Returns when the stream completes, is cancelled (via signal), or errors.\n * The `reason` field of the result indicates which case occurred.\n * @param stream - The output stream to read from.\n * @param encoder - The encoder to publish outputs through.\n * @param signal - AbortSignal to monitor for cancellation.\n * @param onCancelled - Optional callback invoked when the stream is cancelled, before the stream ends.\n * @param resolveWriteOptions - Optional per-output hook returning {@link WriteOptions} overrides to pass to `encoder.publishOutput`.\n * @param logger - Optional logger for diagnostic output.\n * @returns A {@link StreamResult}: `reason` is why the pipe ended, and `error` holds the caught error when `reason` is `'error'`.\n */\nexport const pipeStream = async <TInput extends CodecInputEvent, TOutput extends CodecOutputEvent>(\n stream: ReadableStream<TOutput>,\n encoder: Encoder<TInput, TOutput>,\n signal: AbortSignal | undefined,\n onCancelled?: (write: (output: TOutput) => Promise<void>) => void | Promise<void>,\n resolveWriteOptions?: (output: TOutput) => WriteOptions | undefined,\n logger?: Logger,\n): Promise<StreamResult> => {\n logger?.trace('pipeStream();');\n\n const reader = stream.getReader();\n const abort = abortSignalToPromise(signal);\n\n let reason: StreamResult['reason'] = 'complete';\n let caughtError: Error | undefined;\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- intentional infinite loop broken by return/break\n while (true) {\n // .then() is intentional: transforms the AbortSignal into a discriminant\n // for Promise.race — no async/await equivalent for this pattern.\n const result = await Promise.race([reader.read(), abort.promise.then(() => 'cancelled' as const)]);\n\n if (result === 'cancelled') {\n reason = 'cancelled';\n logger?.debug('pipeStream(); stream cancelled by AbortSignal');\n if (onCancelled) {\n await onCancelled(async (output: TOutput) => encoder.publishOutput(output));\n }\n await encoder.cancel('cancelled');\n break;\n }\n\n const { done, value } = result;\n if (done) {\n await encoder.close();\n logger?.debug('pipeStream(); stream completed');\n break;\n }\n\n await encoder.publishOutput(value, resolveWriteOptions?.(value));\n }\n } catch (error) {\n reason = 'error';\n caughtError = error instanceof Error ? error : new Error(String(error));\n logger?.error('pipeStream(); stream error', { error: caughtError.message });\n try {\n await encoder.close();\n } catch {\n // Best-effort: encoder close in the error path may also fail\n // (e.g. channel disconnected). The original error is preserved in\n // the StreamResult reason (\"error\").\n }\n } finally {\n abort.cleanup();\n reader.releaseLock();\n }\n\n return { reason, error: caughtError };\n};\n","/**\n * Server-side run state management and lifecycle event publishing.\n *\n * Owns the authoritative run lifecycle. Tracks active runs with their\n * AbortControllers and clientIds. Publishes run-start, run-resume, run-suspend, and\n * run-end events on the Ably channel so all clients can react to run\n * state changes.\n */\n\nimport type * as Ably from 'ably';\n\nimport { EVENT_RUN_END, EVENT_RUN_RESUME, EVENT_RUN_START, EVENT_RUN_SUSPEND } from '../../constants.js';\nimport type { Logger } from '../../logger.js';\nimport { buildLifecycleHeaders } from './headers.js';\nimport type { RunEndReason } from './types.js';\n\n/**\n * Per-invocation metadata carried on a run's opening lifecycle event. A\n * continuation (re-entering an existing run) sets `continuation` and omits the\n * structural `parent` / `forkOf` / `regenerates` fields.\n */\nexport interface StartRunMetadata {\n /** Structural parent codec-message-id (fresh run-start only). */\n parent?: string;\n /** Forked user-prompt codec-message-id for an edit (fresh run-start only). */\n forkOf?: string;\n /** Regenerated assistant codec-message-id (fresh run-start only). */\n regenerates?: string;\n /** Agent-minted invocation id, carried on the lifecycle event. */\n invocationId?: string;\n /** ClientId of the triggering input event. */\n inputClientId?: string;\n /** Codec-message-id of the triggering input event. */\n inputCodecMessageId?: string;\n /** When true, publish `ai-run-resume` (re-entry) instead of `ai-run-start`. */\n continuation?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Interface\n// ---------------------------------------------------------------------------\n\n/** Manages active runs and publishes run lifecycle events on the channel. */\nexport interface RunManager {\n /**\n * Register a run and publish its opening lifecycle event. Publishes\n * `ai-run-start` for a fresh run, or `ai-run-resume` when `metadata.continuation`\n * is set (a subsequent invocation re-entering an existing run). A resume omits\n * the structural `parent` / `forkOf` / `regenerates` headers — the original\n * run-start owns the run's structure. Returns the run's AbortSignal.\n */\n startRun(\n runId: string,\n clientId?: string,\n controller?: AbortController,\n metadata?: StartRunMetadata,\n ): Promise<AbortSignal>;\n /**\n * Suspend a run. Publishes run-suspend on the channel and drops the run's\n * active-run entry — the agent process terminates on suspend, so there is no\n * live AbortController to retain. A cancel arriving during suspension is a\n * no-op; the resuming invocation re-registers the run via {@link startRun}.\n * Carries the same per-invocation attribution as {@link endRun}\n * (`inputClientId`, `inputCodecMessageId`), since a suspend is the terminal\n * event of the suspending invocation just as run-end is of an ending one.\n */\n suspendRun(runId: string, invocationId?: string, inputClientId?: string, inputCodecMessageId?: string): Promise<void>;\n /**\n * End a run. Publishes run-end on the channel (stamping `reason` as the\n * run-reason header) and drops the run's active-run entry. Carries the same\n * per-invocation attribution as {@link suspendRun} (`invocationId`,\n * `inputClientId`, `inputCodecMessageId`), since run-end is the terminal event\n * of the ending invocation.\n */\n endRun(\n runId: string,\n reason: RunEndReason,\n invocationId?: string,\n inputClientId?: string,\n inputCodecMessageId?: string,\n ): Promise<void>;\n /** Get the AbortSignal for a run. */\n getSignal(runId: string): AbortSignal | undefined;\n /** Get the clientId that owns a run. */\n getClientId(runId: string): string | undefined;\n /** Fire the AbortSignal for a run to cancel any in-flight work. */\n cancel(runId: string): void;\n /** Get all active run IDs. */\n getActiveRunIds(): string[];\n /** Cancel all active runs and clear state. */\n close(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal state\n// ---------------------------------------------------------------------------\n\ninterface ActiveRunEntry {\n controller: AbortController;\n clientId: string;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nclass DefaultRunManager implements RunManager {\n private readonly _channel: Ably.RealtimeChannel;\n private readonly _logger: Logger | undefined;\n private readonly _activeRuns = new Map<string, ActiveRunEntry>();\n\n constructor(channel: Ably.RealtimeChannel, logger?: Logger) {\n this._channel = channel;\n this._logger = logger?.withContext({ component: 'RunManager' });\n }\n\n async startRun(\n runId: string,\n clientId?: string,\n externalController?: AbortController,\n metadata?: StartRunMetadata,\n ): Promise<AbortSignal> {\n this._logger?.trace('DefaultRunManager.startRun();', { runId, clientId });\n\n const controller = externalController ?? new AbortController();\n const resolvedClientId = clientId ?? '';\n this._activeRuns.set(runId, { controller, clientId: resolvedClientId });\n\n // A continuation re-enters an already-started run: publish `ai-run-resume`\n // rather than `ai-run-start`. Resume is a pure re-entry signal — the\n // original run-start already established the run's structure, so the\n // parent / forkOf / regenerates metadata is NOT re-stamped here (doing so\n // would point the run at content within itself). The agent learned this is\n // a continuation from the run-id on the triggering input; the re-entry is\n // conveyed to clients by the event name, not a header echo. The\n // invocation-id / input attribution headers are carried on both.\n const continuation = metadata?.continuation === true;\n\n const headers = buildLifecycleHeaders({\n runId,\n runClientId: resolvedClientId,\n parent: continuation ? undefined : metadata?.parent,\n forkOf: continuation ? undefined : metadata?.forkOf,\n regenerates: continuation ? undefined : metadata?.regenerates,\n invocationId: metadata?.invocationId,\n inputClientId: metadata?.inputClientId,\n inputCodecMessageId: metadata?.inputCodecMessageId,\n });\n\n await this._channel.publish({\n name: continuation ? EVENT_RUN_RESUME : EVENT_RUN_START,\n extras: { ai: { transport: headers } },\n });\n\n this._logger?.debug('DefaultRunManager.startRun(); run started', { runId });\n return controller.signal;\n }\n\n async suspendRun(\n runId: string,\n invocationId?: string,\n inputClientId?: string,\n inputCodecMessageId?: string,\n ): Promise<void> {\n this._logger?.trace('DefaultRunManager.suspendRun();', { runId });\n await this._publishTerminal(EVENT_RUN_SUSPEND, runId, { invocationId, inputClientId, inputCodecMessageId });\n this._logger?.debug('DefaultRunManager.suspendRun(); run suspended', { runId });\n }\n\n async endRun(\n runId: string,\n reason: RunEndReason,\n invocationId?: string,\n inputClientId?: string,\n inputCodecMessageId?: string,\n ): Promise<void> {\n this._logger?.trace('DefaultRunManager.endRun();', { runId, reason });\n await this._publishTerminal(EVENT_RUN_END, runId, { reason, invocationId, inputClientId, inputCodecMessageId });\n this._logger?.debug('DefaultRunManager.endRun(); run ended', { runId, reason });\n }\n\n /**\n * Publish a run's terminal lifecycle event (run-suspend or run-end) and drop\n * its active-run entry. Both events are the suspending/ending invocation's\n * terminal signal, carrying the same per-invocation correlation; they differ\n * only by event name and the run-reason header (run-end). Publishes BEFORE\n * dropping local state so a publish failure leaves the run in the active set.\n * @param eventName - The lifecycle event to publish (run-suspend or run-end).\n * @param runId - The run being suspended or ended.\n * @param attribution - Per-invocation correlation and the terminal reason.\n * @param attribution.reason - Terminal reason; set for run-end, omitted for run-suspend.\n * @param attribution.invocationId - The invocation's id.\n * @param attribution.inputClientId - ClientId of the triggering input event.\n * @param attribution.inputCodecMessageId - Codec-message-id of the triggering input event.\n */\n private async _publishTerminal(\n eventName: string,\n runId: string,\n attribution: {\n reason?: RunEndReason;\n invocationId?: string;\n inputClientId?: string;\n inputCodecMessageId?: string;\n },\n ): Promise<void> {\n const resolvedClientId = this._activeRuns.get(runId)?.clientId ?? '';\n const headers = buildLifecycleHeaders({ runId, runClientId: resolvedClientId, ...attribution });\n await this._channel.publish({ name: eventName, extras: { ai: { transport: headers } } });\n this._activeRuns.delete(runId);\n }\n\n getSignal(runId: string): AbortSignal | undefined {\n return this._activeRuns.get(runId)?.controller.signal;\n }\n\n getClientId(runId: string): string | undefined {\n return this._activeRuns.get(runId)?.clientId;\n }\n\n cancel(runId: string): void {\n this._logger?.debug('DefaultRunManager.cancel();', { runId });\n this._activeRuns.get(runId)?.controller.abort();\n }\n\n getActiveRunIds(): string[] {\n return [...this._activeRuns.keys()];\n }\n\n close(): void {\n this._logger?.trace('DefaultRunManager.close();', { activeRuns: this._activeRuns.size });\n for (const state of this._activeRuns.values()) {\n state.controller.abort();\n }\n this._activeRuns.clear();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a run manager bound to the given channel.\n * @param channel - The Ably channel to publish lifecycle events on.\n * @param logger - Optional logger for diagnostic output.\n * @returns A new {@link RunManager} instance.\n */\nexport const createRunManager = (channel: Ably.RealtimeChannel, logger?: Logger): RunManager =>\n new DefaultRunManager(channel, logger);\n","/**\n * Core agent (server-side) session, parameterized by codec.\n *\n * Composes RunManager and pipeStream to handle the full server-side run\n * lifecycle. Cancel message routing is handled directly by the session's\n * single channel subscription — no separate cancel manager needed.\n *\n * The session exposes a single factory method — `createRun()` — which returns\n * a Run object with explicit lifecycle methods: start(), pipe(), addEvents(),\n * suspend(), and end() (suspend() and end() are both terminal).\n */\n\nimport * as Ably from 'ably';\n\nimport {\n EVENT_CANCEL,\n HEADER_CODEC_MESSAGE_ID,\n HEADER_EVENT_ID,\n HEADER_FORK_OF,\n HEADER_INPUT_CODEC_MESSAGE_ID,\n HEADER_MSG_REGENERATE,\n HEADER_PARENT,\n HEADER_RUN_CLIENT_ID,\n HEADER_RUN_ID,\n} from '../../constants.js';\nimport { ErrorCode } from '../../errors.js';\nimport type { Logger } from '../../logger.js';\nimport { compareBySerial, getTransportHeaders } from '../../utils.js';\nimport { registerAgent } from '../agent.js';\nimport type { Codec, CodecInputEvent, CodecOutputEvent } from '../codec/types.js';\nimport { buildTransportHeaders } from './headers.js';\nimport { evictOldestIfFull } from './internal/bounded-map.js';\nimport { Invocation } from './invocation.js';\nimport { loadConversation, loadRunProjection } from './load-conversation.js';\nimport { pipeStream } from './pipe-stream.js';\nimport type { RunManager } from './run-manager.js';\nimport { createRunManager } from './run-manager.js';\nimport type {\n AgentSession,\n AgentSessionOptions,\n CancelRequest,\n EventsNode,\n LoadConversationOptions,\n MessageNode,\n PipeOptions,\n Run,\n RunEndReason,\n RunRuntime,\n RunView,\n StreamResult,\n} from './types.js';\n\n// ---------------------------------------------------------------------------\n// Run-state lookup helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Wait for every event-id in `expectedInputEventIds` to arrive as a channel\n * message before letting the run proceed to LLM work. Uses the session's\n * unfiltered channel dispatcher (registered in `connect()`) so that\n * messages replayed via channel rewind on attach reach the lookup — no\n * separate history fetch needed.\n *\n * Scope: this awaits the data-carrying input events a send publishes —\n * fresh prompts, edits, regenerates, tool results, and approvals. Control\n * events (cancel etc.) carry no `event-id`, are dispatched\n * separately, and never enter this lookup.\n *\n * Each client-published event in a send (user-message AND amend events\n * such as tool-approval responses and client tool outputs) is stamped\n * with its own `event-id`.\n * The lookup matches incoming messages against the expected set; ids\n * not in the set are ignored, duplicates (rewind redelivering a message\n * also seen live) are deduped by event-id. The wait completes when\n * every expected id has arrived, guaranteeing the channel state is\n * consistent with what the client promised before any downstream\n * processing (loadProjection, streamText) runs.\n *\n * User-message arrivals decode into MessageNodes that populate\n * `run.view.messages`; amend arrivals fold into a fresh projection that\n * has no target message, so they're orphaned and dropped — they only\n * count toward the wait. Collected nodes are returned sorted by Ably\n * `serial` ascending.\n *\n * Bounded by `timeoutMs` as a total budget across all N arrivals. The\n * caller's `signal` aborts the wait. On partial collection at timeout the\n * promise rejects with `InputEventNotFound` and an error message including\n * \"received X of Y\". If any decode throws mid-collection, the whole lookup\n * rejects with `InputEventNotFound` wrapping the decode error as cause —\n * already-collected messages are discarded.\n * @param opts - Lookup parameters.\n * @param opts.register - Session-provided registration that delivers the input events for the expected event-ids. Returns an unregister function.\n * @param opts.codec - Codec used to decode arriving messages.\n * @param opts.invocationId - Invocation identifier — used only for diagnostic logging and error messages.\n * @param opts.runId - Run identifier (used for logging and error messages).\n * @param opts.expectedInputEventIds - Input-event ids the lookup must observe before resolving.\n * @param opts.timeoutMs - Maximum total time to wait for all event-id arrivals.\n * @param opts.signal - AbortSignal that cancels the wait when the run is cancelled.\n * @param opts.logger - Optional logger for diagnostic output.\n * @returns The MessageNodes for arriving user-message events (sorted by Ably\n * serial — empty when every input event was a tool-resolution wire message that\n * decoded to a chunk and produced no node), and the transport headers of\n * the first matched wire message. `firstHeaders` is the canonical source for\n * run-level metadata (clientId, parent, forkOf, continuation flag) because\n * it lands whether or not the decode produced a MessageNode. `firstClientId`\n * carries the publisher's Ably-level `clientId` from that same message — the\n * source of `inputClientId` re-stamping on the agent's published events.\n */\ninterface InputEventLookupResult<TMessage> {\n nodes: MessageNode<TMessage>[];\n firstHeaders?: Record<string, string>;\n firstClientId?: string;\n /**\n * Raw Ably messages observed live for the matched input-event ids, in\n * arrival order. The agent forwards these to `loadRunProjection` so a\n * continuation invocation can fold the just-published client wires\n * (e.g. a tool-output-available) without waiting on Ably's channel\n * history indexing window.\n */\n rawMessages: Ably.InboundMessage[];\n}\n\nconst lookupInputEvents = async <\n TInput extends CodecInputEvent,\n TOutput extends CodecOutputEvent,\n TProjection,\n TMessage,\n>(opts: {\n register: (callback: (msg: Ably.InboundMessage) => void) => () => void;\n codec: Codec<TInput, TOutput, TProjection, TMessage>;\n invocationId: string;\n runId: string;\n expectedInputEventIds: readonly string[];\n timeoutMs: number;\n signal: AbortSignal;\n logger: Logger | undefined;\n}): Promise<InputEventLookupResult<TMessage>> => {\n const { register, codec, invocationId, runId, expectedInputEventIds, timeoutMs, signal, logger } = opts;\n const expectedSet = new Set(expectedInputEventIds);\n const expectedCount = expectedSet.size;\n\n const collected: MessageNode<TMessage>[] = [];\n const rawMessages: Ably.InboundMessage[] = [];\n const matchedInputEventIds = new Set<string>();\n let firstHeaders: Record<string, string> | undefined;\n let firstClientId: string | undefined;\n\n /**\n * Decode an inbound Ably message into MessageNodes via the codec.\n * @param m - The inbound Ably message to decode.\n * @returns The decoded MessageNodes carrying transport headers and serial.\n */\n const decode = (m: Ably.InboundMessage): MessageNode<TMessage>[] => {\n const decoder = codec.createDecoder();\n const headers = getTransportHeaders(m);\n const codecMessageId = headers[HEADER_CODEC_MESSAGE_ID] ?? '';\n const { inputs, outputs } = decoder.decode(m);\n const events: (TInput | TOutput)[] = [...inputs, ...outputs];\n let projection = codec.init();\n for (const event of events) {\n projection = codec.fold(projection, event, { serial: m.serial ?? '', messageId: codecMessageId });\n }\n return codec.getMessages(projection).map(({ message }) => ({\n kind: 'message' as const,\n message,\n codecMessageId,\n parentId: headers[HEADER_PARENT],\n forkOf: headers[HEADER_FORK_OF],\n headers,\n serial: m.serial,\n }));\n };\n\n return new Promise<InputEventLookupResult<TMessage>>((resolve, reject) => {\n let settled = false;\n // Dedupe across rewind-redelivery: rewind may surface a message the\n // listener also saw live. Scoped to the active lookup so it cannot\n // grow unbounded.\n const seenSerials = new Set<string>();\n // Forward-declared so that cleanup() and onCancelled() can reference them\n // before they are assigned. cleanup may run synchronously inside\n // `register(...)` (when buffered input events drain on registration) before\n // `unregister`/`timer` have been assigned — the no-op fallback for\n // unregister and undefined-guard for timer handle that window. The\n // settled-flag re-check after `register` returns reconciles the\n // listener-detach that cleanup couldn't perform inside that window.\n /* eslint-disable prefer-const, unicorn/consistent-function-scoping, @typescript-eslint/no-empty-function -- forward-declared state for the sync-drain reconciliation pattern; see comment above. */\n let unregister: () => void = () => {};\n let timer: ReturnType<typeof setTimeout> | undefined;\n /* eslint-enable */\n const cleanup = (): void => {\n unregister();\n if (timer !== undefined) clearTimeout(timer);\n signal.removeEventListener('abort', onCancelled);\n };\n const onCancelled = (): void => {\n if (settled) return;\n settled = true;\n cleanup();\n reject(\n new Ably.ErrorInfo(`unable to look up input event; run ${runId} was cancelled`, ErrorCode.InvalidArgument, 400),\n );\n };\n signal.addEventListener('abort', onCancelled, { once: true });\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- onCancelled may have settled the promise synchronously above when the signal was already aborted.\n if (settled) return;\n unregister = register((m) => {\n if (settled) return;\n if (m.serial !== undefined && seenSerials.has(m.serial)) return;\n if (m.serial !== undefined) seenSerials.add(m.serial);\n\n const wireHeaders = getTransportHeaders(m);\n\n // Only count messages whose event-id is in the expected set.\n const msgEventId = wireHeaders[HEADER_EVENT_ID];\n if (!msgEventId || !expectedSet.has(msgEventId) || matchedInputEventIds.has(msgEventId)) return;\n matchedInputEventIds.add(msgEventId);\n\n // Capture the trigger event's headers AND its Ably channel-level `clientId`\n // so run-level metadata (parent / forkOf / continuation flag from headers;\n // `inputClientId` from the wire publisher) is available even when the decode\n // produces zero MessageNodes — the case for continuation tool-resolution\n // trigger events whose chunks fold into a fresh empty projection without\n // an assistant to land on.\n if (firstHeaders === undefined) {\n firstHeaders = wireHeaders;\n firstClientId = m.clientId;\n }\n\n let decoded: MessageNode<TMessage>[];\n try {\n decoded = decode(m);\n } catch (error) {\n settled = true;\n cleanup();\n const cause = error instanceof Ably.ErrorInfo ? error : undefined;\n reject(\n new Ably.ErrorInfo(\n `unable to look up input event; decode failed for invocation ${invocationId}: ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.InputEventNotFound,\n 504,\n cause,\n ),\n );\n return;\n }\n for (const node of decoded) collected.push(node);\n rawMessages.push(m);\n if (matchedInputEventIds.size < expectedCount) return;\n settled = true;\n cleanup();\n // Sort by Ably serial ascending so callers see publish order regardless\n // of interleaved rewind+live delivery. Null serials sort last (defensive\n // — input events should always carry a serial).\n collected.sort(compareBySerial);\n logger?.debug('lookupInputEvents(); collected input events', {\n runId,\n invocationId,\n count: collected.length,\n });\n resolve({ nodes: collected, firstHeaders, firstClientId, rawMessages });\n });\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- the register callback may have settled the promise synchronously during buffered input-event drain.\n if (settled) {\n // Sync drain inside register settled the promise; cleanup ran but\n // could not detach the listener because `unregister` was still the\n // no-op. Detach it now.\n unregister();\n return;\n }\n timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n cleanup();\n reject(\n new Ably.ErrorInfo(\n `unable to look up input event; received ${String(collected.length)} of ${String(expectedCount)} input events for invocation ${invocationId} within ${String(timeoutMs)}ms`,\n ErrorCode.InputEventNotFound,\n 504,\n ),\n );\n }, timeoutMs);\n });\n};\n\n// ---------------------------------------------------------------------------\n// Internal run record for cancel routing\n// ---------------------------------------------------------------------------\n\ninterface RegisteredRun {\n runId: string;\n /** Invocation-id this run is associated with, minted by the agent at `createRun` (or the `runtime.invocationId` override). */\n invocationId: string;\n controller: AbortController;\n /** Composite signal that fires when either the internal controller or the external signal aborts. */\n signal: AbortSignal;\n onCancel?: (request: CancelRequest) => Promise<boolean>;\n onError?: (error: Ably.ErrorInfo) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal state machines\n// ---------------------------------------------------------------------------\n\nenum SessionState {\n READY = 'ready',\n CLOSED = 'closed',\n}\n\nenum RunState {\n INITIALIZED = 'initialized',\n STARTED = 'started',\n ENDED = 'ended',\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n// Spec: AIT-ST1\nclass DefaultAgentSession<\n TInput extends CodecInputEvent,\n TOutput extends CodecOutputEvent,\n TProjection,\n TMessage,\n> implements AgentSession<TOutput, TProjection, TMessage> {\n private readonly _channel: Ably.RealtimeChannel;\n private readonly _codec: AgentSessionOptions<TInput, TOutput, TProjection, TMessage>['codec'];\n private readonly _logger: Logger | undefined;\n private readonly _onError: ((error: Ably.ErrorInfo) => void) | undefined;\n private readonly _runManager: RunManager;\n private readonly _registeredRuns = new Map<string, RegisteredRun>();\n /**\n * Reverse index from a run's triggering input codec-message-id to its\n * run-id, populated once `Run.start()`'s input-event lookup resolves the\n * triggering input. Lets `_handleCancelMessage` route a cancel keyed by the\n * input codec-message-id (a fresh send whose run-id the client doesn't know)\n * to the registered run. Entries are removed when the run ends / suspends /\n * the session closes, alongside `_registeredRuns`.\n */\n private readonly _runIdByInputCodecMessageId = new Map<string, string>();\n /**\n * Cancels buffered by triggering input codec-message-id when they arrived\n * before the run was known — i.e. before `Run.start()`'s input-event lookup\n * resolved that input to a run. A fresh run has no run-id at the client's\n * send time (the agent mints it at run-start), so an early cancel can only be\n * keyed by the input codec-message-id, and the `inputCodecMessageId → run`\n * linkage doesn't exist until the lookup completes. `Run.start()` consults\n * this buffer as a PULL once it resolves its `resolvedInputCodecMessageId`,\n * honouring any cancel that arrived first. Mirrors `_inputEventBuffer`: FIFO\n * eviction at `_inputEventBufferLimit` entries, cleared on `close()`.\n */\n private readonly _deferredCancels = new Map<string, Ably.InboundMessage>();\n /**\n * Active input-event lookups keyed by `event-id`. The channel listener\n * dispatches each input event to the lookup that registered for its\n * `event-id`, so that messages replayed via channel rewind (and live\n * messages alike) reach the right lookup without each lookup having to\n * subscribe separately, and without depending on a client-minted\n * `invocation-id`.\n */\n private readonly _pendingInputEventLookups = new Map<string, (msg: Ably.InboundMessage) => void>();\n /**\n * Input events buffered by `event-id` when no lookup callback was\n * registered at delivery time. Rewind replays user messages on attach —\n * before `run.start()` runs — so without buffering they would be dropped.\n * Each `event-id` maps to an ordered array so rewind redelivery of the\n * same event before registration is preserved (the lookup later dedupes by\n * serial). `_registerInputEventListener` drains the buffer on registration.\n * FIFO eviction at `_inputEventBufferLimit` event entries (each entry counts\n * once regardless of array length).\n */\n private readonly _inputEventBuffer = new Map<string, Ably.InboundMessage[]>();\n private readonly _inputEventBufferLimit: number;\n private readonly _channelListener: (msg: Ably.InboundMessage) => void;\n private readonly _inputEventLookupTimeoutMs: number;\n\n private _state = SessionState.READY;\n private _connectPromise: Promise<void> | undefined;\n private _hasAttachedOnce: boolean;\n private readonly _onChannelStateChange: Ably.channelEventCallback;\n\n constructor(options: AgentSessionOptions<TInput, TOutput, TProjection, TMessage>) {\n this._codec = options.codec;\n // Spec: AIT-ST1a, AIT-ST1a2 — register this SDK on both the connection\n // (options.agents) and channel-attach (params.agent) paths. Idempotent\n // across sessions sharing one client.\n const registerOptions = registerAgent(options.client, options.codec);\n // Attach with a rewind window (default 2m) so a freshly-constructed\n // agent session can locate an input event that was published before it\n // attached (closes the lookup race when a per-request agent is spun\n // up after the client has already POSTed). Tunable via\n // `AgentSessionOptions.rewindWindow`.\n const channelOptions: Ably.ChannelOptions = {\n params: { ...registerOptions.params, rewind: options.rewindWindow ?? '2m' },\n };\n this._channel = options.client.channels.get(options.channelName, channelOptions);\n this._logger = options.logger?.withContext({ component: 'AgentSession' });\n this._onError = options.onError;\n this._runManager = createRunManager(this._channel, this._logger);\n this._inputEventLookupTimeoutMs = options.inputEventLookupTimeoutMs ?? 30000;\n this._inputEventBufferLimit = options.inputEventBufferLimit ?? 200;\n\n this._channelListener = (msg: Ably.InboundMessage) => {\n this._handleChannelMessage(msg);\n };\n\n // Spec: AIT-ST12, AIT-ST12a\n // Listen for channel state changes that break message continuity. The\n // session only consumes cancel messages from the channel, so losing one\n // is survivable — but the developer needs to know so they can decide\n // whether to cancel in-flight work. _hasAttachedOnce is seeded from the\n // channel's current state so pre-attached channels are handled correctly;\n // it distinguishes the initial attach from a genuine discontinuity.\n this._hasAttachedOnce = this._channel.state === 'attached';\n this._onChannelStateChange = (stateChange: Ably.ChannelStateChange) => {\n this._handleChannelStateChange(stateChange);\n };\n this._channel.on(this._onChannelStateChange);\n\n this._logger?.debug('DefaultAgentSession(); session created');\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n // Spec: AIT-ST2\n // eslint-disable-next-line @typescript-eslint/promise-function-async -- preserve reference equality across calls\n connect(): Promise<void> {\n if (this._state === SessionState.CLOSED) {\n return Promise.reject(new Ably.ErrorInfo('unable to connect; session is closed', ErrorCode.SessionClosed, 400));\n }\n if (this._connectPromise) return this._connectPromise;\n\n this._logger?.trace('DefaultAgentSession.connect();');\n // Subscribe unfiltered (before attach, per RTL7g — subscribe implicitly\n // attaches the channel). An unfiltered subscribe ensures that messages\n // replayed via channel rewind reach the dispatcher so input-event\n // lookups can match against them; the dispatcher then routes by name\n // (cancel vs. input event). A name-filtered subscribe would silently\n // drop replayed user messages because rewind delivers them to listeners\n // registered at attach time only.\n this._connectPromise = this._channel.subscribe(this._channelListener).then(\n () => {\n this._logger?.debug('DefaultAgentSession.connect(); subscribed and attached');\n },\n (error: unknown) => {\n const errInfo = new Ably.ErrorInfo(\n `unable to subscribe to channel; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.SessionSubscriptionError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n this._logger?.error('DefaultAgentSession.connect(); subscribe failed');\n this._onError?.(errInfo);\n throw errInfo;\n },\n );\n return this._connectPromise;\n }\n\n /**\n * Register a callback to receive the input events carrying any of the\n * given `eventIds`. Lookups must share the session's unfiltered\n * subscription rather than registering their own subscribe — Ably's\n * rewind only delivers to listeners present at attach time.\n *\n * The listener remains registered after the initial buffer drain so a\n * matching event that arrives live (rather than from the buffer) still\n * reaches the lookup until it unregisters itself. Today the only caller\n * registers a single trigger event-id; the array form keeps the\n * registration capable of awaiting several ids without changing callers.\n * @param eventIds - The `event-id`s this listener cares about.\n * @param callback - Invoked once per matching Ably message, in buffer-insertion order for drained entries.\n * @returns Unregister function. Safe to call multiple times.\n */\n private _registerInputEventListener(\n eventIds: readonly string[],\n callback: (msg: Ably.InboundMessage) => void,\n ): () => void {\n for (const eventId of eventIds) {\n this._pendingInputEventLookups.set(eventId, callback);\n }\n // Drain any buffered input events for these event-ids — rewind replays\n // user messages on attach before run.start() can register the callback.\n // Without this drain, the lookup waits the full\n // `inputEventLookupTimeoutMs` for a live arrival that never comes. Set\n // all listeners before draining so a drain that completes the lookup\n // synchronously cannot leave a later event-id unmapped.\n for (const eventId of eventIds) {\n const buffered = this._inputEventBuffer.get(eventId);\n if (buffered) {\n this._inputEventBuffer.delete(eventId);\n for (const m of buffered) callback(m);\n }\n }\n return () => {\n for (const eventId of eventIds) {\n if (this._pendingInputEventLookups.get(eventId) === callback) {\n this._pendingInputEventLookups.delete(eventId);\n }\n }\n };\n }\n\n // Spec: AIT-ST3\n createRun(invocation: Invocation, runtime?: RunRuntime<TOutput>): Run<TOutput, TProjection, TMessage> {\n this._logger?.trace('DefaultAgentSession.createRun();', { inputEventId: invocation.inputEventId });\n return this._createRun(invocation, runtime ?? {});\n }\n\n // Spec: AIT-ST11\n async close(): Promise<void> {\n if (this._state === SessionState.CLOSED) return;\n this._state = SessionState.CLOSED;\n this._logger?.trace('DefaultAgentSession.close();');\n if (this._connectPromise) {\n this._channel.unsubscribe(this._channelListener);\n }\n this._channel.off(this._onChannelStateChange);\n for (const reg of this._registeredRuns.values()) {\n reg.controller.abort();\n }\n this._registeredRuns.clear();\n this._runIdByInputCodecMessageId.clear();\n this._deferredCancels.clear();\n this._pendingInputEventLookups.clear();\n this._inputEventBuffer.clear();\n this._runManager.close();\n\n // Detach the channel this session attached. connect() subscribes (which\n // implicitly attaches), so we only detach when connect() ran. Best-effort:\n // a detach failure (e.g. the channel is already FAILED) must not throw out\n // of close().\n if (this._connectPromise) {\n try {\n await this._channel.detach();\n } catch (error) {\n // Swallowed (see above): a detach failure must not throw out of\n // close(). Logged at debug for observability.\n this._logger?.debug('DefaultAgentSession.close(); channel detach failed', { error });\n }\n }\n\n this._logger?.debug('DefaultAgentSession.close(); session closed');\n }\n\n // -------------------------------------------------------------------------\n // Cancel message routing\n // -------------------------------------------------------------------------\n\n private async _handleCancelMessage(msg: Ably.InboundMessage): Promise<void> {\n const headers = getTransportHeaders(msg);\n const runId = headers[HEADER_RUN_ID];\n const inputCodecMessageId = headers[HEADER_INPUT_CODEC_MESSAGE_ID];\n\n // Malformed cancel: drop with warn. A cancel must identify its target by\n // `run-id` (a continuation, whose run-id the client knows) and/or by\n // `input-codec-message-id` (a fresh send, before the agent minted the\n // run-id). Neither present means there is nothing to route to.\n if (!runId && !inputCodecMessageId) {\n this._logger?.warn('DefaultAgentSession._handleCancelMessage(); missing run-id and input-codec-message-id', {\n serial: msg.serial,\n });\n return;\n }\n\n // Primary path — match by run-id (continuations, whose run-id the client\n // already knows). Resolve the input-codec-message-id to a run-id when the\n // run-id wasn't supplied (a fresh-send cancel that arrived after the run's\n // input-event lookup resolved, so the linkage already exists).\n const resolvedRunId =\n runId ?? (inputCodecMessageId ? this._runIdByInputCodecMessageId.get(inputCodecMessageId) : undefined);\n const reg = resolvedRunId ? this._registeredRuns.get(resolvedRunId) : undefined;\n\n if (!reg) {\n // The run isn't known yet. A fresh-send cancel can race ahead of the\n // run's input-event lookup (which is what establishes the\n // input-codec-message-id → run linkage). Buffer it by\n // input-codec-message-id so `Run.start()` can pull and honour it once it\n // resolves the triggering input. A bare run-id cancel for an unknown run\n // is a no-op (the run never existed here, or already ended).\n if (inputCodecMessageId !== undefined) {\n this._bufferDeferredCancel(inputCodecMessageId, msg);\n }\n return;\n }\n\n await this._cancelRegistration(reg, msg);\n }\n\n /**\n * Buffer a cancel that arrived before its target run was known, keyed by the\n * triggering input's codec-message-id. FIFO-evicts the oldest entry at\n * `_inputEventBufferLimit` (mirroring `_inputEventBuffer`). A later cancel\n * for the same input replaces the earlier one — the intent is identical.\n * @param inputCodecMessageId - The triggering input's codec-message-id.\n * @param msg - The raw cancel message (passed to `onCancel`).\n */\n private _bufferDeferredCancel(inputCodecMessageId: string, msg: Ably.InboundMessage): void {\n const evicted = evictOldestIfFull(this._deferredCancels, inputCodecMessageId, this._inputEventBufferLimit);\n if (evicted !== undefined) {\n this._logger?.warn('DefaultAgentSession._bufferDeferredCancel(); deferred-cancel buffer full, dropping oldest', {\n evictedInputCodecMessageId: evicted,\n limit: this._inputEventBufferLimit,\n });\n }\n this._deferredCancels.set(inputCodecMessageId, msg);\n this._logger?.debug('DefaultAgentSession._bufferDeferredCancel(); buffered early cancel', {\n inputCodecMessageId,\n serial: msg.serial,\n });\n }\n\n /**\n * Pull and honour a cancel buffered before this run was known. Called from\n * `Run.start()` once the input-event lookup resolves the run's triggering\n * input codec-message-id — the point at which the\n * `input-codec-message-id → run` linkage first exists. No-op when no cancel\n * was buffered for that input.\n * @param reg - The now-known run registration.\n * @param inputCodecMessageId - The run's resolved triggering input codec-message-id.\n */\n private async _pullDeferredCancel(reg: RegisteredRun, inputCodecMessageId: string): Promise<void> {\n const buffered = this._deferredCancels.get(inputCodecMessageId);\n if (buffered === undefined) return;\n this._deferredCancels.delete(inputCodecMessageId);\n this._logger?.debug('DefaultAgentSession._pullDeferredCancel(); honouring buffered cancel', {\n runId: reg.runId,\n inputCodecMessageId,\n });\n await this._cancelRegistration(reg, buffered);\n }\n\n /**\n * Fire a cancel against a known run: consult its `onCancel` authorization\n * hook (if any), then abort the run's controller. Shared by the run-id match,\n * the input-codec-message-id match, and the buffered-cancel pull so all three\n * honour `onCancel` and surface handler errors identically.\n * @param reg - The target run registration.\n * @param msg - The raw cancel message (passed to `onCancel`).\n */\n private async _cancelRegistration(reg: RegisteredRun, msg: Ably.InboundMessage): Promise<void> {\n const { runId } = reg;\n this._logger?.debug('DefaultAgentSession._cancelRegistration(); matched run', { runId });\n\n const request: CancelRequest = { message: msg, runId };\n\n try {\n if (reg.onCancel) {\n const allowed = await reg.onCancel(request);\n if (!allowed) {\n this._logger?.debug('DefaultAgentSession._cancelRegistration(); cancel rejected by onCancel', {\n runId,\n });\n return;\n }\n }\n reg.controller.abort();\n this._logger?.debug('DefaultAgentSession._cancelRegistration(); run cancelled', { runId });\n } catch (error) {\n const errInfo = new Ably.ErrorInfo(\n `unable to process cancel for run ${runId}; onCancel handler threw: ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.CancelListenerError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n this._logger?.error('DefaultAgentSession._cancelRegistration(); onCancel threw', { runId });\n (reg.onError ?? this._onError)?.(errInfo);\n }\n }\n\n // -------------------------------------------------------------------------\n // Channel state change handler\n // -------------------------------------------------------------------------\n\n // Spec: AIT-ST12, AIT-ST12a\n private _handleChannelStateChange(stateChange: Ably.ChannelStateChange): void {\n if (this._state === SessionState.CLOSED) return;\n\n const { current, resumed } = stateChange;\n\n // Track the initial attach so we don't treat it as a discontinuity\n if (current === 'attached' && !this._hasAttachedOnce) {\n this._hasAttachedOnce = true;\n return;\n }\n\n // Continuity-breaking states:\n // - FAILED, SUSPENDED, DETACHED: no more messages expected (or gap)\n // - ATTACHED with resumed: false (UPDATE): messages were lost\n const continuityLost =\n current === 'failed' || current === 'suspended' || current === 'detached' || (current === 'attached' && !resumed);\n\n if (!continuityLost) return;\n\n this._logger?.error('DefaultAgentSession._handleChannelStateChange(); channel continuity lost', {\n current,\n resumed,\n previous: stateChange.previous,\n });\n\n const err = new Ably.ErrorInfo(\n `unable to deliver cancel messages; channel continuity lost (${current}${current === 'attached' ? ', resumed: false' : ''})`,\n ErrorCode.ChannelContinuityLost,\n 500,\n stateChange.reason,\n );\n\n // Session-level notification only: continuity loss is not scoped to any\n // run. Per-run onError handlers are reserved for errors from that run's\n // own operations (publish failures, encoder errors). Developers that need\n // per-run reaction can iterate active runs from the session handler.\n this._onError?.(err);\n }\n\n // -------------------------------------------------------------------------\n // Channel subscription handler\n // -------------------------------------------------------------------------\n\n private _handleChannelMessage(msg: Ably.InboundMessage): void {\n try {\n if (msg.name === EVENT_CANCEL) {\n // Fire-and-forget async handler — errors are caught internally.\n this._handleCancelMessage(msg).catch((error: unknown) => {\n const errInfo = new Ably.ErrorInfo(\n `unable to route cancel message; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.CancelListenerError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n this._logger?.error('DefaultAgentSession._handleChannelMessage(); cancel routing error');\n this._onError?.(errInfo);\n });\n return;\n }\n\n // Dispatch client-published input events to the lookup registered\n // for their `event-id`. Every client-originated event in an\n // invocation (user-message AND amend events such as tool-approval\n // responses and client tool outputs) carries `event-id`; the lookup\n // waits for every promised id to arrive before letting the run start\n // LLM work. Routing by `event-id` rather than `invocation-id` keeps\n // the dispatcher independent of any client-minted invocation\n // identity. Server-side lifecycle messages (run-start, run-end,\n // cancel, error) never stamp `event-id`, so they're naturally\n // excluded.\n const headers = getTransportHeaders(msg);\n const eventId = headers[HEADER_EVENT_ID];\n if (eventId !== undefined) {\n const listener = this._pendingInputEventLookups.get(eventId);\n if (listener) {\n listener(msg);\n } else {\n // Buffer for a future `_registerInputEventListener` call. This is\n // load-bearing for the \"agent attaches after publish\" scenario\n // where channel rewind delivers user messages before\n // `run.start()` runs.\n const existing = this._inputEventBuffer.get(eventId);\n if (existing) {\n existing.push(msg);\n } else {\n // FIFO eviction: drop the oldest event entry (and all its buffered\n // redeliveries). Clients whose input event was evicted will fail\n // their lookup with `InputEventNotFound` — this warn is the only\n // operator-visible signal that capacity caused the failure.\n const evicted = evictOldestIfFull(this._inputEventBuffer, eventId, this._inputEventBufferLimit);\n if (evicted !== undefined) {\n this._logger?.warn(\n 'DefaultAgentSession._handleChannelMessage(); input-event buffer full, dropping oldest entry',\n { evictedEventId: evicted, limit: this._inputEventBufferLimit },\n );\n }\n this._inputEventBuffer.set(eventId, [msg]);\n }\n }\n }\n } catch (error) {\n const errInfo = new Ably.ErrorInfo(\n `unable to process channel message; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.SessionSubscriptionError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n this._logger?.error('DefaultAgentSession._handleChannelMessage(); subscription error');\n this._onError?.(errInfo);\n }\n }\n\n // -------------------------------------------------------------------------\n // Connection guard\n // -------------------------------------------------------------------------\n\n private async _requireConnected(method: string): Promise<void> {\n if (!this._connectPromise) {\n throw new Ably.ErrorInfo(\n `unable to ${method}; connect() must be called before ${method}()`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n return this._connectPromise;\n }\n\n // -------------------------------------------------------------------------\n // Run creation\n // -------------------------------------------------------------------------\n\n private _createRun(invocation: Invocation, runtime: RunRuntime<TOutput>): Run<TOutput, TProjection, TMessage> {\n // The run-id is not carried in the invocation body — the agent mints it.\n // Mint a provisional id now (or take the `runtime.runId` override for\n // tests / in-process drivers) — this IS the id for a fresh run. A\n // continuation overrides it in `Run.start()` with the existing run-id read\n // off the triggering input event's wire headers (the run it re-enters).\n // Mirrors the invocationId mint below.\n let runId = runtime.runId ?? crypto.randomUUID();\n // The agent mints the invocation id — one per HTTP request that invokes\n // it. A per-run override (runtime.invocationId) supports deterministic ids\n // in tests and in-process drivers.\n const invocationId = runtime.invocationId ?? crypto.randomUUID();\n const inputEventLookupTimeoutMs = this._inputEventLookupTimeoutMs;\n const { onMessage, onCancelled, onCancel, onError: runOnError, signal: externalSignal } = runtime;\n\n const controller = new AbortController();\n let state = RunState.INITIALIZED;\n\n // Compose the internal controller signal with the external signal (e.g.\n // req.signal) so platform-level cancellation (request cancellation, function\n // timeout) cancels the run through the same path as Ably cancel messages.\n const signal = externalSignal ? AbortSignal.any([controller.signal, externalSignal]) : controller.signal;\n\n // Spec: AIT-ST3a — register immediately so `close()` aborts an in-flight\n // start() and a post-lookup cancel can fire the AbortSignal. Keyed by the\n // provisional run-id; a continuation re-keys to the real id in start()\n // once the triggering input reveals it.\n const registration: RegisteredRun = {\n runId,\n invocationId,\n controller,\n signal,\n onCancel,\n onError: runOnError,\n };\n this._registeredRuns.set(runId, registration);\n\n // Capture instance members as locals so arrow functions close over them\n // without needing `this` (avoids unicorn/no-this-assignment).\n const logger = this._logger;\n const runManager = this._runManager;\n const codec = this._codec;\n const channel = this._channel;\n const registeredRuns = this._registeredRuns;\n const runIdByInputCodecMessageId = this._runIdByInputCodecMessageId;\n const deferredCancels = this._deferredCancels;\n const requireConnected = this._requireConnected.bind(this);\n const registerInputEventListener = this._registerInputEventListener.bind(this);\n const pullDeferredCancel = this._pullDeferredCancel.bind(this);\n const inputEventId = invocation.inputEventId;\n\n // `viewMessages` starts empty. `Run.start()` populates it via the\n // channel-rewind input-event lookup, pulling in user-message MessageNodes\n // as they arrive on the channel.\n const viewMessages: MessageNode<TMessage>[] = [];\n const view: RunView<TMessage> = {\n get messages() {\n return viewMessages;\n },\n };\n\n // Per-run metadata resolved from the input-event lookup result. The first\n // matched wire message's headers carry the run's `clientId`, `parent`, and\n // `forkOf`, and — for a continuation — the `run-id` it re-enters (a fresh\n // input carries none; the client stamps a run-id only when re-entering a\n // run it already knows). Its Ably-level publisher `clientId` becomes the\n // `inputClientId` re-stamped on the agent's own publishes. Captured\n // separately from `viewMessages` because tool-resolution wire messages\n // (`tool-output-available` etc.) decode to chunks and produce zero\n // MessageNodes — the metadata still needs to surface.\n let resolvedClientId: string | undefined;\n let resolvedInputClientId: string | undefined;\n let resolvedParent: string | undefined;\n let resolvedForkOf: string | undefined;\n let resolvedRegenerates: string | undefined;\n let resolvedInputCodecMessageId: string | undefined;\n let resolvedContinuation = false;\n let firstLookupHeaders: Record<string, string> | undefined;\n /**\n * The reply run's structural-parent fallback, computed once in\n * `Run.start()` (after the input-event lookup has populated `viewMessages`)\n * and consumed by every `Run.pipe()` publish. A per-stream\n * `streamOpts.parent` still overrides it. Storing it here keeps it stable\n * across pipes and decouples the assistant's structural parent from the\n * run-start wire's own `parent`.\n */\n let assistantParentFallback: string | undefined;\n /**\n * Raw Ably messages observed live by the input-event lookup. Passed to\n * `loadRunProjection` so the just-published client wires don't need\n * to wait on Ably's channel history indexing window. Empty when no\n * lookup ran or no messages matched.\n */\n let liveLookupMessages: readonly Ably.InboundMessage[] | undefined;\n\n /**\n * Remove this run from the session's routing maps. Drops the\n * `_registeredRuns` entry plus the `input-codec-message-id → run-id`\n * reverse index (and any stale deferred cancel still buffered for that\n * input), keeping the cancel-routing state consistent when the run ends,\n * suspends, or its start fails.\n */\n const deregisterRun = (): void => {\n registeredRuns.delete(runId);\n if (resolvedInputCodecMessageId !== undefined) {\n runIdByInputCodecMessageId.delete(resolvedInputCodecMessageId);\n deferredCancels.delete(resolvedInputCodecMessageId);\n }\n };\n\n // Most recently loaded projection for this run only, cached by\n // `Run.loadProjection()` and `Run.loadConversation()` so the `messages`\n // getter can return the run's folded messages. `undefined` before any\n // load call; the getter then falls back to the live `viewMessages`.\n let cachedProjection: TProjection | undefined;\n\n // Full multi-turn conversation, set by `Run.loadConversation()`. When set,\n // it takes priority over `cachedProjection` in the `messages` getter —\n // the getter then returns the complete ancestor-chain + current-run\n // messages instead of the current run alone.\n let cachedConversation: TMessage[] | undefined;\n\n const run: Run<TOutput, TProjection, TMessage> = {\n get runId() {\n return runId;\n },\n get invocationId() {\n return invocationId;\n },\n get abortSignal() {\n return signal;\n },\n get view() {\n return view;\n },\n get messages() {\n if (cachedConversation !== undefined) {\n return [...cachedConversation];\n }\n if (cachedProjection !== undefined) {\n return codec.getMessages(cachedProjection).map((m) => m.message);\n }\n return viewMessages.map((n) => n.message);\n },\n\n // Spec: AIT-ST4, AIT-ST4a, AIT-ST4b\n start: async (): Promise<void> => {\n logger?.trace('Run.start();', { runId, inputEventId });\n\n await requireConnected('start');\n\n // Spec: AIT-ST4a\n if (signal.aborted) {\n throw new Ably.ErrorInfo(\n `unable to start run; run ${runId} was cancelled before start()`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n if (state !== RunState.INITIALIZED) return;\n state = RunState.STARTED;\n\n // Look up the triggering input event on the channel so the agent\n // can read the user's message and per-run metadata (parent, forkOf,\n // continuation flag) before publishing run-start. Skip when\n // inputEventLookupTimeoutMs === 0 (tests and in-process drivers) or\n // when no inputEventId is set (invocation requires no channel lookup).\n if (inputEventId && inputEventLookupTimeoutMs > 0) {\n try {\n const found = await lookupInputEvents<TInput, TOutput, TProjection, TMessage>({\n register: (callback) => registerInputEventListener([inputEventId], callback),\n codec,\n invocationId,\n runId,\n expectedInputEventIds: [inputEventId],\n timeoutMs: inputEventLookupTimeoutMs,\n signal,\n logger,\n });\n for (const m of found.nodes) viewMessages.push(m);\n if (found.firstHeaders !== undefined) firstLookupHeaders = found.firstHeaders;\n if (found.firstClientId !== undefined) resolvedInputClientId = found.firstClientId;\n liveLookupMessages = found.rawMessages;\n } catch (error) {\n const errInfo =\n error instanceof Ably.ErrorInfo\n ? error\n : new Ably.ErrorInfo(\n `unable to look up input event; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.InputEventNotFound,\n 504,\n );\n // The rejection bubbles up to the developer's HTTP handler,\n // which surfaces the failure as a non-2xx response — that is\n // the signal the client sees. No channel publish: an\n // `ai-run-end` without a preceding `ai-run-start` would break\n // the lifecycle invariant for other channel observers.\n deregisterRun();\n logger?.error('Run.start(); input-event lookup failed', { runId, invocationId });\n throw errInfo;\n }\n }\n\n // Resolve per-run metadata from the first matched wire message's\n // headers — they carry `clientId`, `parent`, and `forkOf`.\n // Continuations of a suspended run pick up the suspended assistant's\n // parent in the same headers (the continuation wire message parents off\n // the assistant). A `run-id` on the triggering input marks a\n // continuation (re-entry via `ai-run-resume`); a fresh input carries\n // none and opens the run with `ai-run-start`. Fall back to the first\n // MessageNode's headers for the path where the lookup ran with\n // `viewMessages` already populated and no `firstHeaders` was captured.\n const sourceHeaders = firstLookupHeaders ?? viewMessages[0]?.headers;\n if (sourceHeaders) {\n resolvedClientId = sourceHeaders[HEADER_RUN_CLIENT_ID];\n resolvedParent = sourceHeaders[HEADER_PARENT];\n resolvedForkOf = sourceHeaders[HEADER_FORK_OF];\n resolvedRegenerates = sourceHeaders[HEADER_MSG_REGENERATE];\n resolvedInputCodecMessageId = sourceHeaders[HEADER_CODEC_MESSAGE_ID];\n\n // The triggering input's run-id (if any) IS this run's identity.\n // Present → a continuation re-entering that run: adopt the id,\n // overriding the provisional one minted at construction, and re-key\n // the registration so cancel routing / deregistration resolve to the\n // real run. Absent → a fresh run: the provisional id stands and the\n // run opens with run-start.\n const wireRunId = sourceHeaders[HEADER_RUN_ID];\n resolvedContinuation = wireRunId !== undefined;\n if (wireRunId !== undefined && wireRunId !== runId) {\n registeredRuns.delete(runId);\n runId = wireRunId;\n registration.runId = runId;\n registeredRuns.set(runId, registration);\n }\n }\n\n // Compute the reply run's structural-parent fallback now that the\n // lookup has populated `viewMessages`: the triggering user message,\n // or — for regenerate wires that match by inputEventId but produce no\n // MessageNodes — the input wire's own `parent`. `Run.pipe()` consumes\n // this for every assistant publish.\n assistantParentFallback = viewMessages.at(-1)?.codecMessageId ?? resolvedParent;\n\n // The triggering input's codec-message-id is now resolved, so the\n // `input-codec-message-id → run` linkage exists: index it for live\n // cancels and pull any cancel that arrived before the run was known\n // (a fresh-send cancel published before the agent minted this run-id).\n // Honouring it here may abort the controller before run-start; that is\n // fine — the abort propagates through the same signal a normal cancel\n // would use.\n if (resolvedInputCodecMessageId !== undefined) {\n runIdByInputCodecMessageId.set(resolvedInputCodecMessageId, runId);\n await pullDeferredCancel(registration, resolvedInputCodecMessageId);\n }\n\n try {\n await runManager.startRun(runId, resolvedClientId, controller, {\n // Stamp the reply run's STRUCTURAL parent (its input node, M_user) —\n // the same value the output path stamps — not the input wire's own\n // parent. Makes `parent` structural on every wire so the Tree's two\n // creation paths agree regardless of arrival order. Valid only now\n // that M_user is a separate input node (the two-node flip).\n parent: assistantParentFallback,\n forkOf: resolvedForkOf,\n regenerates: resolvedRegenerates,\n invocationId,\n inputClientId: resolvedInputClientId,\n inputCodecMessageId: resolvedInputCodecMessageId,\n continuation: resolvedContinuation,\n });\n } catch (error) {\n const errInfo = new Ably.ErrorInfo(\n `unable to publish run-start for run ${runId}; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.RunLifecycleError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n logger?.error('Run.start(); failed to publish run-start', { runId });\n throw errInfo;\n }\n\n logger?.debug('Run.start(); run started', { runId, inputEventId });\n },\n\n // Spec: AIT-ST5c\n addEvents: async (nodes: EventsNode<TOutput>[]): Promise<void> => {\n logger?.trace('Run.addEvents();', { runId, count: nodes.length });\n\n await requireConnected('addEvents');\n\n if (state === RunState.INITIALIZED) {\n throw new Ably.ErrorInfo(\n `unable to add events; start() must be called before addEvents() (run ${runId})`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n\n const runOwnerClientId = runManager.getClientId(runId);\n\n try {\n for (const node of nodes) {\n const headers = buildTransportHeaders({\n role: 'assistant',\n runId,\n codecMessageId: node.codecMessageId,\n runClientId: runOwnerClientId,\n invocationId,\n inputClientId: resolvedInputClientId,\n inputCodecMessageId: resolvedInputCodecMessageId,\n });\n\n const encoder = codec.createEncoder(channel, {\n extras: { headers },\n onMessage,\n });\n\n for (const event of node.events) {\n await encoder.publishOutput(event);\n }\n\n await encoder.close();\n }\n } catch (error) {\n const errInfo = new Ably.ErrorInfo(\n `unable to publish events for run ${runId}; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.RunLifecycleError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n logger?.error('Run.addEvents(); publish failed', { runId });\n throw errInfo;\n }\n\n logger?.debug('Run.addEvents(); events published', { runId, count: nodes.length });\n },\n\n loadProjection: async (): Promise<TProjection> => {\n logger?.trace('Run.loadProjection();', { runId });\n await requireConnected('loadProjection');\n const projection = await loadRunProjection<TInput, TOutput, TProjection, TMessage>({\n channel,\n codec,\n runId,\n signal,\n logger,\n liveMessages: liveLookupMessages,\n });\n cachedProjection = projection;\n return projection;\n },\n\n loadConversation: async (options?: LoadConversationOptions): Promise<TMessage[]> => {\n logger?.trace('Run.loadConversation();', { runId });\n await requireConnected('loadConversation');\n const { messages, projection } = await loadConversation<TInput, TOutput, TProjection, TMessage>({\n channel,\n codec,\n runId,\n signal,\n logger,\n liveMessages: liveLookupMessages,\n assistantParentFallback,\n pageLimit: options?.pageLimit ?? 200,\n maxMessages: options?.maxMessages ?? 2000,\n });\n cachedProjection = projection;\n cachedConversation = messages;\n return messages;\n },\n\n // Spec: AIT-ST6, AIT-ST6a, AIT-ST6b, AIT-ST6b1, AIT-ST6b2, AIT-ST6b3, AIT-ST6c\n pipe: async (stream: ReadableStream<TOutput>, streamOpts?: PipeOptions<TOutput>): Promise<StreamResult> => {\n logger?.trace('Run.pipe();', { runId });\n\n await requireConnected('pipe');\n\n if (state === RunState.INITIALIZED) {\n throw new Ably.ErrorInfo(\n `unable to pipe stream; start() must be called before pipe() (run ${runId})`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n\n const runOwnerClientId = runManager.getClientId(runId);\n\n // The assistant message's parent: an explicit per-stream\n // `streamOpts.parent` from the caller, else the reply run's\n // structural-parent fallback computed once at run-start\n // (`assistantParentFallback` — the triggering user message, or the\n // input wire's own parent for regenerate wires that produced no\n // MessageNodes). Owning the default here means agent routes don't have\n // to pass `{ parent: lastUserCodecMessageId }` to keep tree threading\n // correct; edit-then-regenerate sibling resolution relies on the\n // user→assistant chain being explicit.\n const assistantParent = streamOpts?.parent ?? assistantParentFallback;\n const assistantForkOf = streamOpts?.forkOf ?? resolvedForkOf;\n // Echo `msg-regenerate` on the assistant wire so that a\n // client receiving the assistant chunk before `ai-run-start`\n // (e.g. via history pagination across a page boundary, or a lost\n // lifecycle publish) can still populate `RunNode.regeneratesCodecMessageId`\n // when creating the Run from headers. Mirrors the symmetric\n // behaviour for `assistantForkOf` on edit runs.\n const assistantRegenerates = resolvedRegenerates;\n\n const codecMessageId = crypto.randomUUID();\n const defaultHeaders = buildTransportHeaders({\n role: 'assistant',\n runId,\n codecMessageId,\n runClientId: runOwnerClientId,\n parent: assistantParent,\n forkOf: assistantForkOf,\n invocationId,\n inputClientId: resolvedInputClientId,\n inputCodecMessageId: resolvedInputCodecMessageId,\n regenerates: assistantRegenerates,\n });\n const encoder = codec.createEncoder(channel, {\n extras: { headers: defaultHeaders },\n onMessage,\n messageId: codecMessageId,\n });\n\n const result = await pipeStream(stream, encoder, signal, onCancelled, streamOpts?.resolveWriteOptions, logger);\n\n if (result.error) {\n const errInfo = new Ably.ErrorInfo(\n `unable to pipe response for run ${runId}; ${result.error.message}`,\n ErrorCode.StreamError,\n 500,\n result.error instanceof Ably.ErrorInfo ? result.error : undefined,\n );\n logger?.error('Run.pipe(); stream error', { runId });\n runOnError?.(errInfo);\n }\n\n logger?.debug('Run.pipe(); stream finished', { runId, reason: result.reason });\n return result;\n },\n\n suspend: async (): Promise<void> => {\n logger?.trace('Run.suspend();', { runId });\n\n await requireConnected('suspend');\n\n if (state === RunState.INITIALIZED) {\n throw new Ably.ErrorInfo(\n `unable to suspend run; start() must be called before suspend() (run ${runId})`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n // ENDED is the terminal state for either an end or a suspend on this\n // Run instance; a second terminal call is a no-op.\n if (state === RunState.ENDED) return;\n state = RunState.ENDED;\n\n try {\n await runManager.suspendRun(runId, invocationId, resolvedInputClientId, resolvedInputCodecMessageId);\n } catch (error) {\n const errInfo = new Ably.ErrorInfo(\n `unable to publish run-suspend for run ${runId}; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.RunLifecycleError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n logger?.error('Run.suspend(); failed to publish run-suspend', { runId });\n throw errInfo;\n } finally {\n deregisterRun();\n }\n\n logger?.debug('Run.suspend(); run suspended', { runId });\n },\n\n // Spec: AIT-ST7, AIT-ST7a, AIT-ST7b\n end: async (reason: RunEndReason): Promise<void> => {\n logger?.trace('Run.end();', { runId, reason });\n\n await requireConnected('end');\n\n if (state === RunState.INITIALIZED) {\n throw new Ably.ErrorInfo(\n `unable to end run; start() must be called before end() (run ${runId})`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n if (state === RunState.ENDED) return;\n state = RunState.ENDED;\n\n try {\n await runManager.endRun(runId, reason, invocationId, resolvedInputClientId, resolvedInputCodecMessageId);\n } catch (error) {\n const errInfo = new Ably.ErrorInfo(\n `unable to publish run-end for run ${runId}; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.RunLifecycleError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n logger?.error('Run.end(); failed to publish run-end', { runId });\n throw errInfo;\n } finally {\n deregisterRun();\n }\n\n logger?.debug('Run.end(); run ended', { runId, reason });\n },\n };\n\n return run;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create an agent (server-side) session bound to the given Realtime client\n * and channel name. The caller owns the client's lifecycle; the session\n * owns its channel.\n * @param options - Session configuration.\n * @returns A new {@link AgentSession} instance.\n */\nexport const createAgentSession = <\n TInput extends CodecInputEvent,\n TOutput extends CodecOutputEvent,\n TProjection,\n TMessage,\n>(\n options: AgentSessionOptions<TInput, TOutput, TProjection, TMessage>,\n): AgentSession<TOutput, TProjection, TMessage> => new DefaultAgentSession(options);\n","/**\n * Type-safe EventEmitter wrapping Ably's internal EventEmitter.\n *\n * Takes a single `EventsMap` type parameter — an interface mapping event names\n * to payload types — rather than Ably's three type parameters. Adapted from\n * the ably-chat-js SDK.\n *\n * ```ts\n * interface MyEvents {\n * reaction: { emoji: string };\n * status: { online: boolean };\n * }\n *\n * const emitter = new EventEmitter<MyEvents>(logger);\n * emitter.on('reaction', (event) => console.log(event.emoji));\n * emitter.emit('reaction', { emoji: '👍' });\n * ```\n */\n\nimport * as Ably from 'ably';\n\nimport type { Logger } from './logger.js';\n\n/** Callback receiving a union of all possible event payloads. */\ntype Callback<EventsMap> = (arg: EventsMap[keyof EventsMap]) => void;\n\n/** Callback receiving the payload for a single event type. */\ntype CallbackSingle<K> = (arg: K) => void;\n\n/**\n * Type-safe interface for the Ably EventEmitter, parameterized by an EventsMap\n * that maps event names to their payload types.\n */\ninterface InterfaceEventEmitter<EventsMap> extends Ably.EventEmitter<Callback<EventsMap>, void, keyof EventsMap> {\n /** Emit an event with a type-safe payload. Payload is optional for `undefined`-typed events. */\n emit<K extends keyof EventsMap>(\n event: K,\n ...args: EventsMap[K] extends undefined ? [EventsMap[K]?] : [EventsMap[K]]\n ): void;\n\n /** Subscribe to a single event with a typed callback. */\n on<K extends keyof EventsMap>(event: K, callback: CallbackSingle<EventsMap[K]>): void;\n /** Subscribe to two events with a union-typed callback. */\n on<K1 extends keyof EventsMap, K2 extends keyof EventsMap>(\n events: [K1, K2],\n callback: CallbackSingle<EventsMap[K1] | EventsMap[K2]>,\n ): void;\n /** Subscribe to three events with a union-typed callback. */\n on<K1 extends keyof EventsMap, K2 extends keyof EventsMap, K3 extends keyof EventsMap>(\n events: [K1, K2, K3],\n callback: CallbackSingle<EventsMap[K1] | EventsMap[K2] | EventsMap[K3]>,\n ): void;\n /** Subscribe to an array of events. */\n on(events: (keyof EventsMap)[], callback: Callback<EventsMap>): void;\n /** Subscribe to all events. */\n on(callback: Callback<EventsMap>): void;\n\n /** Unsubscribe a callback from a specific event. */\n off<K extends keyof EventsMap>(event: K, listener: CallbackSingle<EventsMap[K]>): void;\n /** Unsubscribe a callback from all events, or remove all listeners if no callback provided. */\n off(listener?: Callback<EventsMap>): void;\n}\n\n/**\n * Bridge from our {@link Logger} to the Ably EventEmitter's internal logger\n * contract. Ably's EventEmitter calls `logger.logAction(level, action, message)`\n * when a listener throws — we route that to our Logger's `error` method.\n * @param logger - The application logger to delegate to.\n * @returns An object satisfying the Ably EventEmitter's logger interface.\n */\nconst toAblyLogger = (logger: Logger): unknown => ({\n logAction: (_level: number, action: string, message?: string) => {\n logger.error(action, { detail: message });\n },\n shouldLog: () => true,\n});\n\n// CAST: Access Ably's internal EventEmitter constructor. Not publicly exported\n// but available to other Ably SDKs. Ably always catches listener exceptions\n// internally; the logger parameter ensures those caught exceptions are logged\n// rather than silently swallowed.\nconst InternalEventEmitter: new <EventsMap>(logger: unknown) => InterfaceEventEmitter<EventsMap> = (\n Ably.Realtime as unknown as { EventEmitter: new <EventsMap>(logger: unknown) => InterfaceEventEmitter<EventsMap> }\n).EventEmitter;\n\n/**\n * Type-safe EventEmitter based on Ably's internal EventEmitter.\n *\n * Provides the same semantics as {@link Ably.EventEmitter} (error isolation\n * between listeners, synchronous dispatch) but with a single `EventsMap` type\n * parameter for ergonomic type safety.\n *\n * Requires a {@link Logger} so that listener exceptions are routed through\n * the application's logging infrastructure rather than silently swallowed.\n */\nexport class EventEmitter<EventsMap> extends InternalEventEmitter<EventsMap> {\n /**\n * Create a new EventEmitter.\n * @param logger - Application logger. Listener exceptions are logged at error level.\n */\n constructor(logger: Logger) {\n super(toAblyLogger(logger));\n }\n}\n","import * as Ably from 'ably';\n\nimport { ErrorCode } from './errors.js';\n\n/**\n * Structured logger with leveled output and hierarchical context.\n * Implementations filter messages by level and delegate to a {@link LogHandler}.\n */\nexport interface Logger {\n /**\n * Log a message at the trace level.\n * @param message The message to log.\n * @param context The context of the log message as key-value pairs.\n */\n trace(message: string, context?: LogContext): void;\n\n /**\n * Log a message at the debug level.\n * @param message The message to log.\n * @param context The context of the log message as key-value pairs.\n */\n debug(message: string, context?: LogContext): void;\n\n /**\n * Log a message at the info level.\n * @param message The message to log.\n * @param context The context of the log message as key-value pairs.\n */\n info(message: string, context?: LogContext): void;\n\n /**\n * Log a message at the warn level.\n * @param message The message to log.\n * @param context The context of the log message as key-value pairs.\n */\n warn(message: string, context?: LogContext): void;\n\n /**\n * Log a message at the error level.\n * @param message The message to log.\n * @param context The context of the log message as key-value pairs.\n */\n error(message: string, context?: LogContext): void;\n\n /**\n * Creates a new logger with a context that will be merged with any context provided to individual log calls.\n * The context will be overridden by any matching keys in the individual log call's context.\n * @param context The context to use for all log calls.\n * @returns A new logger instance with the context.\n */\n withContext(context: LogContext): Logger;\n}\n\n/**\n * Represents the different levels of logging that can be used.\n */\nexport enum LogLevel {\n /**\n * Something routine and expected has occurred. This level will provide logs for the vast majority of operations\n * and function calls.\n */\n Trace = 'trace',\n\n /**\n * Development information, messages that are useful when trying to debug library behavior,\n * but superfluous to normal operation.\n */\n Debug = 'debug',\n\n /**\n * Informational messages. Operationally significant to the library but not out of the ordinary.\n */\n Info = 'info',\n\n /**\n * Anything that is not immediately an error, but could cause unexpected behavior in the future. For example,\n * passing an invalid value to an option. Indicates that some action should be taken to prevent future errors.\n */\n Warn = 'warn',\n\n /**\n * A given operation has failed and cannot be automatically recovered. The error may threaten the continuity\n * of operation.\n */\n Error = 'error',\n\n /**\n * No logging will be performed.\n */\n Silent = 'silent',\n}\n\n/**\n * Represents the context of a log message.\n * It is an object of key-value pairs that can be used to provide additional context to a log message.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type LogContext = Record<string, any>;\n\n/**\n * A function that can be used to handle log messages.\n * @param message The message to log.\n * @param level The log level of the message.\n * @param context The context of the log message as key-value pairs.\n */\nexport type LogHandler = (message: string, level: LogLevel, context?: LogContext) => void;\n\n/**\n * A simple console logger that logs messages to the console.\n * @param message The message to log.\n * @param level The log level of the message.\n * @param context - The context of the log message as key-value pairs.\n */\nexport const consoleLogger = (message: string, level: LogLevel, context?: LogContext) => {\n const contextString = context ? `, context: ${JSON.stringify(context)}` : '';\n const formattedMessage = `[${new Date().toISOString()}] ${level.valueOf().toUpperCase()} ably-ai-transport: ${message}${contextString}`;\n\n switch (level) {\n case LogLevel.Trace:\n case LogLevel.Debug: {\n console.log(formattedMessage);\n break;\n }\n case LogLevel.Info: {\n console.info(formattedMessage);\n break;\n }\n case LogLevel.Warn: {\n console.warn(formattedMessage);\n break;\n }\n case LogLevel.Error: {\n console.error(formattedMessage);\n break;\n }\n case LogLevel.Silent: {\n break;\n }\n }\n};\n\n/**\n * Options for creating a logger.\n */\nexport interface LoggerOptions {\n /**\n * The handler that receives formatted log messages. Defaults to {@link consoleLogger} when omitted.\n */\n logHandler?: LogHandler;\n /**\n * The minimum level to emit; messages below this level are suppressed. Must be a valid {@link LogLevel}, otherwise logger creation throws.\n */\n logLevel: LogLevel;\n}\n\n/**\n * Creates a {@link Logger} from the given options.\n * @param options The handler and minimum level for the logger.\n * @returns A logger that filters by level and delegates to the handler.\n * @throws {@link Ably.ErrorInfo} with {@link ErrorCode.InvalidArgument} if `options.logLevel` is not a recognised {@link LogLevel}.\n */\nexport const makeLogger = (options: LoggerOptions): Logger => {\n const logHandler = options.logHandler ?? consoleLogger;\n\n return new DefaultLogger(logHandler, options.logLevel);\n};\n\n/**\n * A convenient list of log levels as numbers that can be used for easier comparison.\n */\nenum LogLevelNumber {\n Trace = 0,\n Debug = 1,\n Info = 2,\n Warn = 3,\n Error = 4,\n Silent = 5,\n}\n\n/**\n * A mapping of log levels to their numeric equivalents.\n */\nconst logLevelNumberMap = new Map<LogLevel, LogLevelNumber>([\n [LogLevel.Trace, LogLevelNumber.Trace],\n [LogLevel.Debug, LogLevelNumber.Debug],\n [LogLevel.Info, LogLevelNumber.Info],\n [LogLevel.Warn, LogLevelNumber.Warn],\n [LogLevel.Error, LogLevelNumber.Error],\n [LogLevel.Silent, LogLevelNumber.Silent],\n]);\n\n/**\n * A default logger implementation.\n */\nclass DefaultLogger implements Logger {\n private readonly _handler: LogHandler;\n private readonly _levelNumber: LogLevelNumber;\n private readonly _context?: LogContext;\n\n constructor(handler: LogHandler, level: LogLevel, context?: LogContext) {\n this._handler = handler;\n this._context = context;\n\n const levelNumber = logLevelNumberMap.get(level);\n if (levelNumber === undefined) {\n throw new Ably.ErrorInfo(`unable to create logger; invalid log level: ${level}`, ErrorCode.InvalidArgument, 400);\n }\n\n this._levelNumber = levelNumber;\n }\n\n trace(message: string, context?: LogContext): void {\n this._write(message, LogLevel.Trace, LogLevelNumber.Trace, context);\n }\n\n debug(message: string, context?: LogContext): void {\n this._write(message, LogLevel.Debug, LogLevelNumber.Debug, context);\n }\n\n info(message: string, context?: LogContext): void {\n this._write(message, LogLevel.Info, LogLevelNumber.Info, context);\n }\n\n warn(message: string, context?: LogContext): void {\n this._write(message, LogLevel.Warn, LogLevelNumber.Warn, context);\n }\n\n error(message: string, context?: LogContext): void {\n this._write(message, LogLevel.Error, LogLevelNumber.Error, context);\n }\n\n withContext(context: LogContext): Logger {\n // Get the original log level by finding the key in logLevelNumberMap that matches this._levelNumber.\n // The Error fallback is defensive and unreachable in practice: _levelNumber always originates from the map.\n const originalLevel =\n [...logLevelNumberMap.entries()].find(([, value]) => value === this._levelNumber)?.[0] ?? LogLevel.Error;\n\n return new DefaultLogger(this._handler, originalLevel, this._mergeContext(context));\n }\n\n private _write(message: string, level: LogLevel, levelNumber: LogLevelNumber, context?: LogContext): void {\n if (levelNumber >= this._levelNumber) {\n this._handler(message, level, this._mergeContext(context));\n }\n }\n\n private _mergeContext(context?: LogContext): LogContext | undefined {\n if (!this._context) {\n return context ?? undefined;\n }\n\n return context ? { ...this._context, ...context } : this._context;\n }\n}\n","/**\n * Tree — materializes a branching conversation as a forest of nodes. Each turn\n * is two nodes: a user {@link InputNode} keyed by its client-owned\n * codec-message-id and an agent {@link RunNode} keyed by the agent-minted\n * run-id, parented to the input node.\n *\n * Each node holds a per-node codec {@link TProjection} which the Tree folds\n * from inbound events. The Tree owns the complete conversation state across\n * every observed node. The {@link View} walks the parent chain to extract a\n * flat message list for rendering.\n *\n * `applyMessage()` is the entry point for inbound channel messages — it\n * classifies a run-less user input into an input node (keyed by\n * codec-message-id) or routes a run-bearing wire to its reply run (keyed by\n * run-id), folds events into that node's projection, and maintains a secondary\n * `codecMessageId -> nodeKey` index. `applyRunLifecycle()` handles run-start /\n * run-suspend / run-resume / run-end events.\n *\n * Sibling structure: editing a prompt produces a sibling input node linked by\n * {@link InputNode.forkOf}; regenerating a reply produces a sibling reply run\n * sharing the same input-node parent (no fork-of).\n */\n\nimport type * as Ably from 'ably';\n\nimport {\n HEADER_CODEC_MESSAGE_ID,\n HEADER_FORK_OF,\n HEADER_INPUT_CODEC_MESSAGE_ID,\n HEADER_INVOCATION_ID,\n HEADER_MSG_REGENERATE,\n HEADER_PARENT,\n HEADER_ROLE,\n HEADER_RUN_CLIENT_ID,\n HEADER_RUN_ID,\n} from '../../constants.js';\nimport { EventEmitter } from '../../event-emitter.js';\nimport type { Logger } from '../../logger.js';\nimport type { CodecInputEvent, CodecOutputEvent, Reducer } from '../codec/types.js';\nimport type { ConversationNode, InputNode, OutputEvent, RunLifecycleEvent, RunNode, Tree } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Internal node type\n// ---------------------------------------------------------------------------\n\ninterface InternalNode<TProjection> {\n node: ConversationNode<TProjection>;\n /** Insertion sequence — tiebreaker for nodes with no sort serial (optimistic). */\n insertSeq: number;\n}\n\n/**\n * The primary key a node is indexed under: a reply run's `runId`, or an input\n * node's `codecMessageId` (the client owns it before the agent mints a runId).\n * @param node - The node to key.\n * @returns The node's primary key.\n */\nexport const nodeKey = <TProjection>(node: ConversationNode<TProjection>): string =>\n node.kind === 'run' ? node.runId : node.codecMessageId;\n\n/**\n * The serial a node sorts by: a reply run's `startSerial`, an input node's\n * `serial`. Undefined for an optimistic (not-yet-acked) node, which tail-sorts.\n * @param node - The node to read.\n * @returns The sort serial, or undefined for an optimistic node.\n */\nconst sortSerial = <TProjection>(node: ConversationNode<TProjection>): string | undefined =>\n node.kind === 'run' ? node.startSerial : node.serial;\n\n/**\n * Add a value to a `Map<K, Set<V>>`, creating the bucket Set on first use.\n * @param map - The Map to mutate.\n * @param key - The bucket key.\n * @param value - The value to add.\n */\nconst addToSetMap = <K, V>(map: Map<K, Set<V>>, key: K, value: V): void => {\n let set = map.get(key);\n if (!set) {\n set = new Set();\n map.set(key, set);\n }\n set.add(value);\n};\n\n/**\n * Remove a value from a `Map<K, Set<V>>`, dropping the bucket when it empties.\n * @param map - The Map to mutate.\n * @param key - The bucket key.\n * @param value - The value to remove.\n */\nconst deleteFromSetMap = <K, V>(map: Map<K, Set<V>>, key: K, value: V): void => {\n const set = map.get(key);\n if (!set) return;\n set.delete(value);\n if (set.size === 0) map.delete(key);\n};\n\n// ---------------------------------------------------------------------------\n// Internal interface — extended surface consumed by View / ClientSession\n// ---------------------------------------------------------------------------\n\n/** Internal tree surface used by View and ClientSession — not part of the public Tree API. */\nexport interface TreeInternal<\n TInput extends CodecInputEvent,\n TOutput extends CodecOutputEvent,\n TProjection,\n> extends Tree<TOutput, TProjection> {\n /**\n * Walk the visible node chain (both input nodes and reply runs) along the\n * selected branches, in chronological order. The View renders from this.\n * @param selections - Per-group selected member key, keyed by group root.\n * @returns The visible nodes in chronological order.\n */\n visibleNodes(selections?: Map<string, string>): ConversationNode<TProjection>[];\n\n /**\n * Get the \"group root\" key for a sibling group — the stable key the\n * selection map is keyed by (the earliest edit version for input nodes, the\n * original reply for a regenerate group).\n */\n getGroupRoot(key: string): string;\n\n /**\n * The reply runs parented at an input node (its codec-message-id), in\n * iteration order. Empty when none have been observed. Used to resolve a\n * user prompt to its reply run(s).\n * @param inputCodecMessageId - The input node's codec-message-id.\n * @returns The reply runs parented at that input.\n */\n getReplyRuns(inputCodecMessageId: string): RunNode<TProjection>[];\n\n /**\n * Apply an inbound channel message to the tree.\n *\n * Classifies the message and routes it to the owning node:\n * 1. Run-less user input (no run-id, a `user`-role message carrying a\n * codec-message-id and input events): creates or promotes the input node\n * keyed by that codec-message-id, folds the input events.\n * 2. Run-bearing wire (assistant output, continuation tool-resolution, or a\n * fresh agent-minted run): routes to the reply run by run-id (reconciling\n * an optimistic insert by codec-message-id), folds events.\n * @param events - Decoded codec events, split by wire direction. Both are\n * folded into the node's projection, inputs first.\n * @param events.inputs - Client-published events (`ai-input` wire).\n * @param events.outputs - Agent-published events (`ai-output` wire).\n * @param headers - Transport headers from the inbound Ably message.\n * @param serial - Ably channel serial; undefined for optimistic inserts.\n */\n applyMessage(\n events: { inputs: TInput[]; outputs: TOutput[] },\n headers: Record<string, string>,\n serial?: string,\n ): void;\n\n /**\n * Apply a run-lifecycle event.\n *\n * - `start`: creates the reply run (if missing) or, for an existing run,\n * sets RunNode.status to 'active', promotes startSerial, and backfills\n * structural metadata (parent / forkOf / regenerates / invocationId).\n * - `suspend`: sets RunNode.status to 'suspended' and records `endSerial`.\n * The run stays live so a resume under the same `runId` picks up where it\n * left off.\n * - `resume`: re-activates an existing suspended Run (status back to\n * 'active') without touching its structure or serials — a pure re-entry\n * signal. A no-op if the Run is not yet known.\n * - `end`: sets RunNode.status to the terminal reason and records\n * `endSerial`.\n *\n * Always emits a 'run' event to subscribers.\n * @param event - Lifecycle event payload, including the channel serial.\n */\n applyRunLifecycle(event: RunLifecycleEvent): void;\n\n /**\n * Get the node keyed by `key`, or undefined if `key` names no node. The\n * key is a {@link nodeKey} — a runId (reply run) or an input node's\n * codec-message-id — so the result is a {@link ConversationNode} union:\n * narrow on `kind` before reading kind-specific fields. Pairs with\n * {@link getNodeByCodecMessageId}, which resolves an arbitrary owned\n * codec-message-id (including an assistant message's) to its node.\n * @param key - The node key to look up.\n * @returns The node, or undefined if not found.\n */\n getNode(key: string): ConversationNode<TProjection> | undefined;\n\n /**\n * Remove a node from the tree by its key ({@link nodeKey} — a runId or an\n * input node's codec-message-id). Children become unreachable because their\n * parent is no longer on the active path.\n * @param key - The node key to remove.\n */\n delete(key: string): void;\n\n /** Forward a raw Ably message event to tree subscribers. */\n emitAblyMessage(msg: Ably.InboundMessage): void;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/** EventEmitter events map for the tree. */\ninterface TreeEventsMap<TOutput extends CodecOutputEvent> {\n update: undefined;\n 'ably-message': Ably.InboundMessage;\n run: RunLifecycleEvent;\n output: OutputEvent<TOutput>;\n}\n\n// Spec: AIT-CT13\nexport class DefaultTree<\n TInput extends CodecInputEvent,\n TOutput extends CodecOutputEvent,\n TProjection,\n> implements TreeInternal<TInput, TOutput, TProjection> {\n private readonly _codec: Reducer<TInput | TOutput, TProjection>;\n private readonly _logger: Logger;\n private readonly _emitter: EventEmitter<TreeEventsMap<TOutput>>;\n\n /**\n * All nodes indexed by their primary key ({@link nodeKey}): a reply run's\n * runId, or an input node's codec-message-id.\n */\n private readonly _nodeIndex = new Map<string, InternalNode<TProjection>>();\n\n /**\n * Maps every observed `codec-message-id` to its owning node's key\n * ({@link nodeKey}). For a reply run that is the runId of every message the\n * run published; for an input node it is the input's own codec-message-id.\n * Resolves fork-of / parent codec-message-ids to node keys, routes\n * continuation amend wires to existing nodes, and backs UI lookups that hold\n * a codec-message-id.\n */\n private readonly _codecMessageIdToNodeKey = new Map<string, string>();\n\n /**\n * All nodes sorted by their sort serial ({@link sortSerial}: `startSerial`\n * for runs, `serial` for input nodes), lexicographically. Nodes with no sort\n * serial (optimistic) sort after all serial-bearing nodes, ordered among\n * themselves by insertion sequence.\n */\n private readonly _sortedNodes: InternalNode<TProjection>[] = [];\n\n /**\n * Parent index: parent node key (the key its children's\n * `parentCodecMessageId` resolves to) to the set of child node keys. Root\n * nodes (no parent) are indexed under the key `undefined`. Kind-blind — a\n * reply run and an input node parent off each other through the same index.\n */\n private readonly _parentIndex = new Map<string | undefined, Set<string>>();\n\n /**\n * Reverse edge: an input node's codec-message-id to the set of reply-run ids\n * parented at it. Lets the View resolve a user prompt to its (selected) reply\n * run, and groups regenerate siblings (which all parent at the same input\n * node).\n */\n private readonly _replyRunsByInput = new Map<string, Set<string>>();\n\n /** Monotonically increasing counter for insertion sequence. */\n private _seqCounter = 0;\n\n /** Incremented on structural changes; unchanged on projection-only updates. */\n private _structuralVersion = 0;\n\n /**\n * Cached sibling-group lookups keyed by node key. The walk over forkOf\n * chains and the per-parent fan-out are pure functions of the node\n * graph, so the cache is keyed against {@link _structuralVersion}:\n * any topology mutation drops the cache and the next lookup\n * recomputes. Hits matter most during a single render pass where\n * the View calls `getSiblingNodes` once per visible node plus extra\n * per-message branch-anchor probes from React components.\n */\n private _siblingCache = new Map<string, InternalNode<TProjection>[]>();\n private _siblingCacheVersion = -1;\n\n constructor(codec: Reducer<TInput | TOutput, TProjection>, logger: Logger) {\n this._codec = codec;\n this._logger = logger;\n this._emitter = new EventEmitter<TreeEventsMap<TOutput>>(logger);\n }\n\n // -------------------------------------------------------------------------\n // Sorted list maintenance\n // -------------------------------------------------------------------------\n\n /**\n * Compare two nodes (Run or input) for sorted list ordering.\n * Serial-bearing nodes sort by their sort serial (`startSerial` for runs,\n * `serial` for input nodes), lexicographically.\n * Nodes with no sort serial sort after all serial-bearing nodes.\n * Among them, sort by insertion sequence.\n *\n * Optimistic (null-serial) nodes intentionally tail-sort so they reorder\n * into place when the server relay arrives and `applyMessage` promotes\n * startSerial — see {@link applyMessage}'s `_removeSortedNode` /\n * `_insertSortedNode` pair on the promotion path.\n * @param a - First node to compare.\n * @param b - Second node to compare.\n * @returns Negative if a sorts before b, positive if after, zero if equal.\n */\n // Spec: AIT-CT13a\n private _compareNodes(a: InternalNode<TProjection>, b: InternalNode<TProjection>): number {\n const sa = sortSerial(a.node);\n const sb = sortSerial(b.node);\n if (sa === undefined && sb === undefined) return a.insertSeq - b.insertSeq;\n if (sa === undefined) return 1;\n if (sb === undefined) return -1;\n if (sa < sb) return -1;\n if (sa > sb) return 1;\n return a.insertSeq - b.insertSeq;\n }\n\n /**\n * Insert a node into the sorted list at the correct position via binary search.\n * @param internal - The node to insert.\n */\n private _insertSortedNode(internal: InternalNode<TProjection>): void {\n const startSerial = sortSerial(internal.node);\n\n // Fast path: null-startSerial always appends to end.\n if (startSerial === undefined) {\n this._sortedNodes.push(internal);\n return;\n }\n\n let lo = 0;\n let hi = this._sortedNodes.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n const midNode = this._sortedNodes[mid];\n if (!midNode) break; // unreachable\n if (this._compareNodes(midNode, internal) <= 0) {\n lo = mid + 1;\n } else {\n hi = mid;\n }\n }\n this._sortedNodes.splice(lo, 0, internal);\n }\n\n /**\n * Remove a node from the sorted list.\n * @param internal - The node to remove.\n */\n private _removeSortedNode(internal: InternalNode<TProjection>): void {\n const idx = this._sortedNodes.indexOf(internal);\n if (idx !== -1) this._sortedNodes.splice(idx, 1);\n }\n\n /**\n * Insert a freshly-created node into the primary store, the parent index, and\n * the sorted list, then bump the structural version. Kind-specific secondary\n * indexing — the codec-message-id map for input nodes, the reply→input edge\n * for reply runs — is the caller's responsibility.\n * @param key - The node's primary key ({@link nodeKey}).\n * @param entry - The internal node to insert.\n * @param parentCodecMessageId - The node's structural parent, or undefined for a root.\n */\n private _insertNode(key: string, entry: InternalNode<TProjection>, parentCodecMessageId: string | undefined): void {\n this._nodeIndex.set(key, entry);\n this._addToParentIndex(parentCodecMessageId, key);\n this._insertSortedNode(entry);\n this._structuralVersion++;\n }\n\n /**\n * Re-sort a node whose sort key just changed and bump the structural version.\n * The caller mutates the serial field (`serial` for input nodes, `startSerial`\n * for runs); this keeps the sorted list and version in step. Used on the\n * optimistic-serial promotion paths when the server relay/echo arrives.\n * @param entry - The internal node whose serial was just promoted.\n */\n private _promoteSerial(entry: InternalNode<TProjection>): void {\n this._removeSortedNode(entry);\n this._insertSortedNode(entry);\n this._structuralVersion++;\n }\n\n /**\n * Fold a batch of events into a node's projection in place, isolating each\n * fold in a try/catch so a throwing reducer can't abort the rest of the batch\n * or the surrounding apply.\n * @param entry - The internal node whose projection is folded in place.\n * @param events - The decoded events to fold, in wire order.\n * @param serial - Ably channel serial; coerced to '' for an optimistic insert.\n * @param messageId - The reducer routing key (codec-message-id), or undefined.\n */\n private _foldInto(\n entry: InternalNode<TProjection>,\n events: (TInput | TOutput)[],\n serial: string | undefined,\n messageId: string | undefined,\n ): void {\n for (const event of events) {\n try {\n entry.node.projection = this._codec.fold(entry.node.projection, event, { serial: serial ?? '', messageId });\n } catch (error) {\n this._logger.error('Tree._foldInto(); fold threw', { key: nodeKey(entry.node), messageId, err: error });\n }\n }\n }\n\n // -------------------------------------------------------------------------\n // Parent index maintenance\n // -------------------------------------------------------------------------\n\n private _addToParentIndex(parentNodeKey: string | undefined, childKey: string): void {\n addToSetMap(this._parentIndex, parentNodeKey, childKey);\n }\n\n private _removeFromParentIndex(parentNodeKey: string | undefined, childKey: string): void {\n deleteFromSetMap(this._parentIndex, parentNodeKey, childKey);\n }\n\n /**\n * Resolve a node's structural parent to the parent node's key\n * ({@link nodeKey}), or undefined for a root. The parent is named by a\n * codec-message-id (`parentCodecMessageId`); this maps it through the\n * codec-message-id index to the owning node's key (a runId for a reply run,\n * a codec-message-id for an input node). Returns undefined when the parent\n * hasn't been observed yet (the node is treated as a root until it arrives).\n * @param node - The node whose parent to resolve.\n * @returns The parent node's key, or undefined.\n */\n private _parentKeyOf(node: ConversationNode<TProjection>): string | undefined {\n const parentCodecMessageId = node.parentCodecMessageId;\n return parentCodecMessageId === undefined ? undefined : this._codecMessageIdToNodeKey.get(parentCodecMessageId);\n }\n\n // -------------------------------------------------------------------------\n // Sibling grouping\n // -------------------------------------------------------------------------\n\n /**\n * Walk an input node's `forkOf` chain to the group root — the earliest edit\n * version sharing the same structural parent. Stops at a missing target, a\n * non-input target, a parent mismatch, or a cycle.\n * @param node - The input node to walk from.\n * @returns The group-root input node (the node itself when it is the root).\n */\n private _inputGroupRoot(node: InputNode<TProjection>): InputNode<TProjection> {\n let current = node;\n const visited = new Set<string>([nodeKey(current)]);\n while (current.forkOf !== undefined) {\n if (visited.has(current.forkOf)) break;\n const forkTarget = this._nodeIndex.get(current.forkOf);\n if (forkTarget?.node.kind !== 'input' || forkTarget.node.parentCodecMessageId !== current.parentCodecMessageId) {\n break;\n }\n current = forkTarget.node;\n visited.add(nodeKey(current));\n }\n return current;\n }\n\n /**\n * Get the sibling group that the node keyed by `key` belongs to. Kind-split:\n *\n * - **Reply runs** — every reply run sharing the same input-node parent is a\n * sibling (the original reply + its regenerators all parent at the same\n * input node M_user). No fork-of involved.\n * - **Input nodes** — edit versions: nodes sharing a parent AND linked by a\n * `forkOf` chain to the group root.\n *\n * Returned ordered by startSerial (original/oldest first). A group of one is\n * returned as a single-element array (no branching).\n * @param key - The node key ({@link nodeKey}) to look up the group for.\n * @returns The ordered list of sibling nodes.\n */\n // Spec: AIT-CT13b\n private _getSiblingGroup(key: string): InternalNode<TProjection>[] {\n if (this._siblingCacheVersion !== this._structuralVersion) {\n this._siblingCache.clear();\n this._siblingCacheVersion = this._structuralVersion;\n }\n const cached = this._siblingCache.get(key);\n if (cached) return cached;\n\n const entry = this._nodeIndex.get(key);\n if (!entry) return [];\n\n // The \"original\" anchors the group's parent + kind. For an input node,\n // walk the forkOf chain to the earliest version sharing the parent; for a\n // reply run the node itself anchors (all same-parent runs are siblings).\n let original = entry.node;\n if (original.kind === 'input') {\n original = this._inputGroupRoot(original);\n }\n\n // `_parentIndex` is keyed by the raw structural `parentCodecMessageId` (not\n // the resolved parent node key) so a run observed before its input node\n // still files/groups correctly — the parent codec-message-id is known at\n // creation, the resolved key may not be.\n const parentKey = original.parentCodecMessageId;\n const siblings: InternalNode<TProjection>[] = [];\n const candidateKeys = this._parentIndex.get(parentKey);\n if (candidateKeys) {\n for (const childKey of candidateKeys) {\n const childEntry = this._nodeIndex.get(childKey);\n if (childEntry && this._isSiblingOf(childEntry.node, original)) {\n siblings.push(childEntry);\n }\n }\n }\n\n siblings.sort((a, b) => this._compareNodes(a, b));\n // Cache against the queried key AND every member of the group: a single\n // group is the same array regardless of which member triggered the lookup,\n // so subsequent queries against any member hit without recomputing.\n for (const sib of siblings) {\n this._siblingCache.set(nodeKey(sib.node), siblings);\n }\n this._siblingCache.set(key, siblings);\n return siblings;\n }\n\n /**\n * Whether `node` belongs to the sibling group anchored at `original`.\n * Requires the same kind and the same structural parent; reply runs need\n * nothing more (same-parent runs are regenerate siblings), input nodes must\n * additionally be forkOf-linked to the original (edit versions).\n * @param node - The candidate node.\n * @param original - The group's anchor node.\n * @returns True if `node` is a sibling of `original`.\n */\n private _isSiblingOf(node: ConversationNode<TProjection>, original: ConversationNode<TProjection>): boolean {\n if (node.kind !== original.kind) return false;\n if (node.parentCodecMessageId !== original.parentCodecMessageId) return false;\n // Same-parent reply runs are regenerate siblings — no fork-of needed.\n if (node.kind === 'run') return true;\n // Input nodes: must be forkOf-linked to the original (edit versions).\n const originalKey = nodeKey(original);\n if (nodeKey(node) === originalKey) return true;\n let current: ConversationNode<TProjection> = node;\n const visited = new Set<string>([nodeKey(current)]);\n while (current.kind === 'input' && current.forkOf !== undefined) {\n if (current.forkOf === originalKey) return true;\n if (visited.has(current.forkOf)) break;\n const target = this._nodeIndex.get(current.forkOf);\n if (!target) break;\n current = target.node;\n visited.add(nodeKey(current));\n }\n return false;\n }\n\n /**\n * Get the \"group root\" key for a sibling group — the stable key the\n * selection map is keyed by. For an input node (edit versions) that is the\n * earliest fork-of ancestor; for a reply run (regenerate group) it is the\n * oldest same-parent run (the original reply).\n * @param key - Any node key in the sibling group.\n * @returns The group root's key.\n */\n getGroupRoot(key: string): string {\n const entry = this._nodeIndex.get(key);\n if (!entry) return key;\n\n if (entry.node.kind === 'input') {\n return nodeKey(this._inputGroupRoot(entry.node));\n }\n\n // Reply run: the oldest same-parent run is the original reply.\n const group = this._getSiblingGroup(key);\n const root = group[0]?.node;\n return root ? nodeKey(root) : key;\n }\n\n // -------------------------------------------------------------------------\n // Public query methods\n // -------------------------------------------------------------------------\n\n /**\n * Walk the visible node chain along the selected branches, kind-blind. An\n * input node and a reply run reach each other through the same\n * parent-membership check, so seed-only user→user chains and the\n * input→reply→input weave both resolve here. Sibling groups (edit versions /\n * regenerate runs) collapse to the selected member.\n * @param selections - Per-group selected member key, keyed by group root.\n * @returns The visible nodes (both kinds) in chronological order.\n */\n visibleNodes(selections: Map<string, string> = new Map<string, string>()): ConversationNode<TProjection>[] {\n this._logger.trace('DefaultTree.visibleNodes();');\n const result: ConversationNode<TProjection>[] = [];\n const currentPath = new Set<string>();\n const resolvedGroups = new Map<string, string>(); // groupRootKey -> selected key\n\n for (const internal of this._sortedNodes) {\n const node = internal.node;\n const key = nodeKey(node);\n\n // Step 1: Parent reachability (kind-blind — the parent may be an input\n // node or a reply run; resolve its key and check the active path).\n const parentKey = this._parentKeyOf(node);\n if (parentKey !== undefined && !currentPath.has(parentKey)) {\n continue;\n }\n\n // Step 2: Sibling selection.\n const group = this._getSiblingGroup(key);\n if (group.length > 1) {\n const groupRootKey = this.getGroupRoot(key);\n let selectedKey = resolvedGroups.get(groupRootKey);\n if (selectedKey === undefined) {\n const preferredKey = selections.get(groupRootKey);\n if (preferredKey !== undefined && group.some((n) => nodeKey(n.node) === preferredKey)) {\n selectedKey = preferredKey;\n } else {\n const latest = group.at(-1);\n if (!latest) break; // unreachable: group.length > 1\n selectedKey = nodeKey(latest.node);\n }\n resolvedGroups.set(groupRootKey, selectedKey);\n }\n if (key !== selectedKey) {\n continue;\n }\n }\n\n currentPath.add(key);\n result.push(node);\n }\n\n return result;\n }\n\n getRunNode(runId: string): RunNode<TProjection> | undefined {\n this._logger.trace('DefaultTree.getRunNode();', { runId });\n const node = this._nodeIndex.get(runId)?.node;\n return node?.kind === 'run' ? node : undefined;\n }\n\n getNode(key: string): ConversationNode<TProjection> | undefined {\n this._logger.trace('DefaultTree.getNode();', { key });\n return this._nodeIndex.get(key)?.node;\n }\n\n getNodeByCodecMessageId(codecMessageId: string): ConversationNode<TProjection> | undefined {\n this._logger.trace('DefaultTree.getNodeByCodecMessageId();', { codecMessageId });\n const key = this._codecMessageIdToNodeKey.get(codecMessageId);\n return key === undefined ? undefined : this._nodeIndex.get(key)?.node;\n }\n\n getReplyRuns(inputCodecMessageId: string): RunNode<TProjection>[] {\n const runIds = this._replyRunsByInput.get(inputCodecMessageId);\n if (!runIds) return [];\n const result: RunNode<TProjection>[] = [];\n for (const runId of runIds) {\n const node = this._nodeIndex.get(runId)?.node;\n if (node?.kind === 'run') result.push(node);\n }\n return result;\n }\n\n getSiblingNodes(key: string): ConversationNode<TProjection>[] {\n this._logger.trace('DefaultTree.getSiblingNodes();', { key });\n return this._getSiblingGroup(key).map((n) => n.node);\n }\n\n // -------------------------------------------------------------------------\n // Mutation\n // -------------------------------------------------------------------------\n\n applyMessage(\n events: { inputs: TInput[]; outputs: TOutput[] },\n headers: Record<string, string>,\n serial?: string,\n ): void {\n const wireRunId = headers[HEADER_RUN_ID];\n const codecMessageId = headers[HEADER_CODEC_MESSAGE_ID];\n\n // Classify: with NO run-id, a user message carrying a codec-message-id and\n // at least one input event forms an INPUT node keyed by that\n // codec-message-id — the client owns it; the agent mints the reply run-id\n // separately. Everything else needs a run-id to route to a reply run.\n // Capturing the id (not a boolean) narrows it to `string` for the input path.\n const inputNodeCodecMessageId =\n wireRunId === undefined &&\n codecMessageId !== undefined &&\n headers[HEADER_ROLE] === 'user' &&\n events.inputs.length > 0\n ? codecMessageId\n : undefined;\n\n if (wireRunId === undefined && inputNodeCodecMessageId === undefined) {\n this._logger.warn('Tree.applyMessage(); message has no run-id and is not a user input; skipping');\n return;\n }\n\n // Fold inputs first, then outputs, preserving wire order.\n const all: (TInput | TOutput)[] = [...events.inputs, ...events.outputs];\n\n // Wire-only metadata-carrier messages (e.g. `ait-regenerate`) decode to\n // zero events and don't need a node at the tree level — the eventual reply\n // run is created later by run-start, and any regenerate / parent\n // information the wire carried is reread from the run-start headers.\n // Skipping here avoids a phantom node that would inflate sibling counts.\n const existingKey = inputNodeCodecMessageId ?? wireRunId;\n if (all.length === 0 && existingKey !== undefined && !this._nodeIndex.has(existingKey)) {\n return;\n }\n\n // `update` is the structural channel: emit it only when this apply\n // actually changes the tree shape (new node, startSerial promotion).\n // Content-only folds (streaming chunks into an existing node) flow through\n // `output` instead, so they leave `_structuralVersion` untouched.\n const structuralBefore = this._structuralVersion;\n\n if (inputNodeCodecMessageId !== undefined) {\n this._applyInputMessage(inputNodeCodecMessageId, headers, serial, all);\n } else if (wireRunId !== undefined) {\n this._applyRunMessage(wireRunId, events, headers, serial);\n }\n\n if (this._structuralVersion !== structuralBefore) this._emitter.emit('update');\n }\n\n /**\n * Apply a run-less user input wire: create (or promote the serial of) the\n * input node keyed by its codec-message-id, fold the input events into its\n * own projection, and emit an `output` event (with empty outputs — input\n * folds carry none) so the View observes the optimistic insert.\n * @param codecMessageId - The input node's codec-message-id (its primary key).\n * @param headers - Transport headers from the inbound Ably message.\n * @param serial - Ably channel serial; undefined for an optimistic insert.\n * @param all - The decoded input events to fold, in wire order.\n */\n private _applyInputMessage(\n codecMessageId: string,\n headers: Record<string, string>,\n serial: string | undefined,\n all: (TInput | TOutput)[],\n ): void {\n let entry = this._nodeIndex.get(codecMessageId);\n if (!entry) {\n entry = this._createInputNodeFromHeaders(codecMessageId, headers, serial);\n this._insertNode(codecMessageId, entry, entry.node.parentCodecMessageId);\n this._codecMessageIdToNodeKey.set(codecMessageId, codecMessageId);\n this._logger.debug('Tree.applyMessage(); created input node', { codecMessageId });\n } else if (entry.node.kind === 'input' && serial && !entry.node.serial) {\n // Promote optimistic serial when the relay/echo arrives.\n this._logger.debug('Tree.applyMessage(); promoting input serial', { codecMessageId, serial });\n entry.node.serial = serial;\n this._promoteSerial(entry);\n }\n\n this._foldInto(entry, all, serial, codecMessageId);\n\n // An input node owns no agent outputs; the event still fires (empty\n // outputs) so consumers observe the projection change. It has no run-id —\n // the causal routing key is the input's own codec-message-id.\n this._emitter.emit('output', {\n runId: undefined,\n inputCodecMessageId: codecMessageId,\n codecMessageId,\n serial,\n events: [],\n });\n }\n\n /**\n * Apply a reply-run wire (assistant output, continuation tool-resolution, or\n * a fresh run keyed by the agent-minted run-id): create or reconcile the run\n * node, fold its events, maintain the codec-message-id and reply→input\n * indices, and emit the `output` event. Derives the codec-message-id,\n * triggering-input id, fold list, and outputs from `events`/`headers`,\n * mirroring `applyMessage`.\n * @param wireRunId - The run-id from the inbound wire (the node's primary key).\n * @param events - The decoded inputs and outputs from the wire.\n * @param events.inputs - Client-published events (`ai-input` wire).\n * @param events.outputs - Agent-published events (`ai-output` wire).\n * @param headers - Transport headers from the inbound Ably message.\n * @param serial - Ably channel serial; undefined for an optimistic insert.\n */\n private _applyRunMessage(\n wireRunId: string,\n events: { inputs: TInput[]; outputs: TOutput[] },\n headers: Record<string, string>,\n serial: string | undefined,\n ): void {\n const codecMessageId = headers[HEADER_CODEC_MESSAGE_ID];\n // The triggering input's codec-message-id (the agent's echo), surfaced on\n // the `output` event as the stream's causal routing key.\n const inputCodecMessageId = headers[HEADER_INPUT_CODEC_MESSAGE_ID];\n // Fold inputs first, then outputs, preserving wire order.\n const all: (TInput | TOutput)[] = [...events.inputs, ...events.outputs];\n const outputs = events.outputs;\n\n let run = this._nodeIndex.get(wireRunId);\n\n // Reconcile an optimistic insert with its serial-bearing echo by\n // codec-message-id rather than the wire run-id — covers assistant content\n // that pins a codec-message-id before its run-id is indexed.\n if (!run && codecMessageId !== undefined) {\n const indexedKey = this._codecMessageIdToNodeKey.get(codecMessageId);\n const indexed = indexedKey === undefined ? undefined : this._nodeIndex.get(indexedKey);\n if (indexed?.node.kind === 'run' && indexed.node.startSerial === undefined) run = indexed;\n }\n\n if (!run) {\n run = this._createRunFromHeaders(wireRunId, headers, serial);\n this._insertNode(wireRunId, run, run.node.parentCodecMessageId);\n this._indexReplyRun(run.node, wireRunId);\n this._logger.debug('Tree.applyMessage(); created new Run', { runId: wireRunId });\n } else if (serial && run.node.kind === 'run' && !run.node.startSerial) {\n // Promote optimistic startSerial when the relay/echo arrives.\n this._logger.debug('Tree.applyMessage(); promoting startSerial', { runId: wireRunId, serial });\n run.node.startSerial = serial;\n this._promoteSerial(run);\n }\n\n // Index the codec-message-id against the node that actually owns it.\n const ownerKey = nodeKey(run.node);\n if (codecMessageId) this._codecMessageIdToNodeKey.set(codecMessageId, ownerKey);\n\n this._foldInto(run, all, serial, codecMessageId);\n\n this._emitter.emit('output', { runId: ownerKey, inputCodecMessageId, codecMessageId, serial, events: outputs });\n }\n\n /**\n * Record a reply run against its input-node parent (the reverse edge powering\n * `getReplyRuns` and regenerate sibling grouping). A reply run's\n * `parentCodecMessageId` is its input node's codec-message-id (the master\n * invariant), so no resolution is needed.\n * @param node - The reply run node.\n * @param runId - The run's id.\n */\n private _indexReplyRun(node: ConversationNode<TProjection>, runId: string): void {\n if (node.parentCodecMessageId === undefined) return;\n addToSetMap(this._replyRunsByInput, node.parentCodecMessageId, runId);\n }\n\n applyRunLifecycle(event: RunLifecycleEvent): void {\n this._logger.trace('DefaultTree.applyRunLifecycle();', { type: event.type, runId: event.runId });\n // Structural channel: emit `update` only when the lifecycle event changes\n // the tree shape. Only run-start can do that (a new Run, startSerial\n // promotion, or structural-metadata backfill); suspend/resume/end mutate\n // status/endSerial on an existing node — content, not structure — so the\n // conditional naturally never fires for them.\n const structuralBefore = this._structuralVersion;\n switch (event.type) {\n case 'start': {\n this._applyRunStart(event);\n break;\n }\n case 'suspend': {\n this._applyRunSuspend(event);\n break;\n }\n case 'resume': {\n this._applyRunResume(event);\n break;\n }\n case 'end': {\n this._applyRunEnd(event);\n break;\n }\n }\n this._emitter.emit('run', event);\n if (this._structuralVersion !== structuralBefore) this._emitter.emit('update');\n }\n\n /**\n * Apply a run-start lifecycle event's structural effect: create the reply\n * run if it doesn't exist yet, or backfill an optimistic / wire-created\n * node's structure and metadata from the canonical run-start. Mutates\n * `_structuralVersion` when the tree shape changes; the caller owns the\n * `run`/`update` emits.\n * @param event - The run-start lifecycle event.\n */\n private _applyRunStart(event: RunLifecycleEvent & { type: 'start' }): void {\n const existing = this._nodeIndex.get(event.runId);\n if (existing?.node.kind === 'run') {\n const node = existing.node;\n if (node.status !== 'active') {\n node.status = 'active';\n }\n if (event.serial && !node.startSerial) {\n node.startSerial = event.serial;\n this._promoteSerial(existing);\n }\n // Backfill structural metadata if the Run was created from an\n // assistant wire that arrived before run-start (history pagination\n // boundary or out-of-order delivery). The run-start lifecycle event is\n // the canonical source for parent/forkOf/regenerates; only fill in\n // fields the wire didn't already populate. A run-start is always a\n // first start (continuations re-enter via `ai-run-resume`, which\n // carries no structural metadata), so it is unconditionally\n // authoritative here. `parent` is the run's STRUCTURAL parent (its\n // input node) — reachability and the reply→input edge read it.\n if (node.parentCodecMessageId === undefined && event.parent !== undefined) {\n node.parentCodecMessageId = event.parent;\n this._removeFromParentIndex(undefined, event.runId);\n this._addToParentIndex(node.parentCodecMessageId, event.runId);\n this._indexReplyRun(node, event.runId);\n this._structuralVersion++;\n }\n if (node.forkOf === undefined && event.forkOf !== undefined) {\n const forkOfKey = this._codecMessageIdToNodeKey.get(event.forkOf);\n if (forkOfKey !== undefined && forkOfKey !== event.runId) {\n node.forkOf = forkOfKey;\n this._structuralVersion++;\n }\n }\n if (node.regeneratesCodecMessageId === undefined && event.regenerates !== undefined) {\n node.regeneratesCodecMessageId = event.regenerates;\n this._structuralVersion++;\n }\n // Adopt the agent-minted invocation-id onto the optimistic node. The\n // agent mints it, so a node created from an optimistic insert (or an\n // assistant wire that arrived before run-start) carries an empty id\n // until the agent's run-start delivers it. Metadata, not structure —\n // consumers re-read it on the `run` emit, so no structural-version\n // bump.\n if (node.invocationId === '' && event.invocationId !== '') {\n node.invocationId = event.invocationId;\n }\n } else if (!existing) {\n const run = this._createRunFromLifecycle(event);\n this._insertNode(event.runId, run, run.node.parentCodecMessageId);\n this._indexReplyRun(run.node, event.runId);\n }\n }\n\n /**\n * Apply a run-suspend lifecycle event: pause the run without ending it —\n * mark the node 'suspended' and record the serial it paused at, but keep the\n * Run live so a resume under the same runId resumes it. Status/endSerial are\n * content, not structure, so this never mutates `_structuralVersion`; the\n * caller owns the emits.\n * @param event - The run-suspend lifecycle event.\n */\n private _applyRunSuspend(event: RunLifecycleEvent & { type: 'suspend' }): void {\n const run = this._nodeIndex.get(event.runId);\n if (run?.node.kind === 'run') {\n run.node.status = 'suspended';\n run.node.endSerial = event.serial;\n }\n }\n\n /**\n * Apply a run-resume lifecycle event: re-enter an already-started run by\n * flipping a suspended run back to 'active'. Pure re-entry — it carries no\n * parent/forkOf and does not promote startSerial (the original run-start owns\n * the run's structure). Only a suspended run resumes: a no-op when the run\n * isn't known (e.g. a resume replayed from a newer history page before its\n * run-start) and a no-op for an already-active or terminal\n * (complete/cancelled/error) run — a stray resume must never resurrect a run\n * that has ended. The caller owns the emits.\n * @param event - The run-resume lifecycle event.\n */\n private _applyRunResume(event: RunLifecycleEvent & { type: 'resume' }): void {\n const run = this._nodeIndex.get(event.runId);\n if (run?.node.kind === 'run' && run.node.status === 'suspended') {\n run.node.status = 'active';\n }\n }\n\n /**\n * Apply a run-end lifecycle event: record the terminal reason as the node's\n * status and the serial it ended at. Status/endSerial are content, not\n * structure, so this never mutates `_structuralVersion`; the caller owns the\n * emits.\n * @param event - The run-end lifecycle event.\n */\n private _applyRunEnd(event: RunLifecycleEvent & { type: 'end' }): void {\n const run = this._nodeIndex.get(event.runId);\n if (run?.node.kind === 'run') {\n run.node.status = event.reason;\n run.node.endSerial = event.serial;\n }\n }\n\n delete(key: string): void {\n const entry = this._nodeIndex.get(key);\n if (!entry) return;\n\n this._logger.debug('Tree.delete();', { key });\n\n this._removeFromParentIndex(entry.node.parentCodecMessageId, key);\n this._removeSortedNode(entry);\n this._nodeIndex.delete(key);\n // Drop the reply→input reverse edge.\n if (entry.node.kind === 'run' && entry.node.parentCodecMessageId !== undefined) {\n deleteFromSetMap(this._replyRunsByInput, entry.node.parentCodecMessageId, key);\n }\n // _codecMessageIdToNodeKey entries pointing at this node linger but are\n // harmless; they'll be overwritten if the node is re-created and remain\n // dangling otherwise. Cleanup not worth the index walk.\n\n this._structuralVersion++;\n this._emitter.emit('update');\n }\n\n // -------------------------------------------------------------------------\n // Internal helpers\n // -------------------------------------------------------------------------\n\n /**\n * Build a fresh RunNode from a wire message's headers. Used when an\n * inbound message arrives before any run-start event for its runId.\n * @param runId - The run-id from the inbound wire.\n * @param headers - Transport headers from the inbound Ably message.\n * @param serial - Ably channel serial; undefined for optimistic inserts.\n * @returns A newly-allocated internal run node ready for insertion.\n */\n private _createRunFromHeaders(\n runId: string,\n headers: Record<string, string>,\n serial: string | undefined,\n ): InternalNode<TProjection> {\n const forkOfMsgId = headers[HEADER_FORK_OF];\n return this._buildRunNode({\n runId,\n parentCodecMessageId: headers[HEADER_PARENT],\n // forkOf is resolved to the fork target's node key (an input node's\n // codec-message-id, or a run's id) — the same space `_isSiblingOf` walks.\n forkOf: forkOfMsgId ? this._codecMessageIdToNodeKey.get(forkOfMsgId) : undefined,\n regeneratesCodecMessageId: headers[HEADER_MSG_REGENERATE],\n clientId: headers[HEADER_RUN_CLIENT_ID] ?? '',\n invocationId: headers[HEADER_INVOCATION_ID] ?? '',\n startSerial: serial,\n });\n }\n\n /**\n * Allocate a RunNode from already-resolved fields. Shared by the\n * header-driven and lifecycle-driven run creators: both build the identical\n * RunNode literal and stamp an insert sequence.\n * @param params - The resolved run fields.\n * @param params.runId - The run's id (its primary key).\n * @param params.parentCodecMessageId - Structural parent codec-message-id, or undefined for a root.\n * @param params.forkOf - The resolved fork target's node key (already mapped through the codec-message-id index), or undefined.\n * @param params.regeneratesCodecMessageId - The codec-message-id this run regenerates, or undefined.\n * @param params.clientId - The publishing client's id.\n * @param params.invocationId - The agent invocation id.\n * @param params.startSerial - Ably channel serial; undefined for optimistic inserts.\n * @returns A newly-allocated internal run node ready for insertion.\n */\n private _buildRunNode(params: {\n runId: string;\n parentCodecMessageId: string | undefined;\n forkOf: string | undefined;\n regeneratesCodecMessageId: string | undefined;\n clientId: string;\n invocationId: string;\n startSerial: string | undefined;\n }): InternalNode<TProjection> {\n const node: RunNode<TProjection> = {\n kind: 'run',\n runId: params.runId,\n parentCodecMessageId: params.parentCodecMessageId,\n forkOf: params.forkOf,\n regeneratesCodecMessageId: params.regeneratesCodecMessageId,\n clientId: params.clientId,\n invocationId: params.invocationId,\n status: 'active',\n projection: this._codec.init(),\n startSerial: params.startSerial,\n endSerial: undefined,\n };\n\n return { node, insertSeq: this._seqCounter++ };\n }\n\n /**\n * Build a fresh InputNode from a run-less user input wire's headers.\n * @param codecMessageId - The input's codec-message-id (its primary key).\n * @param headers - Transport headers from the inbound Ably message.\n * @param serial - Ably channel serial; undefined for optimistic inserts.\n * @returns A newly-allocated internal input node ready for insertion.\n */\n private _createInputNodeFromHeaders(\n codecMessageId: string,\n headers: Record<string, string>,\n serial: string | undefined,\n ): InternalNode<TProjection> {\n const forkOfMsgId = headers[HEADER_FORK_OF];\n const node: InputNode<TProjection> = {\n kind: 'input',\n codecMessageId,\n parentCodecMessageId: headers[HEADER_PARENT],\n // An edit's fork-of names the original prompt's codec-message-id, which\n // IS that input node's key — no resolution needed.\n forkOf: forkOfMsgId,\n projection: this._codec.init(),\n serial,\n };\n return { node, insertSeq: this._seqCounter++ };\n }\n\n /**\n * Build a fresh RunNode from a run-start lifecycle event. Used when a\n * run-start event arrives before any message for its runId.\n * @param event - The run-start lifecycle event from the agent, including\n * its channel serial.\n * @returns A newly-allocated internal run node ready for insertion.\n */\n private _createRunFromLifecycle(event: RunLifecycleEvent & { type: 'start' }): InternalNode<TProjection> {\n const forkOfMsgId = event.forkOf;\n return this._buildRunNode({\n runId: event.runId,\n parentCodecMessageId: event.parent,\n forkOf: forkOfMsgId ? this._codecMessageIdToNodeKey.get(forkOfMsgId) : undefined,\n regeneratesCodecMessageId: event.regenerates,\n clientId: event.clientId,\n invocationId: event.invocationId,\n startSerial: event.serial,\n });\n }\n\n // -------------------------------------------------------------------------\n // Events\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CT8b, AIT-CT8e\n on(event: 'update', handler: () => void): () => void;\n on(event: 'ably-message', handler: (msg: Ably.InboundMessage) => void): () => void;\n on(event: 'run', handler: (event: RunLifecycleEvent) => void): () => void;\n on(event: 'output', handler: (event: OutputEvent<TOutput>) => void): () => void;\n on(\n event: 'update' | 'ably-message' | 'run' | 'output',\n handler:\n | (() => void)\n | ((msg: Ably.InboundMessage) => void)\n | ((event: RunLifecycleEvent) => void)\n | ((event: OutputEvent<TOutput>) => void),\n ): () => void {\n // CAST: overload signatures enforce correct handler types per event name.\n const cb = handler as (arg: TreeEventsMap<TOutput>[keyof TreeEventsMap<TOutput>]) => void;\n this._emitter.on(event, cb);\n return () => {\n this._emitter.off(event, cb);\n };\n }\n\n /**\n * Forward a raw Ably message event to tree subscribers.\n * @param msg - The raw Ably message to emit.\n */\n emitAblyMessage(msg: Ably.InboundMessage): void {\n this._logger.trace('DefaultTree.emitAblyMessage();');\n this._emitter.emit('ably-message', msg);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a Tree that materializes branching conversation history from a flat\n * oplog of Ably messages as a two-node-per-turn forest (input node + reply run).\n * @param codec - Codec used to fold inbound events into per-Run projections.\n * @param logger - Logger for diagnostic output.\n * @returns A new {@link DefaultTree} instance. The session uses DefaultTree\n * directly for internal methods (applyMessage, applyRunLifecycle,\n * emitAblyMessage). Public consumers see the narrower {@link Tree} interface.\n */\nexport const createTree = <TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection>(\n codec: Reducer<TInput | TOutput, TProjection>,\n logger: Logger,\n): DefaultTree<TInput, TOutput, TProjection> => new DefaultTree<TInput, TOutput, TProjection>(codec, logger);\n","/**\n * loadHistory — load conversation history from an Ably channel and return\n * the raw wire messages as a paginated HistoryPage result.\n *\n * This does NOT decode: it pages back through Ably history until `limit`\n * complete messages are present, then hands the raw Ably messages\n * (oldest-first) to the caller. The View re-decodes them into the Tree\n * itself, so load-history only needs a cheap, header-based completion\n * counter to decide when to stop paging — the decoder never runs here.\n *\n * The `limit` option controls the number of complete **messages** per page,\n * not the number of Ably wire messages fetched. A message is complete when\n * its terminal wire signal — `status: \"complete\"` / `\"cancelled\"`, or a\n * `discrete` create — has been seen. Runs that span a\n * page boundary are handled by the counter requiring both a start and a\n * terminal signal before counting a message complete.\n *\n * Because Ably history returns newest-first, each page's `rawMessages` are\n * reversed to chronological (oldest-first) so the caller can fold them in\n * order.\n */\n\nimport type * as Ably from 'ably';\n\nimport { HEADER_CODEC_MESSAGE_ID, HEADER_DISCRETE, HEADER_STATUS, HEADER_STREAM } from '../../constants.js';\nimport type { Logger } from '../../logger.js';\nimport { getTransportHeaders } from '../../utils.js';\nimport type { HistoryPage, LoadHistoryOptions } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Shared state across pages within one history traversal\n// ---------------------------------------------------------------------------\n\ninterface HistoryState {\n /** All raw Ably messages collected so far, in newest-first order (as received from Ably). */\n rawMessages: Ably.InboundMessage[];\n /**\n * How many complete messages have been served to the consumer so far.\n * Drives the buffered-page logic: when a single fetch gathers more than\n * `limit` completions, later pages are served from the buffer without\n * fetching, advancing this counter `limit` at a time.\n */\n returnedCount: number;\n /** How many raw Ably messages have been served to the consumer so far. */\n returnedRawCount: number;\n /** The last Ably page cursor for continued pagination. */\n lastAblyPage: Ably.PaginatedResult<Ably.InboundMessage> | undefined;\n /**\n * `codec-message-id`s for which a start signal has been seen: any\n * `message.create` / `message.update` / `message.append` with\n * `stream: \"true\"` (the decoder establishes a tracker via create or\n * first-contact), or a `message.create` carrying `discrete` (a discrete\n * message, created and terminated in one wire message).\n */\n startedCodecMessageIds: Set<string>;\n /**\n * `codec-message-id`s with a terminal wire signal: either `discrete`\n * on a `message.create` (discrete message) or `status: \"complete\"`\n * / `\"cancelled\"` on any action (closed stream).\n */\n terminatedCodecMessageIds: Set<string>;\n /**\n * `codec-message-id`s that are both started AND terminated — counted as\n * complete. The fetch loop reads this set's size to decide when to stop\n * paging. Maintained incrementally by {@link countNewCompletions}. Grows\n * monotonically.\n */\n completedCodecMessageIds: Set<string>;\n logger: Logger;\n}\n\n// ---------------------------------------------------------------------------\n// Incremental completion counting (header scan, no decode)\n// ---------------------------------------------------------------------------\n\n/**\n * Scan newly-added raw messages and track which `codec-message-id`s have\n * become complete. Used by {@link fetchUntilLimit} to decide when enough\n * completed messages have been collected, without running the decoder.\n *\n * A codec-message-id is considered complete only when BOTH of these have been seen:\n * - a \"start\" signal: either `discrete` on a `message.create`\n * (discrete messages are created and terminated by the same wire\n * message), OR any `message.create` / `message.update` / `message.append`\n * with `stream: \"true\"` (the decoder establishes a tracker via\n * create or first-contact).\n * - a \"terminal\" signal: `discrete` on the create, or\n * `status: \"complete\"` / `\"cancelled\"` on any later action.\n *\n * Why update and append count as starts: Ably history can compact a live\n * `create + append + ... + append{status:complete}` sequence into a single\n * `message.update` with `STREAM=true` and `STATUS=complete`. The decoder\n * handles that via first-contact. Counting only `message.create` as a start\n * would cause the fetch loop to page past a compacted run without ever\n * marking it complete.\n *\n * Requiring both halves matters when a streaming run spans a page\n * boundary: the terminal arrives in the newer page (fetched first) while\n * the start sits in an older page. Counting the terminal alone would stop\n * the fetch loop prematurely - the decoder would have no stream state to\n * resolve, and the message wouldn't make it into the result.\n *\n * Messages skipped for counting:\n * - Missing `codec-message-id`: lifecycle events not tied to a domain message.\n * - `message.delete`: clears the tracker, doesn't produce output.\n *\n * Amend-class wire messages (events targeting an existing message via\n * `HEADER_CODEC_MESSAGE_ID`) flow through the same counter — the Sets naturally\n * dedup so a tool-output amend on an already-seen codec-message-id is idempotent.\n *\n * Known edge case: if Ably history is truncated and a terminal survives\n * while every start signal for its codec-message-id has rolled off, the counter will\n * never mark that `codec-message-id` complete. The loop keeps fetching until it runs\n * out of pages, then returns whatever raw messages it collected.\n * @param state - The shared history traversal state.\n * @param newMessages - The Ably messages just pushed onto `state.rawMessages`.\n */\nconst countNewCompletions = (state: HistoryState, newMessages: readonly Ably.InboundMessage[]): void => {\n for (const msg of newMessages) {\n const headers = getTransportHeaders(msg);\n const codecMessageId = headers[HEADER_CODEC_MESSAGE_ID];\n if (!codecMessageId) continue;\n\n const action = msg.action;\n const isDiscreteCreate = action === 'message.create' && HEADER_DISCRETE in headers;\n // Any content-producing action on a streamed serial counts as a start:\n // the decoder uses create or first-contact (update/append) to establish\n // its tracker. Delete clears tracker state and emits nothing, so it\n // never counts as a start.\n const hasStreamContent =\n headers[HEADER_STREAM] === 'true' &&\n (action === 'message.create' || action === 'message.update' || action === 'message.append');\n const status = headers[HEADER_STATUS];\n const isTerminal = status === 'complete' || status === 'cancelled';\n\n if (isDiscreteCreate || hasStreamContent) state.startedCodecMessageIds.add(codecMessageId);\n if (isDiscreteCreate || isTerminal) state.terminatedCodecMessageIds.add(codecMessageId);\n if (state.startedCodecMessageIds.has(codecMessageId) && state.terminatedCodecMessageIds.has(codecMessageId)) {\n state.completedCodecMessageIds.add(codecMessageId);\n }\n }\n};\n\n// ---------------------------------------------------------------------------\n// Fetch Ably pages until we have enough completed messages\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch Ably history pages until we have enough completed messages.\n *\n * The loop uses {@link countNewCompletions} - a cheap O(new messages) header\n * scan - to decide when to stop, rather than running the decoder per page.\n * @param state - The shared history traversal state.\n * @param ablyPage - The current Ably paginated result to start from.\n * @param limit - Target number of completed messages beyond what has already been returned.\n */\nconst fetchUntilLimit = async (\n state: HistoryState,\n ablyPage: Ably.PaginatedResult<Ably.InboundMessage>,\n limit: number,\n): Promise<void> => {\n state.rawMessages.push(...ablyPage.items);\n state.lastAblyPage = ablyPage;\n countNewCompletions(state, ablyPage.items);\n\n const target = state.returnedCount + limit;\n while (state.completedCodecMessageIds.size < target && ablyPage.hasNext()) {\n state.logger.debug('loadHistory.fetchUntilLimit(); fetching next page', {\n collected: state.rawMessages.length,\n completed: state.completedCodecMessageIds.size,\n });\n const nextPage = await ablyPage.next();\n if (!nextPage) break;\n ablyPage = nextPage;\n state.rawMessages.push(...nextPage.items);\n state.lastAblyPage = nextPage;\n countNewCompletions(state, nextPage.items);\n }\n};\n\n// ---------------------------------------------------------------------------\n// Build HistoryPage result from current state\n// ---------------------------------------------------------------------------\n\n/**\n * Build a HistoryPage of raw wire messages from the current fetch state.\n * @param state - The shared history traversal state.\n * @param limit - Max complete messages per page.\n * @returns A page of raw history messages with a `next()` cursor.\n */\nconst buildResult = (state: HistoryState, limit: number): HistoryPage => {\n // Advance the served-completion counter by up to `limit`, mirroring the\n // page granularity the consumer asked for. `rawMessages` for this page are\n // all wires fetched since the previous page (empty for buffered pages).\n const totalCompleted = state.completedCodecMessageIds.size;\n const served = Math.min(limit, Math.max(0, totalCompleted - state.returnedCount));\n state.returnedCount += served;\n\n const moreCompleted = totalCompleted > state.returnedCount;\n const moreAblyPages = state.lastAblyPage?.hasNext() ?? false;\n\n // Raw Ably messages for this page in chronological order (oldest first).\n const newRawCount = state.rawMessages.length - state.returnedRawCount;\n const rawMessages = newRawCount > 0 ? state.rawMessages.slice(state.returnedRawCount).toReversed() : [];\n state.returnedRawCount = state.rawMessages.length;\n\n return {\n rawMessages,\n hasNext: () => moreCompleted || moreAblyPages,\n next: async () => {\n if (moreCompleted) {\n return buildResult(state, limit);\n }\n if (!moreAblyPages || !state.lastAblyPage) return;\n const nextAbly = await state.lastAblyPage.next();\n if (!nextAbly) return;\n await fetchUntilLimit(state, nextAbly, limit);\n return buildResult(state, limit);\n },\n };\n};\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Load conversation history from a channel and return the raw wire messages.\n *\n * Attaches the channel if not already attached, then calls\n * `channel.history({ untilAttach: true })` to guarantee no gap between\n * historical and live messages. The attach is idempotent.\n *\n * The `limit` option controls the number of complete messages\n * returned per page, not the number of Ably wire messages fetched.\n * @param channel - The Ably channel to load history from.\n * @param options - Pagination options.\n * @param logger - Logger for diagnostic output.\n * @returns The first page of raw history messages.\n */\n// Spec: AIT-CT11, AIT-CT11b\nexport const loadHistory = async (\n channel: Ably.RealtimeChannel,\n options: LoadHistoryOptions | undefined,\n logger: Logger,\n): Promise<HistoryPage> => {\n const limit = options?.limit ?? 100;\n const state: HistoryState = {\n rawMessages: [],\n returnedCount: 0,\n returnedRawCount: 0,\n lastAblyPage: undefined,\n startedCodecMessageIds: new Set<string>(),\n terminatedCodecMessageIds: new Set<string>(),\n completedCodecMessageIds: new Set<string>(),\n logger,\n };\n\n logger.trace('loadHistory();', { limit });\n\n // Request more Ably messages than the domain limit to account for\n // the many-to-one ratio (multiple wire messages per message).\n const wireLimit = limit * 10;\n\n await channel.attach();\n const ablyPage = await channel.history({ untilAttach: true, limit: wireLimit });\n await fetchUntilLimit(state, ablyPage, limit);\n return buildResult(state, limit);\n};\n","/**\n * DefaultView — a paginated, branch-aware projection over the Tree.\n *\n * Wraps a Tree (RunNode-keyed) and manages a pagination window that controls\n * which Runs are visible to the UI. New live Runs appear immediately; older\n * Runs are revealed progressively via `loadOlder()`.\n *\n * `getMessages()` reads the Tree's visible node chain (input nodes + reply\n * runs, with sibling selection applied) and concatenates each node's\n * `codec.getMessages(node.projection)` to produce the flat\n * `CodecMessage<TMessage>[]` the UI renders.\n *\n * Each View owns its own branch selection state and pagination window,\n * allowing multiple independent Views over the same Tree.\n *\n * Events are scoped to the visible window — 'update' only fires when the\n * visible output changes, 'ably-message' only for messages corresponding to\n * visible Runs, and 'run' only for runs with visible content.\n */\n\nimport * as Ably from 'ably';\n\nimport { HEADER_CODEC_MESSAGE_ID, HEADER_RUN_ID } from '../../constants.js';\nimport { ErrorCode } from '../../errors.js';\nimport { EventEmitter } from '../../event-emitter.js';\nimport type { Logger } from '../../logger.js';\nimport { getTransportHeaders } from '../../utils.js';\nimport type { Codec, CodecInputEvent, CodecMessage, CodecOutputEvent } from '../codec/types.js';\nimport { applyWireMessage } from './decode-fold.js';\nimport { loadHistory } from './load-history.js';\nimport { nodeKey, type TreeInternal } from './tree.js';\nimport type {\n ActiveRun,\n BranchSelection,\n ConversationNode,\n HistoryPage,\n OutputEvent,\n RunInfo,\n RunLifecycleEvent,\n RunNode,\n SendOptions,\n View,\n} from './types.js';\n\n// ---------------------------------------------------------------------------\n// Events map\n// ---------------------------------------------------------------------------\n\ninterface ViewEventsMap {\n update: undefined;\n 'ably-message': Ably.InboundMessage;\n run: RunLifecycleEvent;\n}\n\n// ---------------------------------------------------------------------------\n// Send delegate\n// ---------------------------------------------------------------------------\n\n/**\n * Internal delegate function provided by the session for executing sends.\n * The View pre-computes the visible branch's flat message list and the\n * codec-message-id of its tail (for auto-parent routing) before calling\n * the delegate, so the delegate has no back-reference to the View.\n *\n * Each TInput carries its own routing metadata (`parent` / `target` /\n * `codecMessageId`) via the {@link CodecInputEvent} base; the delegate\n * reads those fields directly without runtime classification.\n *\n * `parentCodecMessageId` is the codec-message-id of the last message in\n * the visible branch (extracted from the tail Run's projection per codec\n * convention), or `undefined` for an empty conversation. The session\n * uses it as the auto-parent for fresh user messages.\n */\nexport type SendDelegate<TInput extends CodecInputEvent> = (\n input: TInput[],\n options: SendOptions | undefined,\n parentCodecMessageId: string | undefined,\n) => Promise<ActiveRun>;\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/** Options for creating a View. */\nexport interface ViewOptions<TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage> {\n /** The tree to project. */\n tree: TreeInternal<TInput, TOutput, TProjection>;\n /** The Ably channel to load history from. */\n channel: Ably.RealtimeChannel;\n /** The codec used to project messages, mint regenerate inputs, and decode history. */\n codec: Codec<TInput, TOutput, TProjection, TMessage>;\n /** Delegate for executing sends through the session. */\n sendDelegate: SendDelegate<TInput>;\n /** Logger for diagnostic output. */\n logger: Logger;\n /** Called when the view is closed, allowing the owner to clean up references. */\n onClose?: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Branch selection\n// ---------------------------------------------------------------------------\n\n/**\n * Internal tagged union representing why a branch was selected for an\n * edit-fork group. Stored per group-root runId in the View's\n * `_branchSelections` map. Not the public-facing {@link BranchSelection}\n * — that's a UI-facing bundle returned by `view.branchSelection(id)`.\n */\ntype BranchSelectionState =\n /** Explicit navigation via `selectSibling()`. The selected input-node key. */\n | { kind: 'user'; selectedKey: string }\n /** This view initiated an edit fork — auto-selected the new input node. */\n | { kind: 'auto'; selectedKey: string }\n /** An external fork appeared — pinned to the currently-visible sibling to prevent drift. */\n | { kind: 'pinned'; selectedKey: string };\n\n/**\n * Selection state for a regenerate group. Keyed by the anchor codec-message-id (the\n * assistant codec-message-id being regenerated). Distinct from {@link BranchSelectionState}\n * because regenerate groups are message-level (group members share an\n * anchor codec-message-id), not edit forks of the user prompt.\n *\n * Unlike fork-of groups, regenerate groups do not \"pin to current visible\"\n * when a new member appears externally — the default for a regenerate\n * slot is always the latest member, so an external regenerator auto-rolls\n * forward unless the user has explicitly selected an earlier member.\n */\ntype RegenSelection =\n /** Explicit navigation via `selectSibling()`. The selected reply-run id. */\n | { kind: 'user'; selectedRunId: string }\n /** This view initiated a regenerate — auto-selected the new reply run when it arrived. */\n | { kind: 'auto'; selectedRunId: string }\n /**\n * This view's `regenerate()` is in flight. Keyed (in `_regenSelections`) by\n * the regenerate group's root; `carrierCodecMessageId` is the regenerate\n * carrier event's id, used to recognise the new reply run when it appears.\n */\n | { kind: 'pending'; carrierCodecMessageId: string };\n\n/**\n * A resolved branch point: the group `kind` plus the sibling nodes that make\n * up the alternatives. `fork-of` is an edit-style branch anchored at the user\n * input node; `regen` is a regenerate-style branch anchored at the assistant\n * slot. `groupRoot` is the group's key (input group root for fork-of, the\n * original reply's group root for regen).\n */\ntype MessageBranchPoint<TProjection> =\n | { kind: 'fork-of'; groupRoot: string; siblings: ConversationNode<TProjection>[] }\n | { kind: 'regen'; groupRoot: string; siblings: ConversationNode<TProjection>[] };\n\n// ---------------------------------------------------------------------------\n// Send-input normalisation\n// ---------------------------------------------------------------------------\n\n/**\n * Normalise the two input shapes `View.send` accepts (a single TInput\n * or an array) into the array shape the SendDelegate consumes.\n * @param input - The raw input from `View.send`.\n * @returns The normalised input array.\n */\nconst _normaliseSend = <TInput extends CodecInputEvent>(input: TInput | TInput[]): TInput[] =>\n Array.isArray(input) ? input : [input];\n\n// ---------------------------------------------------------------------------\n// Fetch tuning\n// ---------------------------------------------------------------------------\n\n/**\n * Multiplier applied to the user-supplied Run-unit `loadOlder(limit)`\n * when issuing the first `loadHistory` page request. `loadHistory`\n * counts complete domain *messages* per page, not Runs; a typical Run\n * produces ~2 messages (user + assistant). Asking for `limit * factor`\n * messages on the first page reduces extra round-trips when the actual\n * messages-per-Run ratio is around the factor. `_loadUntilVisible`\n * still loops on the Run count regardless, so this is purely a\n * fetch-efficiency hint.\n */\nconst _RUN_TO_MESSAGE_FETCH_FACTOR = 3;\n\n/**\n * Project a Tree `RunNode` down to the View-facing `RunInfo` shape:\n * drop the codec projection and the structural fields that callers\n * reach via `session.tree` when they need them.\n * @param run - The tree's RunNode.\n * @returns A projection-free RunInfo.\n */\nconst _toRunInfo = <TProjection>(run: RunNode<TProjection>): RunInfo => ({\n runId: run.runId,\n clientId: run.clientId,\n status: run.status,\n invocationId: run.invocationId,\n});\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nexport class DefaultView<\n TInput extends CodecInputEvent,\n TOutput extends CodecOutputEvent,\n TProjection,\n TMessage,\n> implements View<TInput, TMessage> {\n private readonly _tree: TreeInternal<TInput, TOutput, TProjection>;\n private readonly _channel: Ably.RealtimeChannel;\n private readonly _codec: Codec<TInput, TOutput, TProjection, TMessage>;\n private readonly _sendDelegate: SendDelegate<TInput>;\n private readonly _logger: Logger;\n private readonly _emitter: EventEmitter<ViewEventsMap>;\n private readonly _onClose?: () => void;\n\n /**\n * View-local branch selections: group-root runId → selection intent.\n * Fork points not present here default to the latest sibling.\n */\n private readonly _branchSelections = new Map<string, BranchSelectionState>();\n\n /**\n * View-local regenerate-group selections: anchor codec-message-id (the assistant\n * codec-message-id being regenerated) → selection intent. Distinct from\n * {@link _branchSelections} because a regenerate group is a set of\n * same-parent reply runs — message-level alternatives at a single\n * conversation slot, not edit forks of the prompt. Groups not present here default to the latest\n * member (the most recent regenerator, or the original if no regen has\n * landed).\n */\n private readonly _regenSelections = new Map<string, RegenSelection>();\n\n /** Spec: AIT-CT11c — runIds loaded from history but not yet revealed to the UI. */\n private readonly _withheldRunIds = new Set<string>();\n\n /** Snapshot of visible node keys — used to detect structural changes and for selection pinning. */\n private _lastVisibleNodeKeys: string[] = [];\n\n /**\n * Snapshot of visible projection references — used to detect in-place\n * projection updates (streaming). One entry per visible Run.\n */\n private _lastVisibleProjections: TProjection[] = [];\n\n /**\n * Snapshot of the visible flat message chain with codec-message-ids —\n * exposed verbatim via `getMessages()` and the internal correlation\n * source for parent/branch routing.\n */\n private _lastVisibleMessagePairs: CodecMessage<TMessage>[] = [];\n\n /** Cached visible node-key Set — for O(1) lookup in event scoping. */\n private _lastVisibleNodeKeySet = new Set<string>();\n\n /** Whether there are more history pages to fetch from the channel. */\n private _hasMoreHistory = false;\n\n /** Internal state for continuing history pagination. */\n private _lastHistoryPage: HistoryPage | undefined;\n\n /** Buffer of withheld nodes (input + reply), drained newest-first by successive loadOlder() calls. */\n private readonly _withheldBuffer: ConversationNode<TProjection>[] = [];\n\n /** Unsubscribe functions for tree event subscriptions. */\n private readonly _unsubs: (() => void)[] = [];\n\n /**\n * Cached result of the last flat-nodes computation. Drives the visible\n * message snapshot exposed via `getMessages()`; refreshed by\n * `_computeFlatNodes()` on structural changes, selection changes,\n * and history reveal.\n */\n private _cachedNodes: ConversationNode<TProjection>[] = [];\n\n private _loadingOlder = false;\n private _processingHistory = false;\n private _closed = false;\n\n constructor(options: ViewOptions<TInput, TOutput, TProjection, TMessage>) {\n this._tree = options.tree;\n this._channel = options.channel;\n this._codec = options.codec;\n this._sendDelegate = options.sendDelegate;\n this._onClose = options.onClose;\n this._logger = options.logger.withContext({ component: 'View' });\n this._logger.trace('DefaultView();');\n this._emitter = new EventEmitter<ViewEventsMap>(this._logger);\n\n // Compute initial cache and snapshot visible state\n this._cachedNodes = this._computeFlatNodes();\n this._updateVisibleSnapshot(this._cachedNodes);\n\n // Subscribe to tree events and re-emit scoped versions\n this._unsubs.push(\n this._tree.on('update', () => {\n this._onTreeUpdate();\n }),\n this._tree.on('ably-message', (msg) => {\n this._onTreeAblyMessage(msg);\n }),\n this._tree.on('run', (event) => {\n this._onTreeRun(event);\n }),\n this._tree.on('output', (event) => {\n this._onTreeOutput(event);\n }),\n );\n }\n\n /**\n * Handle decoded outputs folded into a Run (streaming delta). If the run\n * is on the visible chain, recompute the flat message list and emit\n * `update`.\n * @param event - The output event from the Tree.\n */\n private _onTreeOutput(event: OutputEvent<TOutput>): void {\n if (this._processingHistory) return;\n // The fold target may be a reply run (event.runId) or a user input node\n // (event.runId undefined — the agent mints run-ids, so an input fold has\n // none). Gate on whichever key the visible set holds.\n const folded =\n (event.runId !== undefined && this._lastVisibleNodeKeySet.has(event.runId)) ||\n (event.inputCodecMessageId !== undefined && this._lastVisibleNodeKeySet.has(event.inputCodecMessageId));\n if (!folded) return;\n\n // The Tree emits `output` once per inbound message fold (with empty\n // `events` for inputs-only folds), so it fires whenever a visible Run's\n // projection changed and we always re-emit. The Reducer contract permits\n // in-place mutation, which means we cannot use projection-ref or\n // TMessage-ref equality to detect change: a streaming chunk legitimately\n // mutates the same UIMessage object, and a ref-equality short-circuit\n // would suppress every update. React state setters at the subscriber\n // boundary already dedup by array reference, so a redundant emit is a\n // no-op for unchanged hook consumers.\n this._lastVisibleProjections = this._cachedNodes.map((n) => n.projection);\n this._lastVisibleMessagePairs = this._extractMessages(this._cachedNodes);\n this._emitter.emit('update');\n }\n\n // -------------------------------------------------------------------------\n // Public query methods\n // -------------------------------------------------------------------------\n\n getMessages(): CodecMessage<TMessage>[] {\n return this._lastVisibleMessagePairs;\n }\n\n runs(): RunInfo[] {\n // `_cachedNodes` is the visible node chain (inputs + reply runs) with\n // pagination and sibling selection already applied. RunInfo is reply-run\n // shaped, so filter to runs before projecting.\n return this._cachedNodes\n .filter((node): node is RunNode<TProjection> => node.kind === 'run')\n .map((node) => _toRunInfo(node));\n }\n\n /**\n * Compute the fresh visible node chain. The Tree's `visibleNodes` already\n * applies kind-blind reachability and sibling selection (edit versions /\n * regenerate runs collapse to the selected member), so the View only layers\n * its pagination window on top: drop nodes whose key is currently withheld.\n * @returns A fresh array of visible nodes (inputs + reply runs).\n */\n private _computeFlatNodes(): ConversationNode<TProjection>[] {\n const treeNodes = this._treeVisibleNodes();\n if (this._withheldRunIds.size === 0) return treeNodes;\n return treeNodes.filter((node) => !this._withheldRunIds.has(nodeKey(node)));\n }\n\n /**\n * Recompute the visible node chain, refresh the cache + snapshot, and emit\n * `update` unconditionally. Use after a mutation that always changes the\n * visible output (e.g. an explicit selection or a withheld-batch reveal).\n */\n private _recomputeAndEmit(): void {\n this._cachedNodes = this._computeFlatNodes();\n this._updateVisibleSnapshot(this._cachedNodes);\n this._emitter.emit('update');\n }\n\n /**\n * Recompute the visible node chain and, only if it differs from the current\n * snapshot, refresh the cache + snapshot and emit `update`. Use after a\n * mutation that may or may not move the visible window (e.g. a structural\n * tree update, or a deferred regenerate promotion that may already match).\n */\n private _recomputeAndEmitIfChanged(): void {\n const nodes = this._computeFlatNodes();\n if (this._visibleChanged(nodes)) {\n this._cachedNodes = nodes;\n this._updateVisibleSnapshot(nodes);\n this._emitter.emit('update');\n }\n }\n\n /**\n * Resolve the reply Run that owns a codec-message-id, narrowing the Tree's\n * node union to a {@link RunNode}. A user-input codec-message-id resolves to\n * an input node and yields `undefined` here — callers that must handle input\n * nodes use {@link _tree.getNodeByCodecMessageId} directly.\n * @param codecMessageId - The codec-message-id to resolve.\n * @returns The owning RunNode, or undefined if absent or not a reply Run.\n */\n private _runByCodecMessageId(codecMessageId: string): RunNode<TProjection> | undefined {\n const node = this._tree.getNodeByCodecMessageId(codecMessageId);\n return node?.kind === 'run' ? node : undefined;\n }\n\n /**\n * Extract the flat TMessage[] from a visible node chain.\n *\n * In the two-node model the Tree's `visibleNodes` has already selected one\n * member per sibling group (the chosen edit version, the chosen regenerate\n * run), so a regenerate is just a sibling reply run that appears in place of\n * the original. Each visible node contributes its own messages in projection\n * order; the flat list is their concatenation.\n *\n * Deferred caveat: a mid-reply regenerate that replaces a non-head message\n * inside a multi-message reply run is not expressible as a sibling run in\n * this model and is not handled here (see the `regenerate-of-multi-message`\n * golden test).\n * @param nodes - The visible nodes (inputs + reply runs) in chronological order.\n * @returns The flat message list, each message paired with its codec-message-id.\n */\n private _extractMessages(nodes: ConversationNode<TProjection>[]): CodecMessage<TMessage>[] {\n const messages: CodecMessage<TMessage>[] = [];\n for (const node of nodes) {\n for (const m of this._codec.getMessages(node.projection)) {\n messages.push(m);\n }\n }\n return messages;\n }\n\n hasOlder(): boolean {\n return this._withheldBuffer.length > 0 || this._hasMoreHistory;\n }\n\n /**\n * Reveal up to `limit` older Runs in this view.\n *\n * The pagination unit is the **Run**, not the message. A single Run\n * typically materialises into multiple messages (e.g. user + assistant\n * pair) so revealing `limit` Runs may add several messages to the flat\n * list returned by {@link getMessages}. Channel pages don't align to\n * Run boundaries, so {@link _loadUntilVisible} keeps fetching channel\n * pages until at least `limit` Runs are buffered (or the channel is\n * exhausted).\n * @param limit - Maximum number of older Runs to reveal. Defaults to 100.\n */\n async loadOlder(limit = 100): Promise<void> {\n if (this._closed || this._loadingOlder) return;\n this._loadingOlder = true;\n this._logger.trace('DefaultView.loadOlder();', { limit });\n\n try {\n // Drain withheld buffer first (older nodes, released newest-first). The\n // buffer holds a union of input + reply nodes, so this splices the newest\n // `limit` NODES, not `limit` runs. Because an input node travels with the\n // reply run it precedes, a drain may surface fewer than `limit` runs.\n if (this._withheldBuffer.length > 0) {\n const batch = this._withheldBuffer.splice(-limit, limit);\n this._releaseWithheld(batch);\n return;\n }\n\n // Buffer exhausted - load from channel history.\n if (!this._hasMoreHistory && !this._lastHistoryPage) {\n await this._loadFirstPage(limit);\n return;\n }\n\n if (!this._hasMoreHistory) return;\n\n if (!this._lastHistoryPage?.hasNext()) {\n this._hasMoreHistory = false;\n return;\n }\n\n const nextPage = await this._lastHistoryPage.next();\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- close() may be called during await\n if (this._closed || !nextPage) {\n if (!nextPage) this._hasMoreHistory = false;\n return;\n }\n\n await this._revealFromPage(nextPage, limit);\n } catch (error) {\n this._logger.error('DefaultView.loadOlder(); failed', { error });\n throw error;\n } finally {\n this._loadingOlder = false;\n }\n }\n\n // -------------------------------------------------------------------------\n // Run lookup\n // -------------------------------------------------------------------------\n\n runOf(codecMessageId: string): RunInfo | undefined {\n this._logger.trace('DefaultView.runOf();', { codecMessageId });\n const node = this._tree.getNodeByCodecMessageId(codecMessageId);\n if (!node) return undefined;\n if (node.kind === 'run') return _toRunInfo(node);\n // Input node: resolve to its selected reply run (undefined if none started).\n const reply = this._selectedReplyRun(node.codecMessageId);\n return reply ? _toRunInfo(reply) : undefined;\n }\n\n /**\n * Resolve the reply run currently selected for an input node, honouring the\n * View's regenerate selection. Falls back to the latest reply run when no\n * selection has been recorded; undefined when no reply run has started.\n * @param inputCodecMessageId - The input node's codec-message-id.\n * @returns The selected reply RunNode, or undefined.\n */\n private _selectedReplyRun(inputCodecMessageId: string): RunNode<TProjection> | undefined {\n const replies = this._tree.getReplyRuns(inputCodecMessageId);\n if (replies.length === 0) return undefined;\n if (replies.length === 1) return replies[0];\n // Multiple reply runs = a regenerate group. Honour the View's selection\n // (keyed by group root) else default to the latest.\n const groupRoot = this._tree.getGroupRoot(replies[0]?.runId ?? '');\n const sel = this._regenSelections.get(groupRoot);\n const selectedKey = sel && sel.kind !== 'pending' ? sel.selectedRunId : undefined;\n if (selectedKey !== undefined) {\n const chosen = replies.find((r) => r.runId === selectedKey);\n if (chosen) return chosen;\n }\n // Latest by startSerial; getReplyRuns is set-ordered, so sort defensively.\n return replies.toSorted((a, b) => (a.startSerial ?? '￿').localeCompare(b.startSerial ?? '￿')).at(-1);\n }\n\n run(runId: string): RunInfo | undefined {\n this._logger.trace('DefaultView.run();', { runId });\n const run = this._tree.getRunNode(runId);\n return run ? _toRunInfo(run) : undefined;\n }\n\n // -------------------------------------------------------------------------\n // Branch navigation (msg-anchored)\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CT13c, AIT-CT13d — branch points are codec-message-id\n // anchored. The View resolves the anchor (the user prompt for edits,\n // the assistant slot for regens) and routes the selection to the\n // appropriate internal selection map. Tree-level introspection\n // (RunNode access, runId-keyed queries) remains on the {@link Tree}.\n\n branchSelection(codecMessageId: string): BranchSelection<TMessage> {\n const branch = this._resolveMessageBranchPoint(codecMessageId);\n if (branch) {\n // Each sibling contributes its head message as the branch-arrow slot:\n // for an edit fork that is the alternate user prompt; for a regenerate\n // group it is the variant's first (anchor-equivalent) message.\n const siblings = branch.siblings.flatMap((s) => {\n const first = this._codec.getMessages(s.projection).at(0);\n return first ? [first.message] : [];\n });\n\n if (siblings.length > 0) {\n const index = this._resolveSelectedIndex(branch);\n const clamped = Math.max(0, Math.min(index, siblings.length - 1));\n const selected = siblings[clamped];\n return {\n hasSiblings: siblings.length > 1,\n siblings,\n index: clamped,\n selected,\n };\n }\n }\n\n // Known non-anchor message: the bundle's invariant is that\n // `siblings` contains the rendered message itself for any known\n // codec-message-id, so plain bubbles get `siblings.length === 1`\n // (not `0`) and the indexing space matches between read and write.\n // Resolve the owning node kind-blind — a plain user prompt is an input\n // node, an assistant message lives in a reply run; both carry a projection.\n const owner = this._tree.getNodeByCodecMessageId(codecMessageId);\n if (owner) {\n const found = this._codec.getMessages(owner.projection).find((m) => m.codecMessageId === codecMessageId);\n if (found !== undefined) {\n return { hasSiblings: false, siblings: [found.message], index: 0, selected: found.message };\n }\n }\n\n // Unknown id, or the owner Run is known but the codec doesn't surface\n // a message with this id from the projection (e.g. an event-only fold\n // such as a tool result that mutates an assistant in-place without\n // exposing its own TMessage). Treat both as \"no rendered message\",\n // returning the safe empty bundle.\n return { hasSiblings: false, siblings: [], index: 0, selected: undefined };\n }\n\n // Spec: AIT-CT13c, AIT-CT13d\n selectSibling(codecMessageId: string, index: number): void {\n this._logger.trace('DefaultView.selectSibling();', { codecMessageId, index });\n const branch = this._resolveMessageBranchPoint(codecMessageId);\n if (!branch) return;\n const clamped = Math.max(0, Math.min(index, branch.siblings.length - 1));\n const selected = branch.siblings[clamped];\n if (!selected) return; // unreachable: clamped is always in bounds\n if (branch.kind === 'fork-of') {\n this._branchSelections.set(branch.groupRoot, { kind: 'user', selectedKey: nodeKey(selected) });\n this._logger.debug('DefaultView.selectSibling(); fork-of', {\n codecMessageId,\n index: clamped,\n selectedKey: nodeKey(selected),\n });\n } else {\n this._regenSelections.set(branch.groupRoot, { kind: 'user', selectedRunId: nodeKey(selected) });\n this._logger.debug('DefaultView.selectSibling(); regenerate', {\n codecMessageId,\n index: clamped,\n selectedRunId: nodeKey(selected),\n groupRoot: branch.groupRoot,\n });\n }\n this._recomputeAndEmit();\n }\n\n /**\n * Resolve the currently selected sibling's index inside a branch group.\n * Pending selections fall back to the latest sibling. The caller clamps\n * the returned index against any post-extraction filtering.\n * @param branch - Resolved branch-point descriptor from `_resolveMessageBranchPoint`.\n * @returns The selected sibling's index within `branch.siblings`.\n */\n private _resolveSelectedIndex(branch: MessageBranchPoint<TProjection>): number {\n if (branch.kind === 'fork-of') {\n const sel = this._branchSelections.get(branch.groupRoot);\n if (!sel) return branch.siblings.length - 1;\n const idx = branch.siblings.findIndex((n) => nodeKey(n) === sel.selectedKey);\n return idx === -1 ? branch.siblings.length - 1 : idx;\n }\n const sel = this._regenSelections.get(branch.groupRoot);\n if (!sel || sel.kind === 'pending') return branch.siblings.length - 1;\n const idx = branch.siblings.findIndex((n) => nodeKey(n) === sel.selectedRunId);\n return idx === -1 ? branch.siblings.length - 1 : idx;\n }\n\n /**\n * Resolve the branch point anchored at `codecMessageId`, if any.\n *\n * Returns the resolved group `kind` along with the sibling list so the\n * caller can update the correct selection map without re-entering the\n * runId-based `select()` dispatch (which biases to fork-of first and\n * would mis-route a regen-anchor codec-message-id when the owning Run is in\n * BOTH groups — e.g. R1 owns both a user prompt that got edited and\n * an assistant that got regenerated).\n *\n * Two anchor cases:\n * - **fork-of** — `codecMessageId` is the first message of a Run in a fork-of\n * sibling group (edit-style branch point anchored at the user prompt).\n * - **regen** — `codecMessageId` is the regen-anchor itself (in the owner Run)\n * or content of a regenerator Run (regen-style branch point anchored\n * at the assistant slot).\n * @param codecMessageId - The codec-message-id to look up.\n * @returns The kind + sibling list + group key (runId for fork-of,\n * anchor codec-message-id for regen), or undefined when `codecMessageId` is not an\n * anchor in either group type.\n */\n private _resolveMessageBranchPoint(codecMessageId: string): MessageBranchPoint<TProjection> | undefined {\n const node = this._tree.getNodeByCodecMessageId(codecMessageId);\n if (!node) return undefined;\n\n // Edit-fork branch point: `codecMessageId` is a user INPUT node that has\n // sibling input nodes (alternate prompts via fork-of). The anchor is the\n // input node's own codec-message-id.\n if (node.kind === 'input') {\n const siblings = this._tree.getSiblingNodes(node.codecMessageId);\n if (siblings.length > 1) {\n return { kind: 'fork-of', groupRoot: this._tree.getGroupRoot(node.codecMessageId), siblings };\n }\n return undefined;\n }\n\n // Regenerate branch point: `codecMessageId` is owned by a reply run that has\n // sibling reply runs (the original reply + its regenerators, all parented at\n // the same input node). Anchor on the head message of the run so arrows\n // appear once per variant, not on every follow-up message.\n const siblings = this._tree.getSiblingNodes(node.runId);\n if (siblings.length > 1) {\n const firstMsg = this._codec.getMessages(node.projection).at(0);\n if (firstMsg?.codecMessageId === codecMessageId) {\n return { kind: 'regen', groupRoot: this._tree.getGroupRoot(node.runId), siblings };\n }\n }\n\n return undefined;\n }\n\n // -------------------------------------------------------------------------\n // Write operations\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CT3, AIT-CT4\n async send(input: TInput | TInput[], options?: SendOptions): Promise<ActiveRun> {\n this._logger.trace('DefaultView.send();');\n if (this._closed) {\n throw new Ably.ErrorInfo('unable to send; view is closed', ErrorCode.InvalidArgument, 400);\n }\n\n const normalised = _normaliseSend<TInput>(input);\n\n // The codec-message-id of the visible branch tail — the delegate uses it\n // for auto-parent routing on fresh user messages.\n const parentCodecMessageId = this._lastVisibleMessagePairs.at(-1)?.codecMessageId;\n\n const result = await this._sendDelegate(normalised, options, parentCodecMessageId);\n this._applyForkAutoSelect(result, options);\n return result;\n }\n\n /**\n * Auto-select / pin branch selections after a forking send.\n * @param result - The ActiveRun returned by the delegate.\n * @param options - The SendOptions passed by the caller.\n */\n private _applyForkAutoSelect(result: ActiveRun, options: SendOptions | undefined): void {\n // Spec: AIT-CT13e\n if (!options?.forkOf) return;\n\n // An edit inserts a NEW user input node optimistically; its codec-message-id\n // is the (only) optimistic id and IS its node key. Edit forks are input-node\n // sibling groups, so the selection is keyed by the input group root and the\n // selected member is the new input node's key.\n const editedInputKey = result.optimisticCodecMessageIds.at(0);\n if (editedInputKey === undefined) return;\n const groupRoot = this._tree.getGroupRoot(editedInputKey);\n\n this._branchSelections.set(groupRoot, { kind: 'auto', selectedKey: editedInputKey });\n this._recomputeAndEmit();\n }\n\n /**\n * Auto-select / pin the regenerate group anchored at `anchorCodecMessageId` so\n * the new Run's content appears as soon as the agent's run-start lands.\n *\n * `View.regenerate()` calls this with the assistant codec-message-id being\n * regenerated. The Run doesn't exist yet on the channel (the regenerate\n * wire is wire-only); the selection is recorded as `pending` and\n * promoted to `auto` by `_pinRegenSelections` once the corresponding\n * Run is created in the tree.\n * @param result - The ActiveRun returned by the delegate (run-id is the new regenerator's).\n * @param anchorCodecMessageId - The codec-message-id of the assistant being regenerated.\n */\n private _applyRegenerateAutoSelect(result: ActiveRun, anchorCodecMessageId: string): void {\n // A regenerate produces a new reply run parented at the SAME input node as\n // the original reply (the regenerate group). The agent mints the run-id, so\n // we cannot pin by it synchronously. Resolve the group root from the\n // original reply run owning the anchor, and pin a pending selection keyed by\n // that group root, carrying the regenerate carrier's codec-message-id\n // (`result.inputCodecMessageId`) so we can promote when the new reply run lands.\n const anchorRun = this._runByCodecMessageId(anchorCodecMessageId);\n if (!anchorRun) return;\n const groupRoot = this._tree.getGroupRoot(anchorRun.runId);\n\n this._regenSelections.set(groupRoot, {\n kind: 'pending',\n carrierCodecMessageId: result.inputCodecMessageId,\n });\n this._logger.debug('DefaultView._applyRegenerateAutoSelect(); deferring regenerate selection', {\n anchorCodecMessageId,\n groupRoot,\n carrier: result.inputCodecMessageId,\n });\n\n // The new reply run may already be in the tree (run-start raced ahead of the\n // sendDelegate resolution). Promote now and recompute so the visible set\n // catches up without waiting for the next structural change.\n this._resolvePendingRegenSelections();\n this._recomputeAndEmitIfChanged();\n }\n\n // Spec: AIT-CT5, AIT-CT13d\n async regenerate(messageId: string, options?: SendOptions): Promise<ActiveRun> {\n this._logger.trace('DefaultView.regenerate();', { messageId });\n\n if (this._closed) {\n throw new Ably.ErrorInfo('unable to regenerate; view is closed', ErrorCode.InvalidArgument, 400);\n }\n\n // `messageId` is the assistant being regenerated. The new Run is a\n // continuation of the regenerated message's Run, not a fork: the\n // message-level replacement (new assistant supersedes the original)\n // happens at projection extraction time. We still resolve the parent\n // user prompt so the new assistant's wire `parent` is correct,\n // and we send the truncated history (through the parent inclusive)\n // so the LLM re-answers the right message.\n const targetRun = this._runByCodecMessageId(messageId);\n if (!targetRun) {\n throw new Ably.ErrorInfo(\n `unable to regenerate; message not found in tree: ${messageId}`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n const parentCodecMessageId = this._findParentMsgId(targetRun, messageId);\n if (!parentCodecMessageId) {\n throw new Ably.ErrorInfo(\n `unable to regenerate; parent user message not found for ${messageId}`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n\n // Canonical regen anchor: when the user clicks Regenerate on an\n // already-regenerated assistant, the new alternative SHOULD belong\n // to the SAME branch point as the previous regen — but ONLY when\n // the target is the position-equivalent of the group anchor (the\n // head message of the regenerator Run). For a trailing follow-up\n // message inside a regenerator Run (e.g. the LLM text after the\n // regenerated tool call), the user expects the regen to anchor at\n // the specific message they clicked, not roll up to the group root.\n // Rebasing trailing regens to the group root produces a confusing\n // \"N+1 / N+1\" counter on the tool-call bubble and runs the whole\n // turn from scratch instead of just regenerating the text.\n let regenAnchorMsgId = messageId;\n if (targetRun.regeneratesCodecMessageId !== undefined) {\n const firstMsg = this._codec.getMessages(targetRun.projection).at(0);\n if (firstMsg?.codecMessageId === messageId) {\n regenAnchorMsgId = targetRun.regeneratesCodecMessageId;\n }\n }\n\n const sendOptions: SendOptions = {\n ...options,\n parent: parentCodecMessageId,\n };\n\n // Mint a regenerate input via the codec. The codec's well-known\n // `Regenerate` carries `target: regenAnchorMsgId` and `parent:\n // parentCodecMessageId`; the session reads those fields off the input\n // directly when building transport headers (`fork-of` and\n // `parent`). The agent's input-event lookup catches the wire signal;\n // no tree-upsert / projection fold runs locally.\n const regenerate = this._codec.createRegenerate(regenAnchorMsgId, parentCodecMessageId);\n const result = await this._sendDelegate([regenerate], sendOptions, parentCodecMessageId);\n this._applyRegenerateAutoSelect(result, regenAnchorMsgId);\n return result;\n }\n\n // Spec: AIT-CT6\n async edit(messageId: string, inputs: TInput | TInput[], options?: SendOptions): Promise<ActiveRun> {\n this._logger.trace('DefaultView.edit();', { messageId });\n\n if (this._closed) {\n throw new Ably.ErrorInfo('unable to edit; view is closed', ErrorCode.InvalidArgument, 400);\n }\n\n // The edit target is a user prompt — a run-less INPUT node — so resolve\n // it kind-blind, not via the reply-run-only lookup.\n const targetNode = this._tree.getNodeByCodecMessageId(messageId);\n if (!targetNode) {\n throw new Ably.ErrorInfo(\n `unable to edit; message not found in tree: ${messageId}`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n const parentCodecMessageId = this._findParentMsgId(targetNode, messageId);\n\n return this.send(inputs, {\n ...options,\n forkOf: messageId,\n parent: parentCodecMessageId,\n });\n }\n\n /**\n * Find the codec-message-id of the message immediately preceding `targetMsgId` in\n * the visible conversation.\n *\n * Consults the View's visible message chain first so message-level\n * replacements (regenerate) are respected: regenerating an\n * already-regenerated assistant lands the predecessor on the user\n * prompt the regen is responding to, NOT on the hidden original\n * assistant that occupies the same conversation slot. Falls back to a\n * projection-walk for the rare case where `targetMsgId` isn't on the\n * visible chain (e.g. caller is operating on a Run that's selection-\n * hidden by the current branch).\n * @param targetNode - The node (input node or reply run) that owns `targetMsgId`.\n * @param targetMsgId - The codec-message-id to find the parent of.\n * @returns The parent codec-message-id, or undefined if no predecessor exists.\n */\n private _findParentMsgId(targetNode: ConversationNode<TProjection>, targetMsgId: string): string | undefined {\n const visible = this._lastVisibleMessagePairs;\n const visIdx = visible.findIndex((m) => m.codecMessageId === targetMsgId);\n if (visIdx > 0) {\n return visible[visIdx - 1]?.codecMessageId;\n }\n if (visIdx === 0) return undefined;\n\n const messages = this._codec.getMessages(targetNode.projection);\n const idx = messages.findIndex((m) => m.codecMessageId === targetMsgId);\n if (idx > 0) {\n return messages[idx - 1]?.codecMessageId;\n }\n if (idx === 0 && targetNode.parentCodecMessageId !== undefined) {\n // The structural predecessor is the node owning parentCodecMessageId\n // (an input node, or a prior reply run). Its tail message is the parent.\n const parentNode = this._tree.getNodeByCodecMessageId(targetNode.parentCodecMessageId);\n if (parentNode) {\n return this._codec.getMessages(parentNode.projection).at(-1)?.codecMessageId;\n }\n }\n return undefined;\n }\n\n // -------------------------------------------------------------------------\n // Event subscription\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CT8a, AIT-CT8b, AIT-CT8e\n on(event: 'update', handler: () => void): () => void;\n on(event: 'ably-message', handler: (msg: Ably.InboundMessage) => void): () => void;\n on(event: 'run', handler: (event: RunLifecycleEvent) => void): () => void;\n on(\n event: 'update' | 'ably-message' | 'run',\n handler: (() => void) | ((msg: Ably.InboundMessage) => void) | ((event: RunLifecycleEvent) => void),\n ): () => void {\n // CAST: overload signatures enforce correct handler types per event name.\n const cb = handler as (arg: ViewEventsMap[keyof ViewEventsMap]) => void;\n this._emitter.on(event, cb);\n return () => {\n this._emitter.off(event, cb);\n };\n }\n\n // -------------------------------------------------------------------------\n // Lifecycle\n // -------------------------------------------------------------------------\n\n close(): void {\n if (this._closed) return;\n this._logger.info('DefaultView.close();');\n this._closed = true;\n this._loadingOlder = false;\n for (const unsub of this._unsubs) unsub();\n this._unsubs.length = 0;\n this._emitter.off();\n this._branchSelections.clear();\n this._regenSelections.clear();\n this._withheldRunIds.clear();\n this._withheldBuffer.length = 0;\n this._onClose?.();\n }\n\n // -------------------------------------------------------------------------\n // Private: history loading\n // -------------------------------------------------------------------------\n\n private async _loadFirstPage(limit: number): Promise<void> {\n // loadHistory's limit counts complete domain messages per page (not\n // Runs); see `_RUN_TO_MESSAGE_FETCH_FACTOR` for the scaling rationale.\n const messageLimit = limit * _RUN_TO_MESSAGE_FETCH_FACTOR;\n const firstPage = await loadHistory(this._channel, { limit: messageLimit }, this._logger);\n if (this._closed) return;\n await this._revealFromPage(firstPage, limit);\n }\n\n /**\n * Walk channel history from `page` until at least `limit` new Runs are\n * observed (or the channel is exhausted), then reveal the newest batch and\n * withhold the rest. Snapshots the already-visible nodes up front so only\n * newly-observed Runs count toward `limit`. No-op if the view closed during\n * the page walk.\n * @param page - The decoded history page to start from.\n * @param limit - Max Runs to reveal in this batch.\n */\n private async _revealFromPage(page: HistoryPage, limit: number): Promise<void> {\n // Snapshot before loading: every node already in the tree stays visible.\n const beforeRunIds = new Set(this._treeVisibleNodes().map((n) => nodeKey(n)));\n\n const { newVisible, lastPage } = await this._loadUntilVisible(page, limit, beforeRunIds);\n if (this._closed) return;\n this._lastHistoryPage = lastPage;\n this._hasMoreHistory = lastPage.hasNext();\n this._splitReveal(newVisible, limit);\n }\n\n /**\n * Reveal the newest `limit` Runs from `newVisible` and withhold the rest\n * so subsequent `loadOlder` calls can drain them. Called by\n * {@link _revealFromPage} to enforce the Run-unit pagination contract.\n * @param newVisible - Newly observed Runs from the history fetch.\n * @param limit - Max Runs to reveal in this batch.\n */\n private _splitReveal(newVisible: ConversationNode<TProjection>[], limit: number): void {\n // Reveal granularity is the reply RUN; an input node travels with the reply\n // run it precedes. Walk newest-first, counting reply runs toward `limit`,\n // and split the union list at the resulting boundary so an input + its reply\n // are revealed or withheld together.\n let runs = 0;\n let splitIdx = newVisible.length; // index of first revealed node\n for (let i = newVisible.length - 1; i >= 0; i--) {\n const node = newVisible[i];\n if (node?.kind === 'run') {\n if (runs === limit) break;\n runs++;\n }\n splitIdx = i;\n }\n const batch = newVisible.slice(splitIdx);\n const withheld = newVisible.slice(0, splitIdx);\n for (const n of withheld) {\n this._withheldRunIds.add(nodeKey(n));\n }\n this._withheldBuffer.push(...withheld);\n this._releaseWithheld(batch);\n }\n\n /**\n * Replay a history page's raw messages into the Tree. Dispatches by Ably\n * message name to run-lifecycle vs. regular wire messages, mirroring the\n * live `client-session._handleMessage` decode loop. Uses a fresh decoder\n * since the session's live decoder maintains its own stream-tracker state.\n * @param page - The history page returned by `loadHistory`.\n */\n private _processHistoryPage(page: HistoryPage): void {\n this._processingHistory = true;\n try {\n // Reconstruct the tree via the shared decode-fold engine — the same path\n // the client's live loop uses, so history replay can't drift from it.\n const decoder = this._codec.createDecoder();\n for (const rawMsg of page.rawMessages) {\n applyWireMessage(this._tree, decoder, rawMsg);\n }\n\n // Emit ably-message in a batch AFTER the whole page is applied, so a\n // subscriber resolving the owning Run sees the fully-rebuilt tree.\n for (const msg of page.rawMessages) {\n this._tree.emitAblyMessage(msg);\n }\n } finally {\n this._processingHistory = false;\n }\n }\n\n private async _loadUntilVisible(\n firstPage: HistoryPage,\n target: number,\n beforeRunIds: Set<string>,\n ): Promise<{ newVisible: ConversationNode<TProjection>[]; lastPage: HistoryPage }> {\n this._processHistoryPage(firstPage);\n let page = firstPage;\n\n const newVisibleCount = (): number => {\n let count = 0;\n for (const n of this._treeVisibleNodes()) {\n // Pagination counts reply RUNS toward the target (an input node travels\n // with the reply run it precedes — see `_splitReveal`).\n if (n.kind === 'run' && !beforeRunIds.has(nodeKey(n))) count++;\n }\n return count;\n };\n\n while (newVisibleCount() < target && page.hasNext()) {\n const nextPage = await page.next();\n if (!nextPage || this._closed) break;\n this._processHistoryPage(nextPage);\n page = nextPage;\n }\n\n const newVisible = this._treeVisibleNodes().filter((n) => !beforeRunIds.has(nodeKey(n)));\n return { newVisible, lastPage: page };\n }\n\n // Spec: AIT-CT11a\n private _releaseWithheld(nodes: ConversationNode<TProjection>[]): void {\n for (const n of nodes) {\n this._withheldRunIds.delete(nodeKey(n));\n }\n if (nodes.length > 0) {\n this._recomputeAndEmit();\n }\n }\n\n // -------------------------------------------------------------------------\n // Private: scoped event forwarding\n // -------------------------------------------------------------------------\n\n private _updateVisibleSnapshot(nodes?: ConversationNode<TProjection>[]): void {\n const resolved = nodes ?? this._cachedNodes;\n // Identity key = nodeKey (runId for reply runs, codecMessageId for inputs),\n // so the visible set scopes events for both kinds and input-node parents.\n this._lastVisibleNodeKeys = resolved.map((n) => nodeKey(n));\n this._lastVisibleNodeKeySet = new Set(this._lastVisibleNodeKeys);\n this._lastVisibleProjections = resolved.map((n) => n.projection);\n this._lastVisibleMessagePairs = this._extractMessages(resolved);\n }\n\n private _onTreeUpdate(): void {\n // Suppress update forwarding while processing history pages. During\n // _processHistoryPage, each tree.applyMessage() fires this handler\n // synchronously — but _withheldRunIds hasn't been populated yet, so\n // _computeFlatNodes() would return unfiltered history. Without this guard,\n // subscribers briefly see all history Runs before the pagination window\n // is applied. The final update is emitted by _releaseWithheld after\n // withholding is set up.\n if (this._processingHistory) return;\n\n // The Tree emits `update` only on structural change (new/removed Run,\n // sort-reorder, startSerial promotion, run-start backfill), so every\n // update reaching here warrants a full re-walk. Content-only folds flow\n // through `output` (_onTreeOutput) instead.\n\n // Pin selections for previously-visible Runs that now have siblings.\n // This prevents new forks (from other views' edits/regenerates) from\n // shifting this view to a branch the user didn't navigate to.\n this._pinBranchSelections();\n this._resolvePendingRegenSelections();\n\n this._recomputeAndEmitIfChanged();\n }\n\n /**\n * Build the unified selection map the Tree's `visibleNodes` consumes:\n * `groupRootKey -> selectedKey`, covering both edit forks (input-node groups,\n * keyed by the input group root) and regenerate groups (reply-run groups,\n * keyed by the original reply's group root). Pending entries (no chosen\n * member yet) are omitted so the Tree falls back to the latest sibling.\n * @returns The merged group-root → selected-key map.\n */\n private _resolveSelections(): Map<string, string> {\n const resolved = new Map<string, string>();\n for (const [groupRoot, sel] of this._branchSelections) {\n resolved.set(groupRoot, sel.selectedKey);\n }\n for (const [groupRoot, sel] of this._regenSelections) {\n if (sel.kind === 'pending') continue;\n resolved.set(groupRoot, sel.selectedRunId);\n }\n return resolved;\n }\n\n /**\n * The Tree's visible node chain under this view's current selections — the\n * reachable, sibling-resolved nodes before the View's pagination window is\n * applied.\n * @returns The selection-resolved visible node chain.\n */\n private _treeVisibleNodes(): ConversationNode<TProjection>[] {\n return this._tree.visibleNodes(this._resolveSelections());\n }\n\n /**\n * For each previously-visible Run that now has siblings but no explicit\n * selection, pin the selection to that Run's runId. This preserves the\n * current branch when new forks appear from other views or external\n * sources.\n *\n * Exception: if the fork was initiated by this view (tracked as a\n * `pending` BranchSelection), select the newest sibling (the awaited Run)\n * instead of pinning the old one.\n */\n private _pinBranchSelections(): void {\n for (const key of this._lastVisibleNodeKeys) {\n const node = this._tree.getNode(key);\n // Edit forks are INPUT-node sibling groups; only input nodes pin here.\n // Regenerate (reply-run) groups roll forward via _resolvePendingRegenSelections.\n if (node?.kind !== 'input') continue;\n const siblings = this._tree.getSiblingNodes(key);\n if (siblings.length <= 1) continue;\n const groupRoot = this._tree.getGroupRoot(key);\n const existing = this._branchSelections.get(groupRoot);\n\n // Spec: AIT-CT13f — external edit fork: pin to the currently-visible\n // sibling so a fork from another view doesn't drift this view's branch.\n if (existing) continue;\n this._branchSelections.set(groupRoot, { kind: 'pinned', selectedKey: key });\n }\n }\n\n /**\n * Roll `pending` and `auto` regenerate selections forward to the newest\n * group member. A regenerate slot defaults to the latest member, so each\n * new regenerator (this view's awaited run, or an external one) auto-rolls\n * the slot forward — UNLESS the user explicitly selected an earlier member\n * (`user`), which pins and is left untouched. The agent mints the run-id, so\n * we can't match the awaited run by id — once the group grows we adopt the\n * newest as the selected member.\n */\n private _resolvePendingRegenSelections(): void {\n for (const [groupRoot, sel] of this._regenSelections) {\n if (sel.kind === 'user') continue;\n const group = this._tree.getSiblingNodes(groupRoot).filter((n): n is RunNode<TProjection> => n.kind === 'run');\n if (group.length <= 1) continue;\n const newest = group.at(-1);\n if (!newest) continue;\n this._regenSelections.set(groupRoot, { kind: 'auto', selectedRunId: newest.runId });\n }\n }\n\n private _onTreeAblyMessage(msg: Ably.InboundMessage): void {\n // Re-emit only if the message corresponds to a visible Run\n const headers = getTransportHeaders(msg);\n const codecMessageId = headers[HEADER_CODEC_MESSAGE_ID];\n const runId = headers[HEADER_RUN_ID];\n\n if (!codecMessageId && !runId) {\n // Lifecycle / control events with no run/message identity (cancel, error)\n // are always forwarded.\n this._emitter.emit('ably-message', msg);\n return;\n }\n\n if (runId && this._lastVisibleNodeKeySet.has(runId)) {\n this._emitter.emit('ably-message', msg);\n }\n }\n\n private _onTreeRun(event: RunLifecycleEvent): void {\n // Check if the run is already on the visible branch.\n if (this._lastVisibleNodeKeySet.has(event.runId)) {\n this._emitter.emit('run', event);\n return;\n }\n\n // For run-start, use branch metadata to predict visibility before\n // messages arrive. Own runs have optimistic inserts (caught above).\n // Remote runs carry parent/forkOf from the agent.\n if (event.type === 'start' && this._isRunStartVisible(event)) {\n this._lastVisibleNodeKeySet.add(event.runId);\n this._emitter.emit('run', event);\n }\n }\n\n /**\n * Predict whether a run-start's messages will be visible on this view's\n * branch using the parent/forkOf metadata from the event.\n * @param event - The run-start lifecycle event.\n * @returns True if the run is expected to be visible on this view's branch.\n */\n private _isRunStartVisible(event: RunLifecycleEvent & { type: 'start' }): boolean {\n const { parent } = event;\n\n // No parent metadata — can't determine branch, forward as default.\n if (parent === undefined) return true;\n\n // The wire `parent` is a codec-message-id (the prior message). Resolve it\n // kind-blind to its owning NODE — an input node (the user prompt this run\n // replies to) or a prior reply run — and check that node's key against the\n // visible set. Input-node keys are populated into the set by\n // _updateVisibleSnapshot.\n const parentNode = this._tree.getNodeByCodecMessageId(parent);\n if (!parentNode) return true; // unknown parent: forward conservatively\n return this._lastVisibleNodeKeySet.has(nodeKey(parentNode));\n }\n\n private _visibleChanged(newNodes: ConversationNode<TProjection>[]): boolean {\n if (newNodes.length !== this._lastVisibleNodeKeys.length) return true;\n for (const [i, node] of newNodes.entries()) {\n if (nodeKey(node) !== this._lastVisibleNodeKeys[i]) return true;\n if (node.projection !== this._lastVisibleProjections[i]) return true;\n }\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a View that projects a paginated window over a Tree.\n * @param options - The tree, channel, codec, and logger to use.\n * @returns A new {@link DefaultView} instance.\n */\nexport const createView = <TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage>(\n options: ViewOptions<TInput, TOutput, TProjection, TMessage>,\n): DefaultView<TInput, TOutput, TProjection, TMessage> => new DefaultView(options);\n","/**\n * Core client-side session, parameterized by codec.\n *\n * Composes the conversation Tree to handle the full client-side lifecycle.\n * `connect()` subscribes to the Ably channel (which implicitly attaches it).\n * The same subscription, decoder, and channel are reused across runs.\n *\n * The client publishes user messages directly to the channel via the shared\n * codec encoder. It does not send HTTP: waking an agent is the application's\n * concern — it POSTs `run.toInvocation().toJSON()` to its own endpoint if and\n * when it wants one woken (the Vercel ChatTransport does this for useChat\n * parity). The agent locates the triggering input event by its `event-id`\n * header and publishes run lifecycle events (run-start, run-end) plus assistant\n * chunks, minting and stamping the invocation-id itself. The channel is the\n * durable session record; agents that weren't running at publish time can\n * resume by reading channel rewind.\n */\n\nimport * as Ably from 'ably';\n\nimport {\n EVENT_CANCEL,\n EVENT_RUN_END,\n HEADER_CODEC_MESSAGE_ID,\n HEADER_ERROR_CODE,\n HEADER_ERROR_MESSAGE,\n HEADER_EVENT_ID,\n HEADER_INPUT_CODEC_MESSAGE_ID,\n HEADER_INVOCATION_ID,\n HEADER_PARENT,\n HEADER_ROLE,\n HEADER_RUN_ID,\n HEADER_RUN_REASON,\n} from '../../constants.js';\nimport { ErrorCode } from '../../errors.js';\nimport { EventEmitter } from '../../event-emitter.js';\nimport type { Logger } from '../../logger.js';\nimport { LogLevel, makeLogger } from '../../logger.js';\nimport { getTransportHeaders } from '../../utils.js';\nimport { registerAgent } from '../agent.js';\nimport type { CodecInputEvent, CodecOutputEvent, Decoder, Encoder } from '../codec/types.js';\nimport { applyWireMessage } from './decode-fold.js';\nimport { buildTransportHeaders } from './headers.js';\nimport { Invocation } from './invocation.js';\nimport type { DefaultTree } from './tree.js';\nimport { createTree } from './tree.js';\nimport type { ActiveRun, ClientSession, ClientSessionOptions, RunEndReason, SendOptions, Tree, View } from './types.js';\nimport { createView, type DefaultView } from './view.js';\n\n/**\n * Returned from `on()` when the session is already closed — the subscription\n * is silently ignored since no further events will fire.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-function -- intentional no-op\nconst noopUnsubscribe = (): void => {};\n\n// ---------------------------------------------------------------------------\n// Internal state machine\n// ---------------------------------------------------------------------------\n\nenum ClientSessionState {\n READY = 'ready',\n CLOSED = 'closed',\n}\n\n// ---------------------------------------------------------------------------\n// Event map for the session's typed EventEmitter\n// ---------------------------------------------------------------------------\n\ninterface ClientSessionEventsMap {\n error: Ably.ErrorInfo;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n// Spec: AIT-CT1\nclass DefaultClientSession<\n TInput extends CodecInputEvent,\n TOutput extends CodecOutputEvent,\n TProjection,\n TMessage,\n> implements ClientSession<TInput, TOutput, TProjection, TMessage> {\n private readonly _channel: Ably.RealtimeChannel;\n private readonly _codec: ClientSessionOptions<TInput, TOutput, TProjection, TMessage>['codec'];\n private readonly _clientId: string | undefined;\n private readonly _logger: Logger;\n\n // Typed event emitter — the session emits only 'error'; all data events live on Tree/View\n private readonly _emitter: EventEmitter<ClientSessionEventsMap>;\n\n // Sub-components\n private readonly _tree: DefaultTree<TInput, TOutput, TProjection>;\n private readonly _view: DefaultView<TInput, TOutput, TProjection, TMessage>;\n private readonly _views = new Set<DefaultView<TInput, TOutput, TProjection, TMessage>>();\n private readonly _decoder: Decoder<TInput, TOutput>;\n /**\n * Shared encoder for the lifetime of the session. The client only ever\n * uses `publishInput` (input wire), so the encoder's stream tracker map\n * stays empty across the session. Closed once on session close.\n */\n private readonly _encoder: Encoder<TInput, TOutput>;\n\n // Spec: AIT-CT10, AIT-CT10a\n readonly tree: Tree<TOutput, TProjection>;\n readonly view: View<TInput, TMessage>;\n\n // Channel subscription is established lazily on connect()\n private _connectPromise: Promise<void> | undefined;\n private readonly _onMessage: (msg: Ably.InboundMessage) => void;\n\n private _state = ClientSessionState.READY;\n private _hasAttachedOnce: boolean;\n private readonly _onChannelStateChange: Ably.channelEventCallback;\n\n /**\n * Backing settlers for each in-flight run's `ActiveRun.runId` promise.\n * Resolved with the agent-minted run-id when the matching `ai-run-start`\n * (fresh send) or `ai-run-resume` (continuation) is observed; rejected if\n * the session closes first. There is no deadline —\n * `send()` resolves on publish and does not block on run-start.\n *\n * Keyed by the triggering input's codec-message-id — the handle the client\n * owns at send time, which the agent echoes back on run-start as\n * `input-codec-message-id`. This is uniform across fresh sends and\n * continuations (a continuation is itself an input event — tool-approval or\n * tool-result — with its own codec-message-id), so reconciliation never\n * depends on a client-minted run/invocation id.\n */\n private readonly _pendingRunStarts = new Map<\n string,\n { resolve: (runId: string) => void; reject: (e: Ably.ErrorInfo) => void }\n >();\n\n constructor(options: ClientSessionOptions<TInput, TOutput, TProjection, TMessage>) {\n // Spec: AIT-CT1a, AIT-CT1a2 — register this SDK on both the connection\n // (options.agents) and channel-attach (params.agent) paths. Idempotent\n // across sessions sharing one client.\n const channelOptions = registerAgent(options.client, options.codec);\n this._channel = options.client.channels.get(options.channelName, channelOptions);\n this._codec = options.codec;\n this._clientId = options.clientId;\n this._logger = (options.logger ?? makeLogger({ logLevel: LogLevel.Silent })).withContext({\n component: 'ClientSession',\n });\n\n this._emitter = new EventEmitter<ClientSessionEventsMap>(this._logger);\n this._hasAttachedOnce = this._channel.state === 'attached';\n\n // Compose sub-components\n this._tree = createTree<TInput, TOutput, TProjection>(this._codec, this._logger);\n this._view = createView<TInput, TOutput, TProjection, TMessage>({\n tree: this._tree,\n channel: this._channel,\n codec: this._codec,\n sendDelegate: this._internalSend.bind(this),\n logger: this._logger,\n onClose: () => this._views.delete(this._view),\n });\n this._decoder = this._codec.createDecoder();\n this._encoder = this._codec.createEncoder(\n this._channel,\n this._clientId === undefined ? undefined : { clientId: this._clientId },\n );\n\n this._views.add(this._view);\n\n // Public accessors (typed as narrow interfaces)\n this.tree = this._tree;\n this.view = this._view;\n\n // Seed tree with initial messages — the session assigns a codecMessageId\n // per seed message. Each seed becomes a run-less input node (no run-id —\n // the client never mints one); the parent chain mirrors the original seed\n // sequence (a user→user input chain the Tree threads kind-blind).\n if (options.messages) {\n let prevMsgId: string | undefined;\n for (const msg of options.messages) {\n const codecMessageId = crypto.randomUUID();\n const seedHeaders: Record<string, string> = {\n [HEADER_CODEC_MESSAGE_ID]: codecMessageId,\n [HEADER_ROLE]: 'user',\n };\n if (prevMsgId) seedHeaders[HEADER_PARENT] = prevMsgId;\n this._tree.applyMessage({ inputs: [this._codec.createUserMessage(msg)], outputs: [] }, seedHeaders);\n prevMsgId = codecMessageId;\n }\n }\n\n // Spec: AIT-CT2\n // Listener function reference — bound now so it can be unsubscribed on close.\n this._onMessage = (ablyMessage: Ably.InboundMessage) => {\n this._handleMessage(ablyMessage);\n };\n\n // Listen for channel state changes that break message continuity.\n // _hasAttachedOnce is seeded from the channel's current state so that\n // pre-attached channels are handled correctly. It distinguishes the\n // initial attach (expected) from a genuine discontinuity.\n this._onChannelStateChange = (stateChange: Ably.ChannelStateChange) => {\n this._handleChannelStateChange(stateChange);\n };\n this._channel.on(this._onChannelStateChange);\n }\n\n // ---------------------------------------------------------------------------\n // Public connection API\n // ---------------------------------------------------------------------------\n\n // Spec: AIT-CT2\n // eslint-disable-next-line @typescript-eslint/promise-function-async -- preserve reference equality across calls\n connect(): Promise<void> {\n if (this._state === ClientSessionState.CLOSED) {\n return Promise.reject(new Ably.ErrorInfo('unable to connect; session is closed', ErrorCode.SessionClosed, 400));\n }\n if (this._connectPromise) return this._connectPromise;\n\n this._logger.trace('DefaultClientSession.connect();');\n // Subscribe before attach (RTL7g) — subscribe implicitly attaches the channel.\n this._connectPromise = this._channel.subscribe(this._onMessage).then(\n () => {\n this._logger.debug('DefaultClientSession.connect(); subscribed and attached');\n },\n (error: unknown) => {\n const errInfo = new Ably.ErrorInfo(\n `unable to subscribe to channel; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.SessionSubscriptionError,\n 500,\n error instanceof Ably.ErrorInfo ? error : undefined,\n );\n this._logger.error('DefaultClientSession.connect(); subscribe failed');\n this._emitter.emit('error', errInfo);\n throw errInfo;\n },\n );\n return this._connectPromise;\n }\n\n private async _requireConnected(method: string): Promise<void> {\n if (!this._connectPromise) {\n throw new Ably.ErrorInfo(\n `unable to ${method}; connect() must be called before ${method}()`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n return this._connectPromise;\n }\n\n // ---------------------------------------------------------------------------\n // Message subscription handler\n // ---------------------------------------------------------------------------\n\n private _handleMessage(ablyMessage: Ably.InboundMessage): void {\n if (this._state === ClientSessionState.CLOSED) return;\n\n try {\n // Spec: AIT-CT16a\n // Live-only: surface an agent error carried on a run-end BEFORE applying\n // it, preserving the original 'error'-before-tree-'run' emit ordering.\n // Consumers that expose a per-run stream (e.g. the Vercel ChatTransport)\n // error their stream off this event. The agent only publishes run-end\n // after run-start, so no pending-run-start tracker is outstanding.\n if (ablyMessage.name === EVENT_RUN_END) {\n const headers = getTransportHeaders(ablyMessage);\n // CAST: agent always writes a valid RunEndReason; default to 'complete' for robustness\n const reason = (headers[HEADER_RUN_REASON] ?? 'complete') as RunEndReason;\n if (reason === 'error') {\n const codeRaw = headers[HEADER_ERROR_CODE];\n const parsedCode = codeRaw === undefined ? Number.NaN : Number(codeRaw);\n const code = Number.isFinite(parsedCode) ? parsedCode : ErrorCode.SessionSubscriptionError;\n const message = headers[HEADER_ERROR_MESSAGE] ?? 'agent reported an error';\n const statusCode = code >= 10000 && code < 60000 ? Math.floor(code / 100) : 500;\n const errInfo = new Ably.ErrorInfo(message, code, statusCode);\n this._logger.error('ClientSession._handleMessage(); agent error received', {\n runId: headers[HEADER_RUN_ID],\n invocationId: headers[HEADER_INVOCATION_ID],\n code,\n });\n this._emitter.emit('error', errInfo);\n }\n }\n\n // Reconstruct the tree via the shared decode-fold engine — the same path\n // the View's history replay uses, so the live loop can't drift from it.\n const event = applyWireMessage(this._tree, this._decoder, ablyMessage);\n\n // Live-only: resolve the pending `runId` promise on a fresh run-start or\n // a continuation run-resume. Key by the echoed `input-codec-message-id`\n // — the mirror of the arming key on `_pendingRunStarts` (see that\n // field's JSDoc). Every send carries at least one input, so the agent\n // always echoes it.\n if (event && (event.type === 'start' || event.type === 'resume')) {\n const startedKey = getTransportHeaders(ablyMessage)[HEADER_INPUT_CODEC_MESSAGE_ID];\n if (startedKey !== undefined) {\n const pending = this._pendingRunStarts.get(startedKey);\n if (pending) {\n this._pendingRunStarts.delete(startedKey);\n // Resolve the run handle's `runId` promise with the agent-minted id.\n pending.resolve(event.runId);\n }\n }\n }\n\n // Emit ably-message AFTER the apply so View subscribers can find the\n // owning node in `_lastVisibleNodeKeySet` (keyed by run-id for reply runs\n // and codec-message-id for inputs), which is refreshed by the tree\n // 'update' events the apply triggers.\n this._tree.emitAblyMessage(ablyMessage);\n } catch (error) {\n const cause = error instanceof Ably.ErrorInfo ? error : undefined;\n this._emitter.emit(\n 'error',\n new Ably.ErrorInfo(\n `unable to process channel message; ${error instanceof Error ? error.message : String(error)}`,\n ErrorCode.SessionSubscriptionError,\n 500,\n cause,\n ),\n );\n }\n }\n\n // ---------------------------------------------------------------------------\n // Channel state change handler\n // ---------------------------------------------------------------------------\n\n // Spec: AIT-CT19, AIT-CT19a\n private _handleChannelStateChange(stateChange: Ably.ChannelStateChange): void {\n if (this._state === ClientSessionState.CLOSED) return;\n\n const { current, resumed } = stateChange;\n\n // Track the initial attach so we don't treat it as a discontinuity\n if (current === 'attached' && !this._hasAttachedOnce) {\n this._hasAttachedOnce = true;\n return;\n }\n\n // Continuity-breaking states:\n // - FAILED, SUSPENDED, DETACHED: no more messages expected (or gap)\n // - ATTACHED with resumed: false (UPDATE): messages were lost\n const continuityLost =\n current === 'failed' || current === 'suspended' || current === 'detached' || (current === 'attached' && !resumed);\n\n if (!continuityLost) return;\n\n this._logger.error('ClientSession._handleChannelStateChange(); channel continuity lost', {\n current,\n resumed,\n previous: stateChange.previous,\n });\n\n const err = new Ably.ErrorInfo(\n `unable to deliver events; channel continuity lost (${current}${current === 'attached' ? ', resumed: false' : ''})`,\n ErrorCode.ChannelContinuityLost,\n 500,\n stateChange.reason,\n );\n\n // Surface the loss via the session `error` event. Consumers that expose a\n // per-run stream (e.g. the Vercel ChatTransport) error their stream off\n // this event; observer-run state lives entirely in the Tree's projection\n // and stays consistent regardless of continuity loss.\n this._emitter.emit('error', err);\n }\n\n // ---------------------------------------------------------------------------\n // Cancel helpers\n // ---------------------------------------------------------------------------\n\n /**\n * Tear down local state for a send whose channel publish failed.\n * Idempotent.\n * @param codecMessageIds - The codec-message-ids of the failed send's\n * optimistic input nodes (the client mints no run-id, so the optimistic\n * inserts are keyed by their codec-message-ids).\n */\n private _cleanupFailedSend(codecMessageIds: string[]): void {\n for (const codecMessageId of codecMessageIds) {\n // Drop the optimistic input node only if the publish never produced a\n // server-assigned serial (i.e. nothing live observed it). A server-acked\n // node is part of the canonical channel state and must stay; the View /\n // observers already see it. A fresh send's optimistic inserts are input\n // nodes (keyed by codec-message-id).\n const node = this._tree.getNodeByCodecMessageId(codecMessageId);\n if (node?.kind === 'input' && node.serial === undefined) {\n // An input node's key is its codec-message-id, so delete by it directly.\n this._tree.delete(node.codecMessageId);\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n // Spec: AIT-CT10b\n createView(): View<TInput, TMessage> {\n if (this._state === ClientSessionState.CLOSED) {\n throw new Ably.ErrorInfo('unable to create view; session is closed', ErrorCode.SessionClosed, 400);\n }\n this._logger.trace('DefaultClientSession.createView();');\n const view = createView<TInput, TOutput, TProjection, TMessage>({\n tree: this._tree,\n channel: this._channel,\n codec: this._codec,\n sendDelegate: this._internalSend.bind(this),\n logger: this._logger,\n onClose: () => this._views.delete(view),\n });\n this._views.add(view);\n return view;\n }\n\n // Spec: AIT-CT3, AIT-CT4\n private async _internalSend(\n input: TInput[],\n sendOptions: SendOptions | undefined,\n parentCodecMessageId: string | undefined,\n ): Promise<ActiveRun> {\n if (this._state === ClientSessionState.CLOSED) {\n throw new Ably.ErrorInfo('unable to send; session is closed', ErrorCode.SessionClosed, 400);\n }\n await this._requireConnected('send');\n // CAST: re-check after await — close() may have been called while waiting for connect.\n // TypeScript's control flow narrows _state after the first check, but the\n // await yields and close() can mutate _state concurrently.\n if ((this._state as ClientSessionState) === ClientSessionState.CLOSED) {\n throw new Ably.ErrorInfo('unable to send; session is closed', ErrorCode.SessionClosed, 400);\n }\n\n // Spec: AIT-CT20\n const state = this._channel.state;\n if (state !== 'attached' && state !== 'attaching') {\n throw new Ably.ErrorInfo(`unable to send; channel is ${state}`, ErrorCode.ChannelNotReady, 400);\n }\n\n this._logger.trace('ClientSession._internalSend();');\n\n const isContinuation = sendOptions?.runId !== undefined;\n\n // The agent mints run-ids, not the client. A fresh send carries no run-id\n // (the agent mints it and echoes it on run-start); only a continuation\n // reuses the existing run-id the caller passed.\n const runId = sendOptions?.runId;\n\n // Spec: AIT-CT3d\n // Auto-compute parent from the visible branch tail when not explicitly\n // provided. The View pre-resolves the codec-message-id of the last visible message\n // since the session is codec-agnostic and can't extract it from TMessage.\n let autoParent: string | undefined;\n if (sendOptions?.parent === undefined && !sendOptions?.forkOf) {\n autoParent = parentCodecMessageId;\n }\n\n const codecMessageIds = new Set<string>();\n interface ItemState {\n input: TInput;\n codecMessageId: string;\n inputEventId: string;\n headers: Record<string, string>;\n /** Inputs that reference an existing codec-message without contributing fresh local content (regenerate, tool resolutions) are wire-only — no optimistic projection fold. Fresh user-messages always fold, even when they pin their own codecMessageId. */\n isWireOnly: boolean;\n }\n const items: ItemState[] = [];\n\n // Per-input wire prep: read routing fields off the input directly, then\n // mint per-event ids and build transport headers. Regenerate inputs are\n // wire-only (no optimistic fold); other inputs fold into the projection\n // optimistically.\n for (const entry of input) {\n const inputEventId = crypto.randomUUID();\n // Use the input's `codecMessageId` when set (e.g. tool resolution\n // targeting the prior assistant); otherwise mint a fresh id.\n const codecMessageId = entry.codecMessageId ?? crypto.randomUUID();\n codecMessageIds.add(codecMessageId);\n\n // Inputs that reference an existing message (regenerate, tool\n // resolutions targeting an assistant) are wire-only — no optimistic\n // fold needed because either the receiving content doesn't\n // materialise on this side (regenerate) or the target already exists\n // and will be amended when the wire echoes back.\n //\n // A fresh `user-message` is never wire-only, even on the rare path\n // where it carries an explicit `codecMessageId`: it is new content that\n // must fold into the local projection immediately. Excluding it here\n // keeps the optimistic user bubble from depending on the channel\n // round-trip. (The session mints the codec-message-id for fresh user\n // messages; the caller's `message.id` is preserved but never used as\n // the correlation key.)\n const isWireOnly =\n entry.kind !== 'user-message' && (entry.kind === 'regenerate' || entry.codecMessageId !== undefined);\n\n // The input's own routing fields override the auto-parent /\n // sendOptions defaults. For regenerate inputs, `target` becomes the\n // `msg-regenerate` wire header. The fork anchor comes from\n // `sendOptions.forkOf` (set by `View.edit`). The transport reads\n // these directly without runtime classification.\n const parent = entry.parent ?? (sendOptions?.parent === undefined ? autoParent : sendOptions.parent);\n const forkOf = sendOptions?.forkOf;\n const regenerates = entry.kind === 'regenerate' ? entry.target : undefined;\n\n const headers = buildTransportHeaders({\n role: 'user',\n runId,\n codecMessageId,\n runClientId: this._clientId,\n ...(parent !== undefined && { parent }),\n ...(forkOf !== undefined && { forkOf }),\n ...(regenerates !== undefined && { regenerates }),\n inputEventId,\n });\n\n // Spec: AIT-CT3c — optimistic fold for non-wire-only inputs.\n if (!isWireOnly) {\n this._tree.applyMessage({ inputs: [entry], outputs: [] }, headers);\n }\n\n items.push({ input: entry, codecMessageId, inputEventId, headers, isWireOnly });\n\n // Spec: AIT-CT3e — chain subsequent inputs off the previous one when\n // auto-parenting is in effect.\n if (!isWireOnly && sendOptions?.parent === undefined && !sendOptions?.forkOf && entry.parent === undefined) {\n autoParent = codecMessageId;\n }\n }\n\n // The trigger event is the last input — the one the agent looks up on the\n // channel via `event-id`, surfaced on `ActiveRun` (and via `toInvocation()`)\n // so the application can point an invocation at it. Its codec-message-id is\n // the handle the client owns at send time; the agent echoes it back on\n // run-start as `input-codec-message-id`, and it keys the run-start tracker.\n const triggerItem = items.at(-1);\n if (triggerItem === undefined) {\n // Every send must carry at least one input — only new input starts or\n // continues a run. The loop above produced no items, so nothing was\n // published or folded optimistically.\n throw new Ably.ErrorInfo(\n 'unable to send; inputs array is empty (include at least one input)',\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n const triggerInputEventId = triggerItem.inputEventId;\n const startedKey = triggerItem.codecMessageId;\n\n // Arm the run-start tracker backing the returned `ActiveRun.runId` promise.\n // The run-start handler resolves it with the agent-minted run-id when this\n // send's `ai-run-start` is observed; close() rejects it on teardown. No\n // deadline — `send()` resolves on publish; callers bound the wait by racing\n // `run.runId` against their own timeout.\n //\n // Key on the arming side mirrors the resolve side — see `_pendingRunStarts`\n // for the full keying invariant. The executor runs synchronously, so the\n // tracker entry is registered before `new Promise` returns.\n const runIdPromise = new Promise<string>((resolve, reject) => {\n this._pendingRunStarts.set(startedKey, { resolve, reject });\n });\n // Suppress unhandled-rejection warnings for callers that never await\n // `run.runId`; the caller still observes the rejection if it does await.\n runIdPromise.catch(() => {\n /* observed via run.runId, if at all */\n });\n\n // Publish each input in original order via the shared encoder. The\n // codec routes user-message inputs into a per-part discrete batch and\n // tool-resolution / regenerate inputs into a single discrete write —\n // all on the `ai-input` wire.\n const publishPromise = (async () => {\n try {\n for (const item of items) {\n await this._encoder.publishInput(item.input, {\n extras: { headers: item.headers },\n messageId: item.codecMessageId,\n ...(this._clientId !== undefined && { clientId: this._clientId }),\n });\n }\n } catch (error) {\n const cause = error instanceof Ably.ErrorInfo ? error : undefined;\n const isPermission = cause?.statusCode === 401 || cause?.statusCode === 403;\n const err = new Ably.ErrorInfo(\n isPermission\n ? `unable to publish events; missing publish capability on the channel`\n : `unable to publish events; ${error instanceof Error ? error.message : String(error)}`,\n isPermission ? ErrorCode.InsufficientCapability : ErrorCode.SessionSendFailed,\n isPermission ? 401 : 500,\n cause,\n );\n this._emitter.emit('error', err);\n // The input never reached the channel — there is no run to wait on.\n // Drop the run-start tracker so close() doesn't later reject an orphan.\n this._pendingRunStarts.delete(startedKey);\n // Continuations didn't insert optimistic nodes, so there is nothing to\n // clear for them — only a fresh send's optimistic input nodes need\n // removing, keyed by their codec-message-ids (the client mints no runId).\n if (!isContinuation) this._cleanupFailedSend([...codecMessageIds]);\n throw err;\n }\n })();\n\n // `send()` resolves once the input is published. The core never sends\n // HTTP — waking an agent is the application's concern. Callers POST\n // `run.toInvocation().toJSON()` to their endpoint if they want one woken,\n // and await `run.runId` if they need to know it was picked up.\n await publishPromise;\n\n return {\n inputCodecMessageId: startedKey,\n runId: runIdPromise,\n inputEventId: triggerInputEventId,\n // The agent mints the run-id, so a fresh run has none until run-start.\n // Cancel synchronously by the triggering input's codec-message-id (the\n // handle the client owns at send time, = `inputCodecMessageId`): the\n // agent resolves it to the run once its input-event lookup completes, and\n // buffers a cancel that arrives before then so an early cancel is honoured\n // rather than dropped. A continuation additionally carries its known\n // run-id so the agent can match the run directly.\n cancel: async () => {\n await this._publishCancel({\n inputCodecMessageId: startedKey,\n ...(runId !== undefined && { runId }),\n });\n },\n optimisticCodecMessageIds: [...codecMessageIds],\n toInvocation: () =>\n // The invocation body carries no run-id: run identity lives on the\n // channel (the agent mints a fresh run-id, or reads a continuation's\n // from the triggering input event, which carries the reused run-id).\n Invocation.fromJSON({\n inputEventId: triggerInputEventId,\n sessionName: this._channel.name,\n }),\n };\n }\n\n // Spec: AIT-CT7, AIT-CT7a\n async cancel(runId: string): Promise<void> {\n return this._publishCancel({ runId });\n }\n\n /**\n * Publish an `ai-cancel` signal. The agent resolves the target run by\n * whichever identifier is present:\n *\n * - `runId` — a continuation, whose run-id the caller already knows.\n * - `inputCodecMessageId` — a fresh send, whose run-id the agent mints at\n * run-start. The client can only key the cancel by the triggering input's\n * codec-message-id (the `ActiveRun.inputCodecMessageId`) it owns at send\n * time; the agent resolves it to the run once its input-event lookup\n * completes, buffering a cancel that arrives before then.\n *\n * Both may be present (a continuation knows its run-id AND published an\n * input). An `event-id` is always stamped so channel rewind redelivers the\n * cancel to a per-request / serverless agent that attaches after it was\n * published.\n *\n * Publishing the cancel signal is all the core does. The consumer-facing\n * stream (if any) lives in the layer that built it — e.g. the Vercel\n * ChatTransport closes its stream on cancel — and the Tree's RunNode is left\n * intact so late agent events (a cancel append, a trailing\n * `status: cancelled`) still fold into the Run's projection.\n * @param target - The run identifier(s) to cancel. At least one of `runId` /\n * `inputCodecMessageId` must be set.\n * @param target.runId - The run-id to cancel (continuations).\n * @param target.inputCodecMessageId - The triggering input's\n * codec-message-id to cancel (fresh sends, before run-start).\n */\n private async _publishCancel(target: { runId?: string; inputCodecMessageId?: string }): Promise<void> {\n if (this._state === ClientSessionState.CLOSED) return;\n await this._requireConnected('cancel');\n // CAST: re-check after await — close() may have been called while waiting for connect.\n if ((this._state as ClientSessionState) === ClientSessionState.CLOSED) return;\n this._logger.debug('ClientSession._publishCancel();', {\n runId: target.runId,\n inputCodecMessageId: target.inputCodecMessageId,\n });\n\n const headers: Record<string, string> = {\n // Stamp a per-cancel event-id so channel rewind redelivers this cancel\n // to an agent that attaches after it was published.\n [HEADER_EVENT_ID]: crypto.randomUUID(),\n };\n if (target.runId !== undefined) headers[HEADER_RUN_ID] = target.runId;\n if (target.inputCodecMessageId !== undefined) headers[HEADER_INPUT_CODEC_MESSAGE_ID] = target.inputCodecMessageId;\n\n await this._channel.publish({\n name: EVENT_CANCEL,\n extras: { ai: { transport: headers } },\n });\n }\n\n // Spec: AIT-CT8, AIT-CT8c, AIT-CT8d\n on(event: 'error', handler: (error: Ably.ErrorInfo) => void): () => void {\n if (this._state === ClientSessionState.CLOSED) return noopUnsubscribe;\n // CAST: the overload signature enforces the correct handler type.\n const cb = handler;\n this._emitter.on(event, cb);\n return () => {\n this._emitter.off(event, cb);\n };\n }\n\n // Spec: AIT-CT12, AIT-CT12b, AIT-CT10c\n async close(): Promise<void> {\n if (this._state === ClientSessionState.CLOSED) return;\n this._state = ClientSessionState.CLOSED;\n this._logger.info('ClientSession.close();');\n\n if (this._connectPromise) {\n this._channel.unsubscribe(this._onMessage);\n }\n this._channel.off(this._onChannelStateChange);\n\n this._emitter.off();\n for (const v of this._views) v.close();\n this._views.clear();\n // Reject any in-flight `run.runId` promises so callers awaiting run-start\n // settle rather than hang.\n if (this._pendingRunStarts.size > 0) {\n const closedErr = new Ably.ErrorInfo('unable to await run-start; session closed', ErrorCode.SessionClosed, 400);\n for (const pending of this._pendingRunStarts.values()) {\n pending.reject(closedErr);\n }\n this._pendingRunStarts.clear();\n }\n\n // Best-effort encoder close — flushes any pending stream operations.\n // The client only uses the discrete input path (publishInput), so this is\n // typically a no-op, but it releases any internal resources cleanly.\n try {\n await this._encoder.close();\n } catch {\n // Swallow: encoder close is best-effort during teardown\n }\n\n // Detach the channel this session attached. connect() subscribes (which\n // implicitly attaches), so we only detach when connect() ran. Best-effort:\n // a detach failure (e.g. the channel is already FAILED) must not throw out\n // of close().\n if (this._connectPromise) {\n try {\n await this._channel.detach();\n } catch (error) {\n // Swallowed (see above): a detach failure must not throw out of\n // close(). Logged at debug for observability.\n this._logger.debug('ClientSession.close(); channel detach failed', { error });\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a client-side session that manages conversation state over an Ably channel.\n *\n * The caller owns the client's lifecycle; the session owns its channel.\n * The session is created in a not-yet-connected state — callers must\n * `await session.connect()` before `send`, `regenerate`, `edit`, `update`,\n * or `cancel`.\n * @param options - Configuration for the client session.\n * @returns A new {@link ClientSession} instance.\n */\nexport const createClientSession = <\n TInput extends CodecInputEvent,\n TOutput extends CodecOutputEvent,\n TProjection,\n TMessage,\n>(\n options: ClientSessionOptions<TInput, TOutput, TProjection, TMessage>,\n): ClientSession<TInput, TOutput, TProjection, TMessage> => new DefaultClientSession(options);\n","/**\n * Encoder core — message append lifecycle machinery.\n *\n * Provides Ably primitives (publish, append, close, cancel, flush) that\n * domain-specific encoders wire their event types to.\n *\n * Domain encoders call `createEncoderCore(writer, options)` and use the\n * returned core to map domain events to Ably operations without\n * reimplementing the message append lifecycle.\n */\n\nimport * as Ably from 'ably';\n\nimport {\n HEADER_CODEC_MESSAGE_ID,\n HEADER_DISCRETE,\n HEADER_STATUS,\n HEADER_STREAM,\n HEADER_STREAM_ID,\n} from '../../constants.js';\nimport { ErrorCode } from '../../errors.js';\nimport type { Logger } from '../../logger.js';\nimport { mergeHeaders } from '../../utils.js';\nimport type { ChannelWriter, EncoderOptions, Extras, MessagePayload, StreamPayload, WriteOptions } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/** Options for creating an encoder core. Extends {@link EncoderOptions} with a logger. */\nexport interface EncoderCoreOptions extends EncoderOptions {\n /** Logger instance for diagnostic output. */\n logger?: Logger;\n}\n\n// ---------------------------------------------------------------------------\n// Stream tracker (internal)\n// ---------------------------------------------------------------------------\n\ninterface StreamState {\n serial: string;\n name: string;\n streamId: string;\n accumulated: string;\n /** Transport-tier headers repeated on every append (`extras.ai.transport`). */\n persistentTransport: Record<string, string>;\n /** Codec-tier headers repeated on every append (`extras.ai.codec`). */\n persistentCodec: Record<string, string>;\n cancelled: boolean;\n}\n\n/**\n * The SDK's `extras.ai` namespace as written to the wire: a `transport` tier\n * (always present on SDK-published messages) and an optional `codec` tier.\n */\ninterface AiExtras {\n transport: Record<string, string>;\n codec?: Record<string, string>;\n}\n\ninterface PendingAppend {\n promise: Promise<Ably.UpdateDeleteResult>;\n streamId: string;\n}\n\n// ---------------------------------------------------------------------------\n// Encoder core interface\n// ---------------------------------------------------------------------------\n\n/** The core encoder primitives that domain codec encoders delegate to. */\nexport interface EncoderCore {\n /** Publish a single discrete (non-streaming) message described by a payload. */\n publishDiscrete(payload: MessagePayload, opts?: WriteOptions): Promise<Ably.PublishResult>;\n\n /** Publish multiple discrete messages atomically in a single channel publish. */\n publishDiscreteBatch(payloads: MessagePayload[], opts?: WriteOptions): Promise<Ably.PublishResult>;\n\n /** Start a streamed message with status:streaming. */\n startStream(streamId: string, payload: StreamPayload, opts?: WriteOptions): Promise<void>;\n\n /**\n * Append data to an in-flight streamed message. Fire-and-forget: errors are\n * collected internally and surfaced by {@link closeStream}, {@link cancelStream},\n * {@link cancelAllStreams} or {@link close}.\n * @throws {Ably.ErrorInfo} InvalidArgument if there is no active stream for `streamId` or the core is closed.\n */\n appendStream(streamId: string, data: string): void;\n\n /**\n * Close a streamed message with status:complete. Flushes all pending\n * appends for recovery before returning. Repeats persistent and payload headers.\n * @throws {Ably.ErrorInfo} InvalidArgument if there is no active stream for `streamId`, or the encoder has been closed; EncoderRecoveryFailed if a failed append cannot be recovered during the flush.\n */\n closeStream(streamId: string, payload: StreamPayload): Promise<void>;\n\n /**\n * Cancel a single in-progress stream (status:cancelled) and flush all\n * pending appends for recovery before returning.\n */\n cancelStream(streamId: string, opts?: WriteOptions): Promise<void>;\n\n /**\n * Cancel all in-progress streams (status:cancelled) and flush all\n * pending appends for recovery before returning.\n */\n cancelAllStreams(opts?: WriteOptions): Promise<void>;\n\n /** Flush + clear trackers. Idempotent. */\n close(): Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// Default implementation\n// ---------------------------------------------------------------------------\n\n// Spec: AIT-CD1\nclass DefaultEncoderCore implements EncoderCore {\n private readonly _writer: ChannelWriter;\n private readonly _defaultClientId: string | undefined;\n private readonly _defaultExtras: Extras | undefined;\n private readonly _onMessageHook: (message: Ably.Message) => void;\n private readonly _logger: Logger | undefined;\n private readonly _trackers = new Map<string, StreamState>();\n private _pending: PendingAppend[] = [];\n private _flushPromise: Promise<void> | undefined;\n private _closed = false;\n\n constructor(writer: ChannelWriter, options: EncoderCoreOptions = {}) {\n this._writer = writer;\n this._defaultClientId = options.clientId;\n this._defaultExtras = options.extras;\n this._onMessageHook =\n options.onMessage ??\n (() => {\n /* noop */\n });\n this._logger = options.logger?.withContext({ component: 'EncoderCore' });\n }\n\n // Spec: AIT-CD11\n async publishDiscrete(payload: MessagePayload, opts?: WriteOptions): Promise<Ably.PublishResult> {\n this._assertNotClosed();\n this._logger?.trace('DefaultEncoderCore.publishDiscrete();', { name: payload.name });\n const msg = this._buildDiscreteMessage(payload, opts);\n return this._writer.publish(msg);\n }\n\n // Spec: AIT-CD11a\n async publishDiscreteBatch(payloads: MessagePayload[], opts?: WriteOptions): Promise<Ably.PublishResult> {\n this._assertNotClosed();\n this._logger?.trace('DefaultEncoderCore.publishDiscreteBatch();', { count: payloads.length });\n const msgs = payloads.map((p) => this._buildDiscreteMessage(p, opts, true));\n return this._writer.publish(msgs);\n }\n\n // Spec: AIT-CD2\n async startStream(streamId: string, payload: StreamPayload, opts?: WriteOptions): Promise<void> {\n this._assertNotClosed();\n this._logger?.trace('DefaultEncoderCore.startStream();', { name: payload.name, streamId });\n\n const transport = this._buildTransport(payload.transportHeaders, opts);\n transport[HEADER_STREAM] = 'true';\n transport[HEADER_STATUS] = 'streaming';\n transport[HEADER_STREAM_ID] = streamId;\n const codec = payload.codecHeaders ?? {};\n\n const clientId = this._resolveClientId(opts);\n const msg: Ably.Message = {\n name: payload.name,\n data: payload.data,\n extras: { ai: this._aiExtras(transport, codec) },\n ...(clientId ? { clientId } : {}),\n };\n\n this._invokeOnMessage(msg);\n const result = await this._writer.publish(msg);\n const serial = result.serials[0];\n\n // Spec: AIT-CD2a\n if (!serial) {\n throw new Ably.ErrorInfo(\n `unable to start stream; no serial returned for stream '${payload.name}' (streamId: ${streamId})`,\n ErrorCode.BadRequest,\n 400,\n );\n }\n\n this._trackers.set(streamId, {\n serial,\n name: payload.name,\n streamId,\n accumulated: payload.data,\n persistentTransport: transport,\n persistentCodec: codec,\n cancelled: false,\n });\n\n this._logger?.debug('DefaultEncoderCore.startStream(); stream started', {\n name: payload.name,\n streamId,\n serial,\n });\n }\n\n // Spec: AIT-CD3\n appendStream(streamId: string, data: string): void {\n this._assertNotClosed();\n // Spec: AIT-CD3a\n const tracker = this._trackers.get(streamId);\n if (!tracker) {\n throw new Ably.ErrorInfo(\n `unable to append to stream; no active stream for streamId '${streamId}'`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n\n tracker.accumulated += data;\n\n const appendMsg: Ably.Message = {\n serial: tracker.serial,\n data,\n extras: { ai: this._aiExtras({ ...tracker.persistentTransport }, { ...tracker.persistentCodec }) },\n };\n\n this._invokeOnMessage(appendMsg);\n const p = this._writer.appendMessage(appendMsg);\n this._pending.push({ promise: p, streamId });\n }\n\n // Spec: AIT-CD4, AIT-CD4a\n async closeStream(streamId: string, payload: StreamPayload): Promise<void> {\n this._assertNotClosed();\n this._logger?.trace('DefaultEncoderCore.closeStream();', { streamId });\n\n const tracker = this._trackers.get(streamId);\n if (!tracker) {\n throw new Ably.ErrorInfo(\n `unable to close stream; no active stream for streamId '${streamId}'`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n\n // Accumulate closing data so recovery has the full content\n tracker.accumulated += payload.data;\n\n const { transport, codec } = this._buildClosing(tracker, payload);\n transport[HEADER_STATUS] = 'complete';\n\n const msg: Ably.Message = {\n serial: tracker.serial,\n data: payload.data,\n extras: { ai: this._aiExtras(transport, codec) },\n };\n\n this._invokeOnMessage(msg);\n const p = this._writer.appendMessage(msg);\n this._pending.push({ promise: p, streamId });\n\n await this._flushPending();\n\n this._logger?.debug('DefaultEncoderCore.closeStream(); stream closed', { streamId });\n }\n\n // Spec: AIT-CD5, AIT-CD5b\n async cancelStream(streamId: string, opts?: WriteOptions): Promise<void> {\n this._assertNotClosed();\n this._logger?.trace('DefaultEncoderCore.cancelStream();', { streamId });\n\n const tracker = this._trackers.get(streamId);\n if (!tracker) {\n throw new Ably.ErrorInfo(\n `unable to cancel stream; no active stream for streamId '${streamId}'`,\n ErrorCode.InvalidArgument,\n 400,\n );\n }\n\n tracker.cancelled = true;\n\n const { transport, codec } = this._buildClosing(tracker, undefined, opts);\n transport[HEADER_STATUS] = 'cancelled';\n\n const msg: Ably.Message = {\n serial: tracker.serial,\n data: '',\n extras: { ai: this._aiExtras(transport, codec) },\n };\n\n this._invokeOnMessage(msg);\n const p = this._writer.appendMessage(msg);\n this._pending.push({ promise: p, streamId });\n\n await this._flushPending();\n\n this._logger?.debug('DefaultEncoderCore.cancelStream(); stream cancelled', { streamId });\n }\n\n // Spec: AIT-CD5a\n async cancelAllStreams(opts?: WriteOptions): Promise<void> {\n this._assertNotClosed();\n this._logger?.trace('DefaultEncoderCore.cancelAllStreams();', { streamCount: this._trackers.size });\n\n for (const tracker of this._trackers.values()) {\n tracker.cancelled = true;\n\n const { transport, codec } = this._buildClosing(tracker, undefined, opts);\n transport[HEADER_STATUS] = 'cancelled';\n\n const msg: Ably.Message = {\n serial: tracker.serial,\n data: '',\n extras: { ai: this._aiExtras(transport, codec) },\n };\n\n this._invokeOnMessage(msg);\n const p = this._writer.appendMessage(msg);\n this._pending.push({ promise: p, streamId: tracker.streamId });\n }\n\n await this._flushPending();\n }\n\n // Spec: AIT-CD6\n private async _flushPending(): Promise<void> {\n // Re-entrancy guard: if a flush is already in progress, await it instead of starting a new one.\n if (this._flushPromise) {\n return this._flushPromise;\n }\n\n const snapshot = this._pending;\n this._pending = [];\n\n if (snapshot.length === 0) return;\n\n this._logger?.trace('DefaultEncoderCore._flushPending();', { count: snapshot.length });\n\n this._flushPromise = this._doFlush(snapshot);\n try {\n await this._flushPromise;\n } finally {\n this._flushPromise = undefined;\n }\n }\n\n private async _doFlush(snapshot: PendingAppend[]): Promise<void> {\n const results = await Promise.allSettled(snapshot.map(async (p) => p.promise));\n const failures = new Set<string>();\n\n for (const [i, result] of results.entries()) {\n const entry = snapshot[i];\n if (entry && result.status === 'rejected') {\n failures.add(entry.streamId);\n }\n }\n\n if (failures.size === 0) {\n this._logger?.debug('DefaultEncoderCore._flushPending(); all appends succeeded');\n return;\n }\n\n this._logger?.warn('DefaultEncoderCore._flushPending(); recovering failed appends', {\n failedStreams: [...failures],\n });\n\n const recoveryErrors: { streamId: string; error: unknown }[] = [];\n\n for (const streamId of failures) {\n const tracker = this._trackers.get(streamId);\n if (!tracker) continue;\n\n const recoveryStatus = tracker.cancelled ? 'cancelled' : 'complete';\n const msg: Ably.Message = {\n serial: tracker.serial,\n data: tracker.accumulated,\n extras: {\n ai: this._aiExtras(\n { ...tracker.persistentTransport, [HEADER_STATUS]: recoveryStatus },\n { ...tracker.persistentCodec },\n ),\n },\n };\n\n try {\n await this._writer.updateMessage(msg);\n } catch (error) {\n recoveryErrors.push({ streamId, error });\n }\n }\n\n if (recoveryErrors.length > 0) {\n const ids = recoveryErrors.map((e) => e.streamId).join(', ');\n this._logger?.error('DefaultEncoderCore._flushPending(); recovery failed', { failedStreams: ids });\n throw new Ably.ErrorInfo(\n `unable to flush pending appends; recovery failed for stream(s): ${ids}`,\n ErrorCode.EncoderRecoveryFailed,\n 500,\n );\n }\n }\n\n // Spec: AIT-CD12\n async close(): Promise<void> {\n if (this._closed) return;\n this._logger?.trace('DefaultEncoderCore.close();');\n this._closed = true;\n try {\n await this._flushPending();\n } finally {\n this._trackers.clear();\n }\n this._logger?.debug('DefaultEncoderCore.close(); encoder closed');\n }\n\n // -------------------------------------------------------------------------\n // Private helpers\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CD14\n private _invokeOnMessage(msg: Ably.Message): void {\n try {\n this._onMessageHook(msg);\n } catch (error) {\n this._logger?.error('DefaultEncoderCore._invokeOnMessage(); hook threw', { error });\n }\n }\n\n private _assertNotClosed(): void {\n if (this._closed) {\n throw new Ably.ErrorInfo('unable to write to encoder; encoder has been closed', ErrorCode.InvalidArgument, 400);\n }\n }\n\n private _resolveClientId(opts?: WriteOptions): string | undefined {\n return opts?.clientId ?? this._defaultClientId;\n }\n\n /**\n * Build the transport-tier header record for a message: caller-configured\n * transport headers (default extras + per-write overrides) layered with any\n * transport headers the codec payload stamps directly, plus the message-id.\n * @param payloadTransport - Transport headers carried on the codec payload.\n * @param opts - Optional per-write overrides.\n * @returns The transport-tier headers record (`extras.ai.transport`).\n */\n private _buildTransport(\n payloadTransport: Record<string, string> | undefined,\n opts?: WriteOptions,\n ): Record<string, string> {\n const callerHeaders = mergeHeaders(this._defaultExtras?.headers, opts?.extras?.headers);\n const transport = { ...callerHeaders, ...payloadTransport };\n if (opts?.messageId !== undefined) {\n transport[HEADER_CODEC_MESSAGE_ID] = opts.messageId;\n }\n return transport;\n }\n\n /**\n * Assemble the `extras.ai` namespace from its two tiers, omitting the codec\n * tier when empty.\n * @param transport - Transport-tier headers (always present on SDK messages).\n * @param codec - Codec-tier headers; omitted from the wire when empty.\n * @returns The `extras.ai` object.\n */\n private _aiExtras(transport: Record<string, string>, codec: Record<string, string>): AiExtras {\n return Object.keys(codec).length > 0 ? { transport, codec } : { transport };\n }\n\n private _buildDiscreteMessage(payload: MessagePayload, opts?: WriteOptions, discrete = false): Ably.Message {\n const transport = this._buildTransport(payload.transportHeaders, opts);\n transport[HEADER_STREAM] = 'false';\n if (discrete) {\n // Mark batch-published payloads as discrete message parts (from writeMessages).\n // The decoder relies on this header to distinguish message parts from lifecycle\n // events that also happen to be discrete (stream: false).\n transport[HEADER_DISCRETE] = 'true';\n }\n const clientId = this._resolveClientId(opts);\n\n const msg: Ably.Message = {\n name: payload.name,\n data: payload.data,\n extras: {\n ai: this._aiExtras(transport, payload.codecHeaders ?? {}),\n ...(payload.ephemeral ? { ephemeral: true } : {}),\n },\n ...(clientId ? { clientId } : {}),\n };\n\n this._invokeOnMessage(msg);\n return msg;\n }\n\n /**\n * Build both header tiers for a closing append. Closing appends must repeat\n * ALL persistent headers (Ably replaces the entire extras object on append).\n * Then layer caller and codec overrides.\n * @param tracker - The stream tracker with persistent headers.\n * @param payload - The closing stream payload (codec + transport headers).\n * @param opts - Optional per-write overrides.\n * @returns The two tiers for the closing append.\n */\n private _buildClosing(\n tracker: StreamState,\n payload: StreamPayload | undefined,\n opts?: WriteOptions,\n ): { transport: Record<string, string>; codec: Record<string, string> } {\n const callerHeaders = mergeHeaders(this._defaultExtras?.headers, opts?.extras?.headers);\n const transport = { ...tracker.persistentTransport, ...callerHeaders, ...payload?.transportHeaders };\n const codec = { ...tracker.persistentCodec, ...payload?.codecHeaders };\n return { transport, codec };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create an encoder core bound to the given channel writer.\n * @param writer - The channel writer to publish messages through.\n * @param options - Encoder configuration (clientId, extras, hooks, logger).\n * @returns A new {@link EncoderCore} instance.\n */\nexport const createEncoderCore = (writer: ChannelWriter, options: EncoderCoreOptions = {}): EncoderCore =>\n new DefaultEncoderCore(writer, options);\n","/**\n * Decoder core — action dispatch and serial tracking machinery.\n *\n * Handles the Ably message action patterns (create, append, update, delete)\n * and delegates to domain-specific hooks for event building and discrete\n * event decoding.\n *\n * Domain decoders call `createDecoderCore(hooks, options)` and provide hooks\n * for stream classification, event building, and discrete decoding. Hooks\n * return a flat `TEvent[]` — no event-vs-message union. Per-message routing\n * concerns (`codec-message-id`) are handled by the SDK via `ReducerMeta`, not\n * here.\n */\n\nimport type * as Ably from 'ably';\n\nimport { HEADER_STATUS, HEADER_STREAM, HEADER_STREAM_ID } from '../../constants.js';\nimport type { Logger } from '../../logger.js';\nimport { getCodecHeaders, getTransportHeaders } from '../../utils.js';\nimport type { MessagePayload, StreamTrackerState } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/** Options for creating a decoder core. */\nexport interface DecoderCoreOptions {\n /** Called when a tracked stream is replaced (non-prefix update). Receives the tracker with updated state. */\n onStreamUpdate?: (tracker: StreamTrackerState) => void;\n /** Called when a message is deleted. Receives the serial and tracker (if one exists). */\n onStreamDelete?: (serial: string, tracker: StreamTrackerState | undefined) => void;\n /** Logger instance for diagnostic output. */\n logger?: Logger;\n}\n\n// ---------------------------------------------------------------------------\n// Domain hooks\n// ---------------------------------------------------------------------------\n\n/** Hooks that a domain codec provides to the decoder core for stream classification and event building. */\nexport interface DecoderCoreHooks<TEvent> {\n /**\n * Build domain events emitted when a new stream starts. May return multiple\n * events (e.g. a start event and a start-step event).\n */\n buildStartEvents(tracker: StreamTrackerState): TEvent[];\n\n /** Build domain events for a text delta received on a stream. */\n buildDeltaEvents(tracker: StreamTrackerState, delta: string): TEvent[];\n\n /**\n * Build domain events emitted when a stream completes (status:complete).\n * Not called for cancelled streams. The closing codec headers may differ\n * from tracker.codecHeaders if the closing append carried updated headers.\n */\n buildEndEvents(tracker: StreamTrackerState, closingCodecHeaders: Record<string, string>): TEvent[];\n\n /**\n * Decode a discrete message (a `message.create` whose stream header is not\n * \"true\", or a non-streamable first-contact update). Handles user messages,\n * tool lifecycle, data-*, etc.\n */\n decodeDiscrete(input: MessagePayload): TEvent[];\n}\n\n// ---------------------------------------------------------------------------\n// Interface\n// ---------------------------------------------------------------------------\n\n/** The decoder core returned by {@link createDecoderCore}. */\nexport interface DecoderCore<TEvent> {\n /** Decode a single Ably message into zero or more domain TEvents. */\n decode(message: Ably.InboundMessage): TEvent[];\n}\n\n// ---------------------------------------------------------------------------\n// Default implementation\n// ---------------------------------------------------------------------------\n\n// Spec: AIT-CD7\nclass DefaultDecoderCore<TEvent> implements DecoderCore<TEvent> {\n private readonly _hooks: DecoderCoreHooks<TEvent>;\n private readonly _logger: Logger | undefined;\n private readonly _onStreamUpdate: ((tracker: StreamTrackerState) => void) | undefined;\n private readonly _onStreamDelete: ((serial: string, tracker: StreamTrackerState | undefined) => void) | undefined;\n private readonly _serialState = new Map<string, StreamTrackerState>();\n\n constructor(hooks: DecoderCoreHooks<TEvent>, options: DecoderCoreOptions = {}) {\n this._hooks = hooks;\n this._onStreamUpdate = options.onStreamUpdate;\n this._onStreamDelete = options.onStreamDelete;\n this._logger = options.logger?.withContext({ component: 'DecoderCore' });\n }\n\n decode(message: Ably.InboundMessage): TEvent[] {\n const action = message.action;\n\n this._logger?.trace('DefaultDecoderCore.decode();', { action, serial: message.serial, name: message.name });\n\n switch (action) {\n // Spec: AIT-CD7a\n case 'message.create': {\n const payload = this._toPayload(message);\n return payload.transportHeaders?.[HEADER_STREAM] === 'true'\n ? this._decodeStreamedCreate(payload, message.serial)\n : this._hooks.decodeDiscrete(payload);\n }\n\n case 'message.append': {\n return this._decodeAppend(message);\n }\n\n case 'message.update': {\n return this._decodeUpdate(message);\n }\n\n case 'message.delete': {\n return this._decodeDelete(message);\n }\n\n default: {\n return [];\n }\n }\n }\n\n // -------------------------------------------------------------------------\n // Private: extract MessagePayload\n // -------------------------------------------------------------------------\n\n private _toPayload(message: Ably.InboundMessage): MessagePayload {\n return {\n name: message.name ?? '',\n // CAST: Ably SDK types `data` as `any`; cast to unknown is the safe boundary type.\n data: message.data as unknown,\n transportHeaders: getTransportHeaders(message),\n codecHeaders: getCodecHeaders(message),\n };\n }\n\n /**\n * Extract string data from an Ably message, for stream accumulation paths.\n * @param message - The Ably message to extract string data from.\n * @returns The string data, or empty string if data is not a string.\n */\n private _stringData(message: Ably.InboundMessage): string {\n return typeof message.data === 'string' ? message.data : '';\n }\n\n // -------------------------------------------------------------------------\n // Private: safe callback invocation\n // -------------------------------------------------------------------------\n\n private _invokeOnStreamUpdate(tracker: StreamTrackerState): void {\n if (!this._onStreamUpdate) return;\n try {\n this._onStreamUpdate(tracker);\n } catch (error) {\n this._logger?.error('DefaultDecoderCore._invokeOnStreamUpdate(); callback threw', { error });\n }\n }\n\n private _invokeOnStreamDelete(serial: string, tracker: StreamTrackerState | undefined): void {\n if (!this._onStreamDelete) return;\n try {\n this._onStreamDelete(serial, tracker);\n } catch (error) {\n this._logger?.error('DefaultDecoderCore._invokeOnStreamDelete(); callback threw', { error });\n }\n }\n\n // -------------------------------------------------------------------------\n // Private: streamed message create\n // -------------------------------------------------------------------------\n\n private _decodeStreamedCreate(payload: MessagePayload, serial: string | undefined): TEvent[] {\n if (!serial) return [];\n\n const streamId = payload.transportHeaders?.[HEADER_STREAM_ID] ?? '';\n\n const tracker: StreamTrackerState = {\n name: payload.name,\n streamId,\n accumulated: '',\n codecHeaders: { ...payload.codecHeaders },\n transportHeaders: { ...payload.transportHeaders },\n closed: false,\n };\n this._serialState.set(serial, tracker);\n\n this._logger?.debug('DefaultDecoderCore._decodeStreamedCreate(); new stream', {\n name: payload.name,\n streamId,\n serial,\n });\n\n return this._hooks.buildStartEvents(tracker);\n }\n\n // -------------------------------------------------------------------------\n // Private: append handling\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CD8\n private _decodeAppend(message: Ably.InboundMessage): TEvent[] {\n const serial = message.serial;\n if (!serial) return [];\n\n const tracker = this._serialState.get(serial);\n if (!tracker) {\n // Unknown serial on append — treat as first-contact update\n return this._decodeUpdate(message);\n }\n\n const transport = getTransportHeaders(message);\n const closingCodec = getCodecHeaders(message);\n const delta = typeof message.data === 'string' ? message.data : '';\n const status = transport[HEADER_STATUS];\n const outputs: TEvent[] = [];\n\n if (delta.length > 0) {\n tracker.accumulated += delta;\n outputs.push(...this._hooks.buildDeltaEvents(tracker, delta));\n }\n\n if (status === 'complete' && !tracker.closed) {\n tracker.closed = true;\n outputs.push(...this._hooks.buildEndEvents(tracker, closingCodec));\n this._logger?.debug('DefaultDecoderCore._decodeAppend(); stream complete', { streamId: tracker.streamId });\n } else if (status === 'cancelled' && !tracker.closed) {\n tracker.closed = true;\n this._logger?.debug('DefaultDecoderCore._decodeAppend(); stream cancelled', { streamId: tracker.streamId });\n }\n\n return outputs;\n }\n\n // -------------------------------------------------------------------------\n // Private: update handling (first-contact, prefix-match, replacement)\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CD9\n private _decodeUpdate(message: Ably.InboundMessage): TEvent[] {\n const serial = message.serial;\n if (!serial) return [];\n\n const payload = this._toPayload(message);\n const transport = payload.transportHeaders ?? {};\n const codec = payload.codecHeaders ?? {};\n const isStreamed = transport[HEADER_STREAM] === 'true';\n const status = transport[HEADER_STATUS];\n\n const tracker = this._serialState.get(serial);\n\n if (!tracker) {\n return this._decodeFirstContact(payload, isStreamed, status, serial);\n }\n\n // Updates to tracked streams use string data for prefix-match accumulation\n const data = this._stringData(message);\n\n // --- Tracker exists: prefix-match or replacement ---\n if (data.startsWith(tracker.accumulated)) {\n const delta = data.slice(tracker.accumulated.length);\n const outputs: TEvent[] = [];\n\n if (delta.length > 0) {\n tracker.accumulated = data;\n outputs.push(...this._hooks.buildDeltaEvents(tracker, delta));\n }\n\n if (status === 'complete' && !tracker.closed) {\n tracker.closed = true;\n outputs.push(...this._hooks.buildEndEvents(tracker, codec));\n } else if (status === 'cancelled' && !tracker.closed) {\n tracker.closed = true;\n }\n\n return outputs;\n }\n\n // --- Replacement (NOT a prefix match) ---\n tracker.accumulated = data;\n tracker.codecHeaders = { ...codec };\n tracker.transportHeaders = { ...transport };\n\n this._invokeOnStreamUpdate(tracker);\n\n return [];\n }\n\n private _decodeFirstContact(\n payload: MessagePayload,\n isStreamed: boolean,\n status: string | undefined,\n serial: string,\n ): TEvent[] {\n // Non-streamed messages are discrete\n if (!isStreamed) {\n return this._hooks.decodeDiscrete(payload);\n }\n\n const streamId = payload.transportHeaders?.[HEADER_STREAM_ID] ?? '';\n const codec = payload.codecHeaders ?? {};\n const data = typeof payload.data === 'string' ? payload.data : '';\n\n this._logger?.debug('DefaultDecoderCore._decodeFirstContact(); first-contact stream', {\n name: payload.name,\n streamId,\n serial,\n });\n\n // Create tracker\n const newTracker: StreamTrackerState = {\n name: payload.name,\n streamId,\n accumulated: data,\n codecHeaders: { ...codec },\n transportHeaders: { ...payload.transportHeaders },\n closed: status === 'complete' || status === 'cancelled',\n };\n this._serialState.set(serial, newTracker);\n\n // Emit start + delta (if any) + end (if complete)\n const outputs = this._hooks.buildStartEvents(newTracker);\n\n if (data.length > 0) {\n outputs.push(...this._hooks.buildDeltaEvents(newTracker, data));\n }\n\n if (status === 'complete') {\n outputs.push(...this._hooks.buildEndEvents(newTracker, codec));\n }\n\n return outputs;\n }\n\n // -------------------------------------------------------------------------\n // Private: delete handling\n // -------------------------------------------------------------------------\n\n // Spec: AIT-CD10\n private _decodeDelete(message: Ably.InboundMessage): TEvent[] {\n const serial = message.serial;\n if (!serial) return [];\n\n const tracker = this._serialState.get(serial);\n\n this._invokeOnStreamDelete(serial, tracker);\n\n if (tracker) {\n tracker.accumulated = '';\n tracker.closed = true;\n }\n\n this._logger?.debug('DefaultDecoderCore._decodeDelete();', { serial });\n\n return [];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a decoder core with the given domain hooks.\n * @param hooks - Domain-specific hooks for stream classification, event building, and discrete decoding.\n * @param options - Decoder configuration (callbacks, logger).\n * @returns A new {@link DecoderCore} instance.\n */\nexport const createDecoderCore = <TEvent>(\n hooks: DecoderCoreHooks<TEvent>,\n options: DecoderCoreOptions = {},\n): DecoderCore<TEvent> => new DefaultDecoderCore(hooks, options);\n","/**\n * Generic lifecycle tracker for codec decoders.\n *\n * Manages per-scope (typically per-run) tracking of lifecycle phases that\n * must be emitted before content events. When a phase has not been emitted\n * (e.g. mid-stream join), the tracker synthesizes the missing events using\n * codec-provided build functions.\n *\n * Codecs configure the tracker with an ordered list of phases, then compose\n * it into their decoder hooks. The tracker is independent of any specific\n * codec or event type.\n */\n\n// ---------------------------------------------------------------------------\n// Phase configuration\n// ---------------------------------------------------------------------------\n\n/**\n * Configuration for a single lifecycle phase that may need to be\n * synthesized when missing from the wire stream.\n */\nexport interface PhaseConfig<TEvent> {\n /** Unique key identifying this phase (e.g. \"start\", \"start-step\"). */\n key: string;\n /**\n * Build the synthetic event(s) for this phase. Called with a context\n * record that codecs populate at the call site — the tracker passes\n * it through without interpreting it.\n * @param context - Key-value pairs from the call site (e.g. headers).\n * @returns One or more synthetic events to emit for this phase.\n */\n build(context: Record<string, string | undefined>): TEvent[];\n}\n\n// ---------------------------------------------------------------------------\n// Tracker interface\n// ---------------------------------------------------------------------------\n\n/**\n * Per-scope lifecycle tracker that ensures required phases are emitted\n * before content events, synthesizing missing ones for mid-stream joins.\n *\n * Scoped by an arbitrary string key (typically a run ID). Each scope\n * tracks independently which phases have been emitted.\n */\nexport interface LifecycleTracker<TEvent> {\n /**\n * Ensure all configured phases have been emitted for the given scope.\n * Synthesizes and returns the events for any phases not yet marked as\n * emitted, marking each as emitted so it is not synthesized again.\n * Returns an empty array if all phases are already emitted.\n * @param scopeId - The scope to check (e.g. run ID).\n * @param context - Key-value pairs passed through to phase build functions.\n * @returns Synthetic events for missing phases, in configuration order.\n */\n ensurePhases(scopeId: string, context: Record<string, string | undefined>): TEvent[];\n\n /**\n * Mark a phase as emitted from the wire (not synthetic). Call this\n * when the real event arrives so the tracker does not re-synthesize it.\n * @param scopeId - The scope (e.g. run ID).\n * @param phaseKey - The phase key to mark.\n */\n markEmitted(scopeId: string, phaseKey: string): void;\n\n /**\n * Reset a phase so it will be re-synthesized on the next\n * {@link ensurePhases} call. Used for repeating phases (e.g. \"start-step\"\n * resets after \"finish-step\").\n * @param scopeId - The scope (e.g. run ID).\n * @param phaseKey - The phase key to reset.\n */\n resetPhase(scopeId: string, phaseKey: string): void;\n\n /**\n * Remove all tracking state for a scope. Call on run completion\n * (finish, cancel) to free memory.\n * @param scopeId - The scope to clear.\n */\n clearScope(scopeId: string): void;\n}\n\n// ---------------------------------------------------------------------------\n// Default implementation\n// ---------------------------------------------------------------------------\n\n// Spec: AIT-CD13\nclass DefaultLifecycleTracker<TEvent> implements LifecycleTracker<TEvent> {\n private readonly _phases: PhaseConfig<TEvent>[];\n private readonly _emitted = new Map<string, Set<string>>();\n\n constructor(phases: PhaseConfig<TEvent>[]) {\n this._phases = phases;\n }\n\n ensurePhases(scopeId: string, context: Record<string, string | undefined>): TEvent[] {\n const emitted = this._getOrCreate(scopeId);\n const events: TEvent[] = [];\n for (const phase of this._phases) {\n if (!emitted.has(phase.key)) {\n emitted.add(phase.key);\n events.push(...phase.build(context));\n }\n }\n return events;\n }\n\n markEmitted(scopeId: string, phaseKey: string): void {\n this._getOrCreate(scopeId).add(phaseKey);\n }\n\n resetPhase(scopeId: string, phaseKey: string): void {\n this._emitted.get(scopeId)?.delete(phaseKey);\n }\n\n clearScope(scopeId: string): void {\n this._emitted.delete(scopeId);\n }\n\n private _getOrCreate(scopeId: string): Set<string> {\n let set = this._emitted.get(scopeId);\n if (!set) {\n set = new Set();\n this._emitted.set(scopeId, set);\n }\n return set;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a lifecycle tracker configured with the given phases.\n * Phases are checked and synthesized in array order.\n * @param phases - Ordered phase configurations.\n * @returns A new {@link LifecycleTracker} instance.\n */\nexport const createLifecycleTracker = <TEvent>(phases: PhaseConfig<TEvent>[]): LifecycleTracker<TEvent> =>\n new DefaultLifecycleTracker(phases);\n"],"mappings":"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"}