@ably/ai-transport 0.0.1 → 0.1.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 (110) hide show
  1. package/README.md +54 -47
  2. package/dist/ably-ai-transport.js +1006 -539
  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 +4 -0
  7. package/dist/core/codec/types.d.ts +19 -2
  8. package/dist/core/transport/decode-history.d.ts +8 -6
  9. package/dist/core/transport/headers.d.ts +4 -2
  10. package/dist/core/transport/index.d.ts +4 -1
  11. package/dist/core/transport/pipe-stream.d.ts +3 -2
  12. package/dist/core/transport/stream-router.d.ts +11 -1
  13. package/dist/core/transport/tree.d.ts +171 -0
  14. package/dist/core/transport/turn-manager.d.ts +4 -1
  15. package/dist/core/transport/types.d.ts +270 -119
  16. package/dist/core/transport/view.d.ts +166 -0
  17. package/dist/errors.d.ts +19 -2
  18. package/dist/index.d.ts +3 -1
  19. package/dist/react/ably-ai-transport-react.js +1019 -486
  20. package/dist/react/ably-ai-transport-react.js.map +1 -1
  21. package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
  22. package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
  23. package/dist/react/contexts/transport-context.d.ts +31 -0
  24. package/dist/react/contexts/transport-provider.d.ts +49 -0
  25. package/dist/react/create-transport-hooks.d.ts +124 -0
  26. package/dist/react/index.d.ts +14 -8
  27. package/dist/react/use-ably-messages.d.ts +14 -8
  28. package/dist/react/use-active-turns.d.ts +7 -3
  29. package/dist/react/use-client-transport.d.ts +78 -5
  30. package/dist/react/use-create-view.d.ts +22 -0
  31. package/dist/react/use-tree.d.ts +20 -0
  32. package/dist/react/use-view.d.ts +79 -0
  33. package/dist/vercel/ably-ai-transport-vercel.js +1478 -842
  34. package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
  35. package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
  36. package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
  37. package/dist/vercel/codec/tool-transitions.d.ts +50 -0
  38. package/dist/vercel/index.d.ts +3 -0
  39. package/dist/vercel/react/ably-ai-transport-vercel-react.js +9099 -852
  40. package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
  41. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +45 -1
  42. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
  43. package/dist/vercel/react/contexts/chat-transport-context.d.ts +32 -0
  44. package/dist/vercel/react/contexts/chat-transport-provider.d.ts +84 -0
  45. package/dist/vercel/react/index.d.ts +5 -0
  46. package/dist/vercel/react/use-chat-transport.d.ts +61 -20
  47. package/dist/vercel/react/use-message-sync.d.ts +41 -9
  48. package/dist/vercel/react/use-staged-add-tool-approval-response.d.ts +30 -0
  49. package/dist/vercel/tool-approvals.d.ts +124 -0
  50. package/dist/vercel/tool-events.d.ts +26 -0
  51. package/dist/vercel/transport/chat-transport.d.ts +33 -11
  52. package/dist/vercel/transport/index.d.ts +5 -2
  53. package/package.json +23 -17
  54. package/src/constants.ts +6 -0
  55. package/src/core/codec/encoder.ts +10 -1
  56. package/src/core/codec/types.ts +19 -3
  57. package/src/core/transport/client-transport.ts +382 -364
  58. package/src/core/transport/decode-history.ts +229 -81
  59. package/src/core/transport/headers.ts +6 -2
  60. package/src/core/transport/index.ts +13 -5
  61. package/src/core/transport/pipe-stream.ts +8 -5
  62. package/src/core/transport/server-transport.ts +212 -58
  63. package/src/core/transport/stream-router.ts +21 -3
  64. package/src/core/transport/{conversation-tree.ts → tree.ts} +192 -77
  65. package/src/core/transport/turn-manager.ts +28 -10
  66. package/src/core/transport/types.ts +318 -139
  67. package/src/core/transport/view.ts +840 -0
  68. package/src/errors.ts +21 -1
  69. package/src/index.ts +10 -5
  70. package/src/react/contexts/transport-context.ts +37 -0
  71. package/src/react/contexts/transport-provider.tsx +164 -0
  72. package/src/react/create-transport-hooks.ts +144 -0
  73. package/src/react/index.ts +15 -8
  74. package/src/react/use-ably-messages.ts +34 -16
  75. package/src/react/use-active-turns.ts +28 -17
  76. package/src/react/use-client-transport.ts +184 -24
  77. package/src/react/use-create-view.ts +68 -0
  78. package/src/react/use-tree.ts +53 -0
  79. package/src/react/use-view.ts +233 -0
  80. package/src/react/vite.config.ts +4 -1
  81. package/src/vercel/codec/accumulator.ts +64 -79
  82. package/src/vercel/codec/decoder.ts +11 -8
  83. package/src/vercel/codec/encoder.ts +68 -54
  84. package/src/vercel/codec/index.ts +0 -2
  85. package/src/vercel/codec/tool-transitions.ts +122 -0
  86. package/src/vercel/index.ts +17 -0
  87. package/src/vercel/react/contexts/chat-transport-context.ts +40 -0
  88. package/src/vercel/react/contexts/chat-transport-provider.tsx +122 -0
  89. package/src/vercel/react/index.ts +14 -0
  90. package/src/vercel/react/use-chat-transport.ts +164 -42
  91. package/src/vercel/react/use-message-sync.ts +77 -19
  92. package/src/vercel/react/use-staged-add-tool-approval-response.ts +87 -0
  93. package/src/vercel/react/vite.config.ts +4 -2
  94. package/src/vercel/tool-approvals.ts +380 -0
  95. package/src/vercel/tool-events.ts +53 -0
  96. package/src/vercel/transport/chat-transport.ts +225 -79
  97. package/src/vercel/transport/index.ts +14 -3
  98. package/dist/core/transport/conversation-tree.d.ts +0 -9
  99. package/dist/react/use-conversation-tree.d.ts +0 -20
  100. package/dist/react/use-edit.d.ts +0 -7
  101. package/dist/react/use-history.d.ts +0 -19
  102. package/dist/react/use-messages.d.ts +0 -7
  103. package/dist/react/use-regenerate.d.ts +0 -7
  104. package/dist/react/use-send.d.ts +0 -7
  105. package/src/react/use-conversation-tree.ts +0 -71
  106. package/src/react/use-edit.ts +0 -24
  107. package/src/react/use-history.ts +0 -111
  108. package/src/react/use-messages.ts +0 -32
  109. package/src/react/use-regenerate.ts +0 -24
  110. package/src/react/use-send.ts +0 -25
@@ -1 +1 @@
1
- {"version":3,"file":"ably-ai-transport-react.js","names":[],"sources":["../../src/react/use-ably-messages.ts","../../src/constants.ts","../../src/react/use-active-turns.ts","../../src/errors.ts","../../src/event-emitter.ts","../../src/logger.ts","../../src/utils.ts","../../src/core/transport/conversation-tree.ts","../../src/core/transport/decode-history.ts","../../src/core/transport/headers.ts","../../src/core/transport/stream-router.ts","../../src/core/transport/client-transport.ts","../../src/react/use-client-transport.ts","../../src/react/use-conversation-tree.ts","../../src/react/use-edit.ts","../../src/react/use-history.ts","../../src/react/use-messages.ts","../../src/react/use-regenerate.ts","../../src/react/use-send.ts"],"sourcesContent":["/**\n * useAblyMessages — reactive raw Ably message log from a ClientTransport.\n *\n * Returns the accumulated raw Ably InboundMessages in chronological order,\n * including both live messages (from the channel subscription) and\n * history-loaded messages (from transport.history() calls).\n *\n * Subscribes to the transport's \"ably-message\" event and re-reads the\n * list on each update.\n */\n\nimport type * as Ably from 'ably';\nimport { useEffect, useState } from 'react';\n\nimport type { ClientTransport } from '../core/transport/types.js';\n\n/**\n * Subscribe to raw Ably message updates from a client transport.\n * @param transport - The client transport to observe.\n * @returns The accumulated raw Ably messages in chronological order.\n */\nexport const useAblyMessages = <TEvent, TMessage>(\n transport: ClientTransport<TEvent, TMessage>,\n): Ably.InboundMessage[] => {\n const [messages, setMessages] = useState<Ably.InboundMessage[]>(() => transport.getAblyMessages());\n\n useEffect(() => {\n setMessages(transport.getAblyMessages());\n\n const unsub = transport.on('ably-message', () => {\n setMessages(transport.getAblyMessages());\n });\n return unsub;\n }, [transport]);\n\n return messages;\n};\n","/**\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// ---------------------------------------------------------------------------\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// ---------------------------------------------------------------------------\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","/**\n * useActiveTurns: reactive view of active turns on the channel,\n * keyed by clientId.\n *\n * Subscribes to transport turn lifecycle events and maintains a\n * Map<clientId, Set<turnId>> that updates on every turn start/end.\n *\n * Generic — works with any codec, not tied to Vercel types.\n */\n\nimport { useEffect, useState } from 'react';\n\nimport { EVENT_TURN_START } from '../constants.js';\nimport type { ClientTransport, TurnLifecycleEvent } from '../core/transport/types.js';\n\n/**\n * Returns a reactive Map of all active turns on the channel, keyed by clientId.\n * Updates when turns start or end.\n * @param transport - The client transport to observe, or null/undefined if not yet available.\n * @returns A Map where keys are clientIds and values are Sets of active turnIds.\n */\nexport const useActiveTurns = <TEvent, TMessage>(\n transport: ClientTransport<TEvent, TMessage> | null | undefined,\n): Map<string, Set<string>> => {\n const [turns, setTurns] = useState<Map<string, Set<string>>>(() => new Map());\n\n useEffect(() => {\n if (!transport) return;\n\n // Initialize from current state\n setTurns(transport.getActiveTurnIds());\n\n const unsubscribe = transport.on('turn', (event: TurnLifecycleEvent) => {\n setTurns((prev) => {\n const next = new Map(prev);\n\n if (event.type === EVENT_TURN_START) {\n const set = new Set(next.get(event.clientId) ?? []);\n set.add(event.turnId);\n next.set(event.clientId, set);\n } else {\n const set = next.get(event.clientId);\n if (set) {\n set.delete(event.turnId);\n if (set.size === 0) {\n next.delete(event.clientId);\n } else {\n next.set(event.clientId, new Set(set));\n }\n }\n }\n\n return next;\n });\n });\n\n return unsubscribe;\n }, [transport]);\n\n return turns;\n};\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 turn lifecycle event (turn-start or turn-end) failed to publish.\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/**\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 * 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 * 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 * ConversationTree — 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 `flatten()` output once all messages are present.\n *\n * The tree owns conversation state. `flatten()` returns the linear message\n * list for the currently selected branches — this is what the transport's\n * `getMessages()` delegates to.\n */\n\nimport { HEADER_FORK_OF, HEADER_PARENT } from '../../constants.js';\nimport type { Logger } from '../../logger.js';\nimport type { ConversationNode, ConversationTree } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Internal node type\n// ---------------------------------------------------------------------------\n\ninterface InternalNode<TMessage> {\n node: ConversationNode<TMessage>;\n /** Insertion sequence — tiebreaker for null-serial messages. */\n insertSeq: number;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n// Spec: AIT-CT13\nclass DefaultConversationTree<TMessage> implements ConversationTree<TMessage> {\n /** All nodes indexed by msgId (x-ably-msg-id). */\n private readonly _nodeIndex = new Map<string, InternalNode<TMessage>>();\n\n /** Secondary index: codec message key to msgId. Bridges UIMessage.id to x-ably-msg-id. */\n private readonly _codecKeyIndex = new Map<string, string>();\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 /**\n * Selected sibling index at each fork point, keyed by the msgId of\n * the first sibling in the group (the fork target). Default: last.\n */\n private readonly _selections = new Map<string, number>();\n\n private readonly _getKey: (message: TMessage) => string;\n private readonly _logger: Logger;\n\n /** Monotonically increasing counter for insertion sequence. */\n private _seqCounter = 0;\n\n constructor(getKey: (message: TMessage) => string, logger: Logger) {\n this._getKey = getKey;\n this._logger = 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): ConversationNode<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: ConversationNode<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 private _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 flatten(): TMessage[] {\n const result: 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 selectedIdx = this._selections.get(groupRootId) ?? group.length - 1;\n const clamped = Math.max(0, Math.min(selectedIdx, group.length - 1));\n const selected = group[clamped];\n if (!selected) break; // unreachable: clamped is always in bounds\n selectedId = selected.msgId;\n resolvedGroups.set(groupRootId, selectedId);\n }\n if (msgId !== selectedId) {\n continue;\n }\n }\n\n currentPath.add(msgId);\n result.push(node.message);\n }\n\n return result;\n }\n\n getSiblings(msgId: string): TMessage[] {\n return this._getSiblingGroup(msgId).map((n) => n.message);\n }\n\n hasSiblings(msgId: string): boolean {\n return this._getSiblingGroup(msgId).length > 1;\n }\n\n getSelectedIndex(msgId: string): number {\n const group = this._getSiblingGroup(msgId);\n if (group.length <= 1) return 0;\n const groupRootId = this._getGroupRoot(msgId);\n const stored = this._selections.get(groupRootId);\n if (stored !== undefined) return Math.max(0, Math.min(stored, group.length - 1));\n return group.length - 1; // default: latest\n }\n\n // Spec: AIT-CT13c\n select(msgId: string, index: number): void {\n this._logger.debug('ConversationTree.select();', { msgId, index });\n const group = this._getSiblingGroup(msgId);\n if (group.length <= 1) return;\n const groupRootId = this._getGroupRoot(msgId);\n this._selections.set(groupRootId, Math.max(0, Math.min(index, group.length - 1)));\n }\n\n getNode(msgId: string): ConversationNode<TMessage> | undefined {\n return this._nodeIndex.get(msgId)?.node;\n }\n\n getNodeByKey(key: string): ConversationNode<TMessage> | undefined {\n const msgId = this._codecKeyIndex.get(key);\n if (!msgId) return undefined;\n return this._nodeIndex.get(msgId)?.node;\n }\n\n getHeaders(msgId: string): Record<string, string> | undefined {\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 // Maintain codec key → msgId secondary index\n this._codecKeyIndex.set(this._getKey(message), msgId);\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('ConversationTree.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 }\n return;\n }\n\n this._logger.trace('ConversationTree.upsert(); inserting new node', { msgId, parentId, forkOf });\n\n const node: ConversationNode<TMessage> = {\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 }\n\n delete(msgId: string): void {\n const entry = this._nodeIndex.get(msgId);\n if (!entry) return;\n\n this._logger.debug('ConversationTree.delete();', { msgId });\n\n const { node } = entry;\n\n // Clean up secondary index\n const codecKey = this._getKey(node.message);\n if (this._codecKeyIndex.get(codecKey) === msgId) {\n this._codecKeyIndex.delete(codecKey);\n }\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 this._selections.delete(msgId);\n\n // Children are NOT deleted — they become unreachable in flatten()\n // because their parent is no longer on the active path.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a ConversationTree that materializes branching history from a flat oplog.\n * @param getKey - Codec function that returns a stable key for a domain message.\n * @param logger - Logger for diagnostic output.\n * @returns A new {@link ConversationTree} instance.\n */\nexport const createConversationTree = <TMessage>(\n getKey: (message: TMessage) => string,\n logger: Logger,\n): ConversationTree<TMessage> => new DefaultConversationTree(getKey, logger);\n","/**\n * decodeHistory — load conversation history from an Ably channel and\n * return decoded messages as a PaginatedMessages 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 after each page fetch. This handles turns that span\n * page boundaries correctly.\n */\n\nimport type * as Ably from 'ably';\n\nimport { HEADER_MSG_ID, HEADER_TURN_ID } 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 { LoadHistoryOptions, PaginatedMessages } 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 /** Key function for domain messages (codec.getMessageKey). */\n getMessageKey: (message: TMessage) => 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 for discrete messages (writeMessages output), keyed by codec message key.\n const discreteHeaders = new Map<string, Record<string, string>>();\n // Serials for discrete messages, keyed by codec message key.\n const discreteSerials = new Map<string, 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\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\n // Capture headers and serial for discrete messages by codec key.\n for (const output of outputs) {\n if (output.kind === 'message') {\n const key = state.getMessageKey(output.message);\n const existingDiscrete = discreteHeaders.get(key);\n if (!existingDiscrete) {\n discreteHeaders.set(key, { ...headers });\n if (serial) discreteSerials.set(key, serial);\n } else if (Object.keys(headers).length > 0) {\n Object.assign(existingDiscrete, headers);\n }\n }\n }\n }\n\n // Collect completed messages in chronological order (oldest first) by turn.\n const completed: DecodedItem<TMessage>[] = [];\n\n for (const msg of defaultAccumulator.completedMessages) {\n const key = state.getMessageKey(msg);\n completed.push({\n message: msg,\n headers: discreteHeaders.get(key) ?? {},\n serial: discreteSerials.get(key) ?? '',\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 // Discrete messages were already captured by codec key. Accumulated\n // messages need to be matched to the turn's per-msg-id headers.\n const claimedMsgIds = new Set<string>();\n\n // First pass: resolve discrete messages and mark their msg-ids as claimed\n const turnKeyHeaders = new Map<string, Record<string, string>>();\n const turnKeySerials = new Map<string, string>();\n for (const msg of turn.accumulator.completedMessages) {\n const key = state.getMessageKey(msg);\n const discrete = discreteHeaders.get(key);\n if (discrete) {\n turnKeyHeaders.set(key, discrete);\n const dSerial = discreteSerials.get(key);\n if (dSerial) turnKeySerials.set(key, dSerial);\n const mid = discrete[HEADER_MSG_ID];\n if (mid) claimedMsgIds.add(mid);\n }\n }\n\n // Second pass: assign unclaimed msg-id entries to remaining messages\n const unclaimedEntries = [...turn.msgHeaders.entries()].filter(([mid]) => !claimedMsgIds.has(mid));\n let unclaimedIdx = 0;\n\n for (const msg of turn.accumulator.completedMessages) {\n const key = state.getMessageKey(msg);\n const unclaimed = unclaimedEntries[unclaimedIdx];\n if (!turnKeyHeaders.has(key) && unclaimed) {\n const [mid, hdrs] = unclaimed;\n turnKeyHeaders.set(key, hdrs);\n const mSerial = turn.msgSerials.get(mid);\n if (mSerial) turnKeySerials.set(key, mSerial);\n unclaimedIdx++;\n }\n }\n\n for (const msg of turn.accumulator.completedMessages) {\n const key = state.getMessageKey(msg);\n completed.push({\n message: msg,\n headers: turnKeyHeaders.get(key) ?? {},\n serial: turnKeySerials.get(key) ?? '',\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// Fetch Ably pages until we have enough completed messages\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch Ably history pages until we have enough completed messages.\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\n let decodedCount = decodeAll(state).length;\n while (decodedCount < state.returnedCount + limit && ablyPage.hasNext()) {\n state.logger.debug('decodeHistory.fetchUntilLimit(); fetching next page', {\n collected: state.rawMessages.length,\n decoded: decodedCount,\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 decodedCount = decodeAll(state).length;\n }\n};\n\n// ---------------------------------------------------------------------------\n// Build PaginatedMessages result from current state\n// ---------------------------------------------------------------------------\n\n/**\n * Build a PaginatedMessages page 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 messages with a `next()` cursor.\n */\nconst buildResult = <TEvent, TMessage>(\n state: HistoryState<TEvent, TMessage>,\n limit: number,\n): PaginatedMessages<TMessage> => {\n // allCompleted is newest-first. Slice from returnedCount for this page,\n // then reverse to chronological for display.\n const allCompleted = decodeAll(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) => d.message),\n itemHeaders: chronSlice.map((d) => d.headers),\n itemSerials: chronSlice.map((d) => 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 messages.\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<PaginatedMessages<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 getMessageKey: codec.getMessageKey.bind(codec),\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 * 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_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). Null means root.\n * @param opts.forkOf - Forked message's msg-id (for edit/regen).\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 | null;\n forkOf?: 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 return h;\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 or explicit close.\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 was closed. */\n closeStream(turnId: string): 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 /* already closed */\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 * Core client-side transport, parameterized by codec.\n *\n * Composes StreamRouter and ConversationTree 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_CANCEL_ALL,\n HEADER_CANCEL_CLIENT_ID,\n HEADER_CANCEL_OWN,\n HEADER_CANCEL_TURN_ID,\n HEADER_MSG_ID,\n HEADER_PARENT,\n HEADER_ROLE,\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 { createConversationTree } from './conversation-tree.js';\nimport { decodeHistory } from './decode-history.js';\nimport { buildTransportHeaders } from './headers.js';\nimport type { StreamRouter } from './stream-router.js';\nimport { createStreamRouter } from './stream-router.js';\nimport type {\n ActiveTurn,\n CancelFilter,\n ClientTransport,\n ClientTransportOptions,\n CloseOptions,\n ConversationTree,\n LoadHistoryOptions,\n MessageWithHeaders,\n PaginatedMessages,\n SendOptions,\n TurnEndReason,\n TurnLifecycleEvent,\n} from './types.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// Event map for the transport's typed EventEmitter\n// ---------------------------------------------------------------------------\n\ninterface ClientTransportEventsMap {\n message: undefined;\n turn: TurnLifecycleEvent;\n error: Ably.ErrorInfo;\n 'ably-message': undefined;\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 for all transport events\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 clientId per turn for getActiveTurnIds()\n private readonly _turnClientIds = new Map<string, string>();\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 // Raw Ably message log\n private readonly _ablyMessages: Ably.InboundMessage[] = [];\n\n // History pagination: withheld messages hidden from getMessages()\n private readonly _withheldKeys = new Set<string>();\n\n // Sub-components\n private readonly _tree: ConversationTree<TMessage>;\n private readonly _router: StreamRouter<TEvent>;\n private readonly _decoder: StreamDecoder<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 _closed = false;\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 ?? '/api/chat';\n this._credentials = options.credentials;\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\n // Compose sub-components\n this._tree = createConversationTree<TMessage>(this._codec.getMessageKey.bind(this._codec), this._logger);\n this._router = createStreamRouter<TEvent>(this._codec.isTerminal.bind(this._codec), this._logger);\n this._decoder = this._codec.createDecoder();\n\n // Seed tree with initial messages\n if (options.messages) {\n let prevMsgId: string | undefined;\n for (const msg of options.messages) {\n const msgId = this._codec.getMessageKey(msg);\n const seedHeaders: Record<string, string> = {};\n if (prevMsgId) seedHeaders[HEADER_PARENT] = prevMsgId;\n this._tree.upsert(msgId, msg, seedHeaders);\n prevMsgId = msgId;\n }\n this._emitter.emit('message');\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\n // ---------------------------------------------------------------------------\n // Message subscription handler\n // ---------------------------------------------------------------------------\n\n private _handleMessage(ablyMessage: Ably.InboundMessage): void {\n if (this._closed) return;\n\n this._ablyMessages.push(ablyMessage);\n this._emitter.emit('ably-message');\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._turnClientIds.set(turnId, turnCid);\n this._emitter.emit('turn', { type: EVENT_TURN_START, turnId, clientId: turnCid });\n }\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._turnClientIds.delete(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._emitter.emit('turn', { type: EVENT_TURN_END, turnId, clientId: turnCid, reason });\n }\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 // 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 } 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 // 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 key = this._codec.getMessageKey(message);\n const msgId = headers[HEADER_MSG_ID] ?? key;\n this._tree.upsert(msgId, message, headers, serial);\n this._emitter.emit('message');\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 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 this._tree.upsert(\n observer.headers[HEADER_MSG_ID] ?? this._codec.getMessageKey(message),\n message,\n { ...observer.headers },\n observer.serial,\n );\n this._emitter.emit('message');\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 if (filter.all) {\n for (const turnId of this._ownTurnIds) {\n this._router.closeStream(turnId);\n }\n } else if (filter.own) {\n for (const tid of this._ownTurnIds) {\n this._router.closeStream(tid);\n }\n } else if (filter.clientId) {\n for (const [tid, cid] of this._turnClientIds) {\n if (cid === filter.clientId) {\n this._router.closeStream(tid);\n }\n }\n } else if (filter.turnId) {\n this._router.closeStream(filter.turnId);\n }\n }\n\n private _getMatchingTurnIds(filter: CancelFilter): Set<string> {\n const matched = new Set<string>();\n if (filter.all) {\n for (const turnId of this._turnClientIds.keys()) matched.add(turnId);\n } else if (filter.own) {\n for (const [turnId, cid] of this._turnClientIds) {\n if (cid === this._clientId) matched.add(turnId);\n }\n } else if (filter.clientId) {\n for (const [turnId, cid] of this._turnClientIds) {\n if (cid === filter.clientId) matched.add(turnId);\n }\n } else if (filter.turnId && this._turnClientIds.has(filter.turnId)) {\n matched.add(filter.turnId);\n }\n return matched;\n }\n\n // ---------------------------------------------------------------------------\n // Input message helpers\n // ---------------------------------------------------------------------------\n\n private _getMessagesWithHeaders(): MessageWithHeaders<TMessage>[] {\n return this._tree.flatten().map((m) => ({\n message: m,\n headers: this.getMessageHeaders(m),\n }));\n }\n\n /**\n * Compute truncated history: everything before the target message.\n * Used by regenerate so the LLM doesn't see the response being replaced.\n * @param messageId - The msg-id to truncate history before.\n * @returns Input messages preceding the target.\n */\n private _getHistoryBefore(messageId: string): MessageWithHeaders<TMessage>[] {\n const all = this._getMessagesWithHeaders();\n const idx = all.findIndex((inp) => inp.headers?.[HEADER_MSG_ID] === messageId);\n return idx === -1 ? all : all.slice(0, idx);\n }\n\n // ---------------------------------------------------------------------------\n // History pagination helpers\n // ---------------------------------------------------------------------------\n\n private _processHistoryPage(page: PaginatedMessages<TMessage>): void {\n for (const [i, message] of page.items.entries()) {\n const headers = page.itemHeaders?.[i] ?? {};\n const serial = page.itemSerials?.[i];\n const key = this._codec.getMessageKey(message);\n const msgId = headers[HEADER_MSG_ID] ?? key;\n this._tree.upsert(msgId, message, headers, serial);\n }\n this._emitter.emit('message');\n\n // Prepend raw Ably messages (older messages go at the beginning)\n if (page.rawMessages && page.rawMessages.length > 0) {\n this._ablyMessages.unshift(...page.rawMessages);\n this._emitter.emit('ably-message');\n }\n }\n\n private async _loadUntilVisible(\n firstPage: PaginatedMessages<TMessage>,\n target: number,\n beforeKeys: Set<string>,\n ): Promise<{ newVisible: TMessage[]; lastPage: PaginatedMessages<TMessage> }> {\n this._processHistoryPage(firstPage);\n let page = firstPage;\n\n const newVisibleCount = (): number => {\n let count = 0;\n for (const m of this._tree.flatten()) {\n if (!beforeKeys.has(this._codec.getMessageKey(m))) count++;\n }\n return count;\n };\n\n while (newVisibleCount() < target && page.hasNext()) {\n const nextPage = await page.next();\n if (!nextPage) break;\n this._processHistoryPage(nextPage);\n page = nextPage;\n }\n\n const newVisible = this._tree.flatten().filter((m) => !beforeKeys.has(this._codec.getMessageKey(m)));\n return { newVisible, lastPage: page };\n }\n\n private _releaseWithheld(messages: TMessage[]): void {\n for (const m of messages) {\n this._withheldKeys.delete(this._codec.getMessageKey(m));\n }\n if (messages.length > 0) {\n this._emitter.emit('message');\n }\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n // Spec: AIT-CT3, AIT-CT4\n async send(input: TMessage | TMessage[], sendOptions?: SendOptions): Promise<ActiveTurn<TEvent>> {\n if (this._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 _closed to false after the first check, but the\n // await yields and close() can mutate _closed concurrently.\n if (this._closed as boolean) {\n throw new Ably.ErrorInfo('unable to send; transport is closed', ErrorCode.TransportClosed, 400);\n }\n\n this._logger.trace('ClientTransport.send();');\n\n const msgs = Array.isArray(input) ? input : [input];\n const turnId = crypto.randomUUID();\n this._ownTurnIds.add(turnId);\n\n const msgIds = new Set<string>();\n const postMessages: { message: TMessage; headers: Record<string, string> }[] = [];\n\n // Capture history BEFORE optimistic inserts. The optimistic messages are\n // sent in the `messages` field — including them in `history` too would\n // cause the server to see them twice.\n const preInsertHistory = this._getMessagesWithHeaders();\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 flat = this._tree.flatten();\n if (flat.length > 0) {\n const lastMsg = flat.at(-1);\n if (lastMsg) {\n const lastKey = this._codec.getMessageKey(lastMsg);\n const lastNode = this._tree.getNodeByKey(lastKey);\n autoParent = lastNode?.msgId ?? lastKey;\n }\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 ?? undefined);\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 // Include per-message parent so the server chains messages correctly.\n const postHeaders: Record<string, string> = { [HEADER_MSG_ID]: msgId, [HEADER_ROLE]: 'user' };\n if (resolvedParent) postHeaders[HEADER_PARENT] = resolvedParent;\n postMessages.push({ message, headers: postHeaders });\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 };\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 this._emitter.emit(\n 'error',\n 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 );\n this._router.closeStream(turnId);\n }\n })\n .catch((error: unknown) => {\n const cause = error instanceof Ably.ErrorInfo ? error : undefined;\n this._emitter.emit(\n 'error',\n 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 );\n this._router.closeStream(turnId);\n });\n\n return {\n stream,\n turnId,\n cancel: async () => this.cancel({ turnId }),\n };\n }\n\n // Spec: AIT-CT5\n async regenerate(messageId: string, sendOptions?: SendOptions): Promise<ActiveTurn<TEvent>> {\n this._logger.trace('ClientTransport.regenerate();', { messageId });\n\n const node = this._tree.getNode(messageId);\n const parentId = node?.parentId;\n\n return this.send([], {\n ...sendOptions,\n body: {\n history: this._getHistoryBefore(messageId),\n ...sendOptions?.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 sendOptions?: SendOptions,\n ): Promise<ActiveTurn<TEvent>> {\n this._logger.trace('ClientTransport.edit();', { messageId });\n\n const node = this._tree.getNode(messageId);\n const parentId = node?.parentId;\n\n return this.send(newMessages, {\n ...sendOptions,\n body: {\n history: this._getHistoryBefore(messageId),\n ...sendOptions?.body,\n },\n forkOf: messageId,\n parent: parentId,\n });\n }\n\n // Spec: AIT-CT7, AIT-CT7a\n async cancel(filter?: CancelFilter): Promise<void> {\n if (this._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 // Spec: AIT-CT18\n async waitForTurn(filter?: CancelFilter): Promise<void> {\n if (this._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 const handler = (event: TurnLifecycleEvent): void => {\n if (event.type !== EVENT_TURN_END) return;\n remaining.delete(event.turnId);\n if (remaining.size === 0) {\n this._emitter.off('turn', handler);\n resolve();\n }\n };\n this._emitter.on('turn', handler);\n });\n }\n\n // Spec: AIT-CT8, AIT-CT8a, AIT-CT8b, AIT-CT8c, AIT-CT8d\n on(event: 'message' | 'ably-message', handler: () => void): () => void;\n on(event: 'turn', handler: (event: TurnLifecycleEvent) => void): () => void;\n on(event: 'error', handler: (error: Ably.ErrorInfo) => void): () => void;\n on(\n eventName: 'message' | 'turn' | 'error' | 'ably-message',\n handler: (() => void) | ((event: TurnLifecycleEvent) => void) | ((error: Ably.ErrorInfo) => void),\n ): () => void {\n if (this._closed) return noopUnsubscribe;\n // CAST: the overload signatures enforce correct handler types per event name.\n // The implementation must cast to satisfy the EventEmitter's generic callback type.\n const cb = handler as (arg: ClientTransportEventsMap[keyof ClientTransportEventsMap]) => void;\n this._emitter.on(eventName, cb);\n return () => {\n this._emitter.off(eventName, cb);\n };\n }\n\n // Spec: AIT-CT10\n getTree(): ConversationTree<TMessage> {\n return this._tree;\n }\n\n // Spec: AIT-CT17\n getActiveTurnIds(): Map<string, Set<string>> {\n const result = new Map<string, Set<string>>();\n for (const [turnId, cid] of this._turnClientIds) {\n let set = result.get(cid);\n if (!set) {\n set = new Set();\n result.set(cid, set);\n }\n set.add(turnId);\n }\n return result;\n }\n\n getMessageHeaders(message: TMessage): Record<string, string> | undefined {\n const key = this._codec.getMessageKey(message);\n return this._tree.getNodeByKey(key)?.headers;\n }\n\n // Spec: AIT-CT9\n getMessages(): TMessage[] {\n if (this._withheldKeys.size === 0) return this._tree.flatten();\n return this._tree.flatten().filter((m) => !this._withheldKeys.has(this._codec.getMessageKey(m)));\n }\n\n getMessagesWithHeaders(): MessageWithHeaders<TMessage>[] {\n return this._getMessagesWithHeaders();\n }\n\n getAblyMessages(): Ably.InboundMessage[] {\n return [...this._ablyMessages];\n }\n\n // Spec: AIT-CT11, AIT-CT11a, AIT-CT11b, AIT-CT11c\n async history(opts?: LoadHistoryOptions): Promise<PaginatedMessages<TMessage>> {\n if (this._closed) {\n throw new Ably.ErrorInfo('unable to load history; transport is closed', ErrorCode.TransportClosed, 400);\n }\n this._logger.trace('ClientTransport.history();', { limit: opts?.limit });\n const limit = opts?.limit ?? 100;\n\n // Snapshot before loading — everything already in the tree stays visible\n const beforeKeys = new Set(this._tree.flatten().map((m) => this._codec.getMessageKey(m)));\n\n let lastPage = await decodeHistory(this._channel, this._codec, opts, this._logger);\n\n const initial = await this._loadUntilVisible(lastPage, limit, beforeKeys);\n lastPage = initial.lastPage;\n\n // newVisible is chronological (oldest-first from flatten).\n // For \"load older\" pagination: release the NEWEST `limit` now,\n // withhold the older ones for subsequent next() calls.\n const newVisible = initial.newVisible;\n\n // Withhold ALL new visible messages first, then release the newest batch\n for (const m of newVisible) {\n this._withheldKeys.add(this._codec.getMessageKey(m));\n }\n\n const released = newVisible.slice(-limit);\n // Mutable buffer of older messages, drained newest-first by successive next() calls\n const withheldBuffer = newVisible.slice(0, -limit);\n this._releaseWithheld(released);\n\n const buildPage = (items: TMessage[]): PaginatedMessages<TMessage> => ({\n items,\n hasNext: () => withheldBuffer.length > 0 || lastPage.hasNext(),\n next: async () => {\n // Drain withheld buffer first (older messages, released newest-first)\n if (withheldBuffer.length > 0) {\n // Remove and return the newest `limit` items from the buffer\n const batch = withheldBuffer.splice(-limit, limit);\n this._releaseWithheld(batch);\n return buildPage(batch);\n }\n\n // Buffer exhausted — load more pages from decodeHistory\n if (!lastPage.hasNext()) return;\n\n const nextInternal = await lastPage.next();\n if (!nextInternal) return;\n\n // Everything currently in the tree is \"already known\"\n const alreadyKnown = new Set(beforeKeys);\n for (const m of this._tree.flatten()) {\n alreadyKnown.add(this._codec.getMessageKey(m));\n }\n\n const loaded = await this._loadUntilVisible(nextInternal, limit, alreadyKnown);\n lastPage = loaded.lastPage;\n\n const moreVisible = loaded.newVisible;\n for (const m of moreVisible) {\n this._withheldKeys.add(this._codec.getMessageKey(m));\n }\n // Remove and return the newest `limit` items; rest stays in buffer\n const moreBatch = moreVisible.splice(-limit, limit);\n withheldBuffer.push(...moreVisible);\n this._releaseWithheld(moreBatch);\n\n if (moreBatch.length === 0) return;\n return buildPage(moreBatch);\n },\n });\n\n return buildPage(released);\n }\n\n // Spec: AIT-CT12, AIT-CT12a, AIT-CT12b\n async close(options?: CloseOptions): Promise<void> {\n if (this._closed) return;\n this._closed = true;\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\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 this._ownTurnIds.clear();\n this._ownMsgIds.clear();\n this._turnMsgIds.clear();\n this._turnClientIds.clear();\n this._withheldKeys.clear();\n this._ablyMessages.length = 0;\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 * useClientTransport: creates and memoizes a core ClientTransport instance\n * across renders.\n *\n * Stores the instance in a ref so the same transport is returned on every render.\n * The transport manages its own Ably channel subscription in the constructor —\n * this hook adds no subscription logic.\n *\n * The hook does NOT auto-close the transport on unmount. Channel lifecycle is\n * managed by the Ably provider (useChannel), which detaches the channel and\n * clears all subscriptions. Auto-closing would break React Strict Mode\n * (double-mount calls close() on the first cleanup, leaving a dead transport\n * on the second mount). Call transport.close() explicitly if you need to tear\n * down the transport independently of the channel lifecycle.\n */\n\nimport { useRef } from 'react';\n\nimport { createClientTransport } from '../core/transport/client-transport.js';\nimport type { ClientTransport, ClientTransportOptions } from '../core/transport/types.js';\n\n/**\n * Create and memoize a {@link ClientTransport} across renders.\n * @param options - Configuration for the client transport.\n * @returns The memoized transport instance.\n */\nexport const useClientTransport = <TEvent, TMessage>(\n options: ClientTransportOptions<TEvent, TMessage>,\n): ClientTransport<TEvent, TMessage> => {\n const transportRef = useRef<ClientTransport<TEvent, TMessage> | null>(null);\n\n if (transportRef.current === null) {\n transportRef.current = createClientTransport(options);\n }\n\n return transportRef.current;\n};\n","/**\n * useConversationTree — reactive branch navigation for a ClientTransport.\n *\n * Subscribes to the transport's \"message\" notification and provides\n * branch navigation primitives (getSiblings, selectSibling, hasSiblings)\n * backed by the transport's ConversationTree.\n *\n * Branch selection state is local to the hook instance — each component\n * (or tab) can navigate branches independently.\n */\n\nimport { useCallback, useEffect, useState } from 'react';\n\nimport type { ClientTransport } from '../core/transport/types.js';\n\n/** Handle for navigating the branching conversation tree. */\nexport interface ConversationTreeHandle<TMessage> {\n /** Linear message list for the currently selected branch. */\n messages: TMessage[];\n /** Get all sibling messages at a fork point. */\n getSiblings: (msgId: string) => TMessage[];\n /** Whether a message has siblings (should show navigation arrows). */\n hasSiblings: (msgId: string) => boolean;\n /** Index of the currently selected sibling. */\n getSelectedIndex: (msgId: string) => number;\n /** Navigate to a sibling. Triggers re-render with updated messages. */\n selectSibling: (msgId: string, index: number) => void;\n}\n\n/**\n * Subscribe to transport message updates and provide branch navigation primitives.\n * @param transport - The client transport whose conversation tree to navigate.\n * @returns A {@link ConversationTreeHandle} with the current messages and navigation methods.\n */\nexport const useConversationTree = <TEvent, TMessage>(\n transport: ClientTransport<TEvent, TMessage>,\n): ConversationTreeHandle<TMessage> => {\n const [messages, setMessages] = useState<TMessage[]>(() => transport.getMessages());\n\n useEffect(() => {\n setMessages(transport.getMessages());\n\n const unsub = transport.on('message', () => {\n setMessages(transport.getMessages());\n });\n return unsub;\n }, [transport]);\n\n const getSiblings = useCallback((msgId: string) => transport.getTree().getSiblings(msgId), [transport]);\n\n const hasSiblings = useCallback((msgId: string) => transport.getTree().hasSiblings(msgId), [transport]);\n\n const getSelectedIndex = useCallback((msgId: string) => transport.getTree().getSelectedIndex(msgId), [transport]);\n\n const selectSibling = useCallback(\n (msgId: string, index: number) => {\n transport.getTree().select(msgId, index);\n // flatten() returns a new array after select(), triggering re-render.\n setMessages(transport.getMessages());\n },\n [transport],\n );\n\n return {\n messages,\n getSiblings,\n hasSiblings,\n getSelectedIndex,\n selectSibling,\n };\n};\n","/**\n * useEdit — stable callback for editing a user message.\n *\n * Delegates to `transport.edit()`, which automatically computes\n * `forkOf`, `parent`, and history from the conversation tree.\n */\n\nimport { useCallback } from 'react';\n\nimport type { ActiveTurn, ClientTransport, SendOptions } from '../core/transport/types.js';\n\n/**\n * Return a stable `edit` callback bound to the given transport.\n * @param transport - The client transport to edit through.\n * @returns A function that edits a user message and returns an {@link ActiveTurn} handle.\n */\nexport const useEdit = <TEvent, TMessage>(\n transport: ClientTransport<TEvent, TMessage>,\n): ((messageId: string, newMessages: TMessage | TMessage[], options?: SendOptions) => Promise<ActiveTurn<TEvent>>) =>\n useCallback(\n async (messageId: string, newMessages: TMessage | TMessage[], options?: SendOptions): Promise<ActiveTurn<TEvent>> =>\n transport.edit(messageId, newMessages, options),\n [transport],\n );\n","/**\n * useHistory — history pagination handle for a ClientTransport.\n *\n * Returns a `HistoryHandle` with `load()`, `next()`, `hasNext`, and\n * `loading` — mirroring the transport's `history()` and\n * `PaginatedMessages` API.\n *\n * The transport's `history()` is branch-aware: `limit` means \"keep loading\n * until N new messages appear on the selected branch.\" Messages on\n * unselected branches are loaded into the tree but not counted toward the\n * limit. The returned `items` contain only the newly visible messages.\n *\n * When `options` are provided, auto-loads the first page on mount\n * (SWR-style: options present = enabled). When omitted or null,\n * no auto-load — call `load()` manually.\n *\n * Usage:\n * ```tsx\n * // Auto-load on mount\n * const history = useHistory(transport, { limit: 30 });\n *\n * // Manual load (e.g. on button press)\n * const history = useHistory(transport);\n * // ...later: await history.load({ limit: 30 });\n *\n * // Scroll-back\n * if (history.hasNext) await history.next();\n * ```\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport type { ClientTransport, LoadHistoryOptions, PaginatedMessages } from '../core/transport/types.js';\n\n/** Handle for paginated history loading. */\nexport interface HistoryHandle {\n /** Are there older pages available? False until `load()` has been called. */\n hasNext: boolean;\n /** Is a page being fetched? */\n loading: boolean;\n /** Load the first page (or re-load with different options). Inserts into the conversation tree. */\n load: (options?: LoadHistoryOptions) => Promise<void>;\n /** Fetch the next (older) page. No-op if loading or no more pages. Inserts into the conversation tree. */\n next: () => Promise<void>;\n}\n\n/**\n * Paginated history handle for a client transport.\n * @param transport - The client transport to load history from, or null/undefined if not yet available.\n * @param options - When provided, auto-loads the first page on mount. Omit or pass null for manual loading.\n * @returns A {@link HistoryHandle} for loading and paginating through history.\n */\nexport const useHistory = <TEvent, TMessage>(\n transport: ClientTransport<TEvent, TMessage> | null | undefined,\n options?: LoadHistoryOptions | null,\n): HistoryHandle => {\n const [hasNext, setHasNext] = useState(false);\n const [loading, setLoading] = useState(false);\n const loadingRef = useRef(false);\n const pageRef = useRef<PaginatedMessages<TMessage> | null>(null);\n const transportRef = useRef(transport);\n transportRef.current = transport;\n\n const load = useCallback(async (loadOptions?: LoadHistoryOptions) => {\n if (!transportRef.current || loadingRef.current) return;\n loadingRef.current = true;\n setLoading(true);\n try {\n const page = await transportRef.current.history(loadOptions);\n pageRef.current = page;\n setHasNext(page.hasNext());\n } finally {\n loadingRef.current = false;\n setLoading(false);\n }\n }, []);\n\n const next = useCallback(async () => {\n const page = pageRef.current;\n if (!page || !page.hasNext() || loadingRef.current || !transportRef.current) return;\n\n loadingRef.current = true;\n setLoading(true);\n try {\n const older = await page.next();\n if (older) {\n pageRef.current = older;\n setHasNext(older.hasNext());\n } else {\n setHasNext(false);\n }\n } finally {\n loadingRef.current = false;\n setLoading(false);\n }\n }, []);\n\n // Auto-load first page on mount when options are provided (SWR-style).\n const autoLoad = options !== undefined && options !== null;\n const autoLoadedRef = useRef(false);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n useEffect(() => {\n if (!autoLoad || autoLoadedRef.current || !transportRef.current) return;\n autoLoadedRef.current = true;\n void load(optionsRef.current ?? undefined);\n }, [autoLoad, load]);\n\n return { hasNext, loading, load, next };\n};\n","/**\n * useMessages — reactive message list from a ClientTransport.\n *\n * Subscribes to the transport's \"message\" notification and returns\n * the current message list as React state. Replaces the manual\n * useState + useEffect + on(\"message\") + getMessages() pattern.\n */\n\nimport { useEffect, useState } from 'react';\n\nimport type { ClientTransport } from '../core/transport/types.js';\n\n/**\n * Subscribe to transport message updates and return the current message list.\n * @param transport - The client transport to observe.\n * @returns The current list of decoded messages.\n */\nexport const useMessages = <TEvent, TMessage>(transport: ClientTransport<TEvent, TMessage>): TMessage[] => {\n const [messages, setMessages] = useState<TMessage[]>(() => transport.getMessages());\n\n useEffect(() => {\n // Sync initial state in case the transport already has messages\n setMessages(transport.getMessages());\n\n const unsub = transport.on('message', () => {\n setMessages(transport.getMessages());\n });\n return unsub;\n }, [transport]);\n\n return messages;\n};\n","/**\n * useRegenerate — stable callback for regenerating an assistant message.\n *\n * Delegates to `transport.regenerate()`, which automatically computes\n * `forkOf`, `parent`, and truncated history from the conversation tree.\n */\n\nimport { useCallback } from 'react';\n\nimport type { ActiveTurn, ClientTransport, SendOptions } from '../core/transport/types.js';\n\n/**\n * Return a stable `regenerate` callback bound to the given transport.\n * @param transport - The client transport to regenerate through.\n * @returns A function that regenerates an assistant message and returns an {@link ActiveTurn} handle.\n */\nexport const useRegenerate = <TEvent, TMessage>(\n transport: ClientTransport<TEvent, TMessage>,\n): ((messageId: string, options?: SendOptions) => Promise<ActiveTurn<TEvent>>) =>\n useCallback(\n async (messageId: string, options?: SendOptions): Promise<ActiveTurn<TEvent>> =>\n transport.regenerate(messageId, options),\n [transport],\n );\n","/**\n * useSend — stable callback for sending messages through a ClientTransport.\n *\n * Returns a `send` function that sends one or more messages in a single\n * turn via `transport.send()`. Callers construct the domain messages\n * themselves; the hook provides a stable reference suitable for React deps.\n */\n\nimport { useCallback } from 'react';\n\nimport type { ActiveTurn, ClientTransport, SendOptions } from '../core/transport/types.js';\n\n/**\n * Return a stable `send` callback bound to the given transport.\n * @param transport - The client transport to send through.\n * @returns A function that sends messages and returns an {@link ActiveTurn} handle.\n */\nexport const useSend = <TEvent, TMessage>(\n transport: ClientTransport<TEvent, TMessage>,\n): ((messages: TMessage[], options?: SendOptions) => Promise<ActiveTurn<TEvent>>) =>\n useCallback(\n async (messages: TMessage[], options?: SendOptions): Promise<ActiveTurn<TEvent>> =>\n transport.send(messages, options),\n [transport],\n );\n"],"mappings":"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"}
1
+ {"version":3,"file":"ably-ai-transport-react.js","names":[],"sources":["../../src/react/contexts/transport-context.ts","../../src/constants.ts","../../src/errors.ts","../../src/event-emitter.ts","../../src/logger.ts","../../src/utils.ts","../../src/core/transport/headers.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/react/contexts/transport-provider.tsx","../../src/react/use-ably-messages.ts","../../src/react/use-active-turns.ts","../../src/react/use-client-transport.ts","../../src/react/use-view.ts","../../src/react/use-create-view.ts","../../src/react/use-tree.ts","../../src/react/create-transport-hooks.ts"],"sourcesContent":["import type * as Ably from 'ably';\nimport { createContext } from 'react';\n\nimport type { ClientTransport } from '../../core/transport/types.js';\n\n/**\n * A single entry in the transport registry, holding the transport and any\n * error that occurred during its construction.\n *\n * `transport` is `undefined` when construction failed.\n * `error` is set when `createClientTransport` threw during provider render.\n */\nexport interface TransportSlot {\n /** The constructed transport, or `undefined` if construction failed. */\n transport: ClientTransport<unknown, unknown> | undefined;\n /** Construction error from `createClientTransport`, or `undefined` on success. */\n error: Ably.ErrorInfo | undefined;\n}\n\n/** The shape of the TransportContext value — a record of channelName → slot. */\nexport type TransportContextValue = Readonly<Record<string, TransportSlot>>;\n\n/**\n * Context that holds the registered {@link ClientTransport} slots, keyed by channelName.\n * Each slot contains the transport (or `undefined` on construction failure) and any error.\n * Populated by {@link TransportProvider}; read by {@link useClientTransport}.\n */\nexport const TransportContext = createContext<TransportContextValue>({});\n\n/**\n * Context that holds the nearest (innermost) transport slot.\n * Each {@link TransportProvider} sets this to its own slot, so descendants\n * can access the nearest transport without knowing its channel name.\n * `undefined` when no provider is present.\n * Read by hooks whose `transport` argument is omitted.\n */\nexport const NearestTransportContext = createContext<TransportSlot | undefined>(undefined);\n","/**\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 * 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 * 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 * 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 * TransportProvider: creates a ClientTransport and makes it available to\n * descendants via TransportContext.\n *\n * Wraps children with Ably's ChannelProvider so the underlying channel\n * lifecycle is managed in one place. An inner component calls useChannel\n * to get the stable channel reference and creates the transport once on\n * first render (via useRef).\n *\n * If createClientTransport throws, the error is stored in the TransportSlot\n * (alongside an undefined transport) so that useClientTransport can surface it\n * as transportError without crashing the component tree.\n *\n * The transport is closed when the provider truly unmounts. The close is\n * scheduled as a microtask so that React Strict Mode's synchronous\n * remount cycle (mount → fake-unmount → remount) can cancel it before it\n * fires, avoiding unnecessary transport teardown in development.\n *\n * Multiple TransportProviders can be nested using distinct channelNames.\n * Each provider merges its slot into the parent record so descendants\n * can access all registered transports via useClientTransport(channelName).\n */\n\nimport * as Ably from 'ably';\nimport { ChannelProvider, useChannel } from 'ably/react';\nimport { type PropsWithChildren, type ReactNode, useContext, useEffect, useMemo, useRef } from 'react';\n\nimport { createClientTransport } from '../../core/transport/client-transport.js';\nimport type { ClientTransport, ClientTransportOptions } from '../../core/transport/types.js';\nimport { ErrorCode } from '../../errors.js';\nimport type { TransportSlot } from '../contexts/transport-context.js';\nimport { NearestTransportContext, TransportContext } from '../contexts/transport-context.js';\n\n/**\n * Props for {@link TransportProvider}.\n *\n * All {@link ClientTransportOptions} except `channel` (managed internally) plus `channelName`.\n */\nexport interface TransportProviderProps<TEvent, TMessage>\n extends Omit<ClientTransportOptions<TEvent, TMessage>, 'channel'>, PropsWithChildren {\n /** The Ably channel name to subscribe to. Also used as the context registry key. */\n channelName: string;\n}\n\n// Inner component: rendered inside ChannelProvider so useChannel resolves to\n// the channel created by the outer wrapper.\nconst TransportProviderInner = <TEvent, TMessage>({\n channelName,\n children,\n ...transportOptions\n}: TransportProviderProps<TEvent, TMessage>) => {\n const { channel } = useChannel({ channelName });\n const transportRef = useRef<ClientTransport<TEvent, TMessage> | undefined>(undefined);\n const transportChannelRef = useRef<string>(channelName);\n const transportsToDisposeRef = useRef<ClientTransport<unknown, unknown>[]>([]);\n const pendingCloseRef = useRef(false);\n const constructionErrorRef = useRef<Ably.ErrorInfo | undefined>(undefined);\n\n const alreadyCreatedOrFailed = !!transportRef.current || !!constructionErrorRef.current;\n\n if (!alreadyCreatedOrFailed || transportChannelRef.current !== channelName) {\n transportChannelRef.current = channelName;\n if (transportRef.current) transportsToDisposeRef.current.push(transportRef.current);\n try {\n transportRef.current = createClientTransport({ ...transportOptions, channel });\n constructionErrorRef.current = undefined;\n } catch (error) {\n transportRef.current = undefined;\n constructionErrorRef.current =\n error instanceof Ably.ErrorInfo\n ? error\n : new Ably.ErrorInfo('Unknown error while creating transport', ErrorCode.BadRequest, 400);\n }\n }\n\n const parentMap = useContext(TransportContext);\n\n // Capture ref values as locals so useMemo deps track changes correctly.\n // CAST: TransportContext stores transports with erased generics.\n // The generic types are fixed at the TransportProvider<TEvent, TMessage> boundary.\n const currentTransport = transportRef.current as ClientTransport<unknown, unknown> | undefined;\n const currentError = constructionErrorRef.current;\n\n const slot = useMemo<TransportSlot>(\n () => ({ transport: currentTransport, error: currentError }),\n [currentTransport, currentError],\n );\n\n const contextValue = useMemo(() => ({ ...parentMap, [channelName]: slot }), [channelName, parentMap, slot]);\n\n useEffect(\n () => () => {\n for (const transport of transportsToDisposeRef.current) void transport.close();\n },\n [channelName],\n );\n\n // Close the transport when the component truly unmounts. The close is\n // scheduled as a microtask: in React Strict Mode (dev) the component\n // remounts synchronously before any microtask can drain, so the remount's\n // effect setup resets pendingCloseRef.current = false and cancels the\n // close. On a real unmount no remount follows, the microtask fires, and\n // the transport is closed.\n useEffect(() => {\n pendingCloseRef.current = false;\n return () => {\n pendingCloseRef.current = true;\n void Promise.resolve().then(() => {\n if (pendingCloseRef.current) {\n void transportRef.current?.close();\n }\n });\n };\n }, []);\n\n return (\n <TransportContext.Provider value={contextValue}>\n <NearestTransportContext.Provider value={slot}>{children}</NearestTransportContext.Provider>\n </TransportContext.Provider>\n );\n};\n\n/**\n * Provide a {@link ClientTransport} to descendant components.\n *\n * Wraps children with Ably's `ChannelProvider` using `channelName`, creates a\n * transport from the resolved channel and the remaining options, and registers it\n * in `TransportContext` under `channelName`. Descendants call\n * {@link useClientTransport} with the same `channelName` to access the transport.\n *\n * If `createClientTransport` throws during construction, the error is surfaced\n * through `useClientTransport` as `transportError` — the component tree does not\n * crash and children are still rendered.\n *\n * ```tsx\n * <TransportProvider channelName=\"ai:demo\" codec={UIMessageCodec}>\n * <Chat />\n * </TransportProvider>\n *\n * // Inside Chat:\n * const { transport, transportError } = useClientTransport({ channelName: 'ai:demo' });\n * ```\n *\n * For multiple transports, nest providers with distinct channelNames:\n *\n * ```tsx\n * <TransportProvider channelName=\"ai:main\" codec={UIMessageCodec}>\n * <TransportProvider channelName=\"ai:aux\" codec={UIMessageCodec}>\n * <App />\n * </TransportProvider>\n * </TransportProvider>\n *\n * // Inside App:\n * const { transport: main } = useClientTransport({ channelName: 'ai:main' });\n * const { transport: aux } = useClientTransport({ channelName: 'ai:aux' });\n * ```\n * @param props - Provider configuration including `channelName`, `codec`, and all other {@link ClientTransportOptions}.\n * @returns A React element wrapping children with ChannelProvider and TransportContext.\n */\nexport const TransportProvider = <TEvent, TMessage>(props: TransportProviderProps<TEvent, TMessage>): ReactNode => (\n <ChannelProvider channelName={props.channelName}>\n <TransportProviderInner {...props} />\n </ChannelProvider>\n);\n","/**\n * useAblyMessages — reactive raw Ably message log from a ClientTransport.\n *\n * Accumulates raw Ably InboundMessages from the transport's tree\n * 'ably-message' event. Messages are appended in arrival order.\n *\n * When `transport` is omitted, defaults to the nearest\n * {@link TransportProvider}'s transport via context.\n * Pass `skip: true` to bypass all subscriptions and return an empty array.\n */\n\nimport type * as Ably from 'ably';\nimport { useContext, useEffect, useRef, useState } from 'react';\n\nimport type { ClientTransport } from '../core/transport/types.js';\nimport { NearestTransportContext } from './contexts/transport-context.js';\n\n/**\n * Subscribe to raw Ably message updates from a client transport's tree.\n * When `transport` is omitted, uses the nearest {@link TransportProvider}'s transport via context.\n * @param props - Options including optional `transport` and `skip`.\n * @param props.transport - Transport to subscribe to; defaults to the nearest provider.\n * @param props.skip - When `true`, skip all subscriptions and return an empty array.\n * @returns The accumulated raw Ably messages in chronological order.\n */\nexport const useAblyMessages = <TEvent, TMessage>({\n transport,\n skip,\n}: { transport?: ClientTransport<TEvent, TMessage>; skip?: boolean } = {}): Ably.InboundMessage[] => {\n const nearestSlot = useContext(NearestTransportContext);\n // CAST: NearestTransportContext stores transport with erased generics; types fixed at call site.\n const resolved = skip\n ? undefined\n : ((transport ?? nearestSlot?.transport) as ClientTransport<TEvent, TMessage> | undefined);\n\n const [messages, setMessages] = useState<Ably.InboundMessage[]>([]);\n const messagesRef = useRef<Ably.InboundMessage[]>([]);\n\n useEffect(() => {\n // Reset on transport change\n messagesRef.current = [];\n setMessages([]);\n\n if (!resolved) return;\n\n const unsub = resolved.tree.on('ably-message', (msg: Ably.InboundMessage) => {\n const next = [...messagesRef.current, msg];\n messagesRef.current = next;\n setMessages(next);\n });\n return unsub;\n }, [resolved]);\n\n return messages;\n};\n","/**\n * useActiveTurns: reactive view of active turns on the channel,\n * keyed by clientId.\n *\n * Subscribes to transport tree turn lifecycle events and maintains a\n * Map<clientId, Set<turnId>> that updates on every turn start/end.\n *\n * Uses tree (not view) so that all turns are tracked — including remote\n * turns whose messages haven't arrived yet.\n *\n * Generic — works with any codec, not tied to Vercel types.\n */\n\nimport { useContext, useEffect, useState } from 'react';\n\nimport { EVENT_TURN_START } from '../constants.js';\nimport type { ClientTransport, TurnLifecycleEvent } from '../core/transport/types.js';\nimport { NearestTransportContext } from './contexts/transport-context.js';\n\n/**\n * Returns a reactive Map of all active turns on the channel, keyed by clientId.\n * Updates when turns start or end. When `transport` is omitted, uses the nearest\n * {@link TransportProvider}'s transport via context.\n * @param props - Options including optional `transport`.\n * @param props.transport - Transport to track turns for; defaults to the nearest provider.\n * @returns A Map where keys are clientIds and values are Sets of active turnIds.\n */\nexport const useActiveTurns = <TEvent, TMessage>({\n transport,\n}: { transport?: ClientTransport<TEvent, TMessage> | null } = {}): Map<string, Set<string>> => {\n const nearestSlot = useContext(NearestTransportContext);\n // CAST: NearestTransportContext stores transport with erased generics; types fixed at call site.\n const resolved = (transport ?? nearestSlot?.transport) as ClientTransport<TEvent, TMessage> | undefined;\n\n const [turns, setTurns] = useState<Map<string, Set<string>>>(() => new Map());\n\n useEffect(() => {\n if (!resolved) return;\n\n // Initialize from current state\n setTurns(resolved.tree.getActiveTurnIds());\n\n const unsubscribe = resolved.tree.on('turn', (event: TurnLifecycleEvent) => {\n setTurns((prev) => {\n const next = new Map(prev);\n\n if (event.type === EVENT_TURN_START) {\n const set = new Set(next.get(event.clientId));\n set.add(event.turnId);\n next.set(event.clientId, set);\n } else {\n const existing = next.get(event.clientId);\n if (existing) {\n const updated = new Set(existing);\n updated.delete(event.turnId);\n if (updated.size === 0) {\n next.delete(event.clientId);\n } else {\n next.set(event.clientId, updated);\n }\n }\n }\n\n return next;\n });\n });\n\n return unsubscribe;\n }, [resolved]);\n\n return turns;\n};\n","/**\n * useClientTransport — read a ClientTransport from the nearest TransportProvider.\n *\n * The transport is created by {@link TransportProvider}, which also wraps the subtree\n * with Ably's `ChannelProvider`. This hook is a thin context reader — it does not\n * create or manage transport state.\n *\n * **Provider lookup**\n * - Omit `channelName` to use the innermost `TransportProvider` in the tree.\n * - Pass `channelName` to look up a specific provider by name.\n * - Pass `skip: true` to receive a stub transport that throws on any access —\n * safe to hold in state before auth or other conditions are ready.\n *\n * **Error handling**\n * - When no matching provider is found, or when the provider's `createClientTransport`\n * call threw, `transportError` is set on the returned object instead of throwing.\n * The component can render an error state without an error boundary.\n * - Pass `onError` to receive post-construction transport errors (e.g. send failures,\n * channel continuity loss) without wiring `transport.on('error', ...)` manually.\n */\n\nimport * as Ably from 'ably';\nimport { useContext, useEffect, useRef } from 'react';\n\nimport type { ClientTransport, Tree, View } from '../core/transport/types.js';\nimport { ErrorCode } from '../errors.js';\nimport { NearestTransportContext, TransportContext } from './contexts/transport-context.js';\n\nconst SKIPPED_TRANSPORT: ClientTransport<unknown, unknown> = {\n get tree(): Tree<unknown> {\n throw new Ably.ErrorInfo('unable to access tree; hook is skipped', ErrorCode.InvalidArgument, 400);\n },\n get view(): View<unknown, unknown> {\n throw new Ably.ErrorInfo('unable to access view; hook is skipped', ErrorCode.InvalidArgument, 400);\n },\n createView: (): View<unknown, unknown> => {\n throw new Ably.ErrorInfo('unable to create view; hook is skipped', ErrorCode.InvalidArgument, 400);\n },\n cancel: () => {\n throw new Ably.ErrorInfo('unable to cancel; hook is skipped', ErrorCode.InvalidArgument, 400);\n },\n stageEvents: () => {\n throw new Ably.ErrorInfo('unable to stage events; hook is skipped', ErrorCode.InvalidArgument, 400);\n },\n stageMessage: () => {\n throw new Ably.ErrorInfo('unable to stage message; hook is skipped', ErrorCode.InvalidArgument, 400);\n },\n waitForTurn: () => {\n throw new Ably.ErrorInfo('unable to wait for turn; hook is skipped', ErrorCode.InvalidArgument, 400);\n },\n on: () => {\n throw new Ably.ErrorInfo('unable to subscribe; hook is skipped', ErrorCode.InvalidArgument, 400);\n },\n close: () => {\n throw new Ably.ErrorInfo('unable to close; hook is skipped', ErrorCode.InvalidArgument, 400);\n },\n};\n\n/**\n * Return value of {@link useClientTransport}.\n *\n * `transport` is always a valid object. When `skip` is `true`, when no provider was\n * found, or when the provider's transport construction failed, `transport` is a stub\n * that throws {@link Ably.ErrorInfo} on every access.\n * Check `transportError` before using `transport` to avoid those throws.\n */\nexport interface ClientTransportHandle<TEvent, TMessage> {\n /**\n * The resolved transport.\n *\n * A throwing stub when `skip` is `true`, when no matching {@link TransportProvider}\n * was found in the tree, or when transport construction failed.\n */\n transport: ClientTransport<TEvent, TMessage>;\n /**\n * Set when no matching {@link TransportProvider} was found, when transport\n * construction failed, and `skip` is `false`.\n * `undefined` when the transport resolved successfully or when `skip` is `true`.\n */\n transportError?: Ably.ErrorInfo | undefined;\n}\n\n/**\n * Read a {@link ClientTransport} from the nearest {@link TransportProvider}.\n *\n * Returns `{ transport, transportError }`. When no provider is found or transport\n * construction failed, `transportError` is set and `transport` is a stub that throws\n * on access — the hook never throws during render.\n *\n * Pass `onError` to subscribe to post-construction transport errors\n * (e.g. {@link ErrorCode.TransportSendFailed}, {@link ErrorCode.ChannelContinuityLost})\n * without calling `transport.on('error', …)` manually. The subscription is\n * created when the transport resolves and removed on unmount.\n * @param props - Hook options.\n * @param props.channelName - Look up a specific provider by channel name; omit for the nearest.\n * @param props.skip - When `true`, return the stub transport immediately without reading context.\n * @param props.onError - Called whenever the resolved transport emits an error event.\n * @returns `{ transport, transportError }`.\n */\nexport const useClientTransport = <TEvent, TMessage>({\n channelName,\n skip,\n onError,\n}: {\n /**\n * Channel name passed to the enclosing {@link TransportProvider}.\n * Omit to use the nearest provider in the tree.\n */\n channelName?: string;\n /**\n * When `true`, skip context lookup and return a stub transport that throws on\n * any access. Use when a condition (auth, feature flag) is not yet resolved.\n */\n skip?: boolean;\n /**\n * Called whenever the resolved transport emits an error event.\n * The subscription is established once the transport resolves and\n * automatically removed on unmount or when the transport changes.\n */\n onError?: (error: Ably.ErrorInfo) => void;\n} = {}): ClientTransportHandle<TEvent, TMessage> => {\n const registry = useContext(TransportContext);\n const nearestSlot = useContext(NearestTransportContext);\n const errorCallbackRef = useRef(onError);\n errorCallbackRef.current = onError;\n\n // Compute the transport for the onError subscription *before* any conditional\n // returns to satisfy React's rules of hooks (no hooks in branches).\n // Erased generics — this ref is only used in the useEffect below.\n const resolvedForEffect: ClientTransport<unknown, unknown> | undefined = skip\n ? undefined\n : channelName === undefined\n ? nearestSlot?.transport\n : registry[channelName]?.transport;\n\n useEffect(() => {\n if (!resolvedForEffect) return;\n return resolvedForEffect.on('error', (errorInfo) => {\n errorCallbackRef.current?.(errorInfo);\n });\n }, [resolvedForEffect]);\n\n if (skip) {\n return {\n transport: SKIPPED_TRANSPORT as unknown as ClientTransport<TEvent, TMessage>,\n };\n }\n\n if (channelName !== undefined) {\n const slot = registry[channelName];\n if (slot) {\n if (slot.transport) {\n // CAST: TransportContext stores transports with erased generics.\n // The caller is responsible for using type parameters matching those of the TransportProvider.\n return {\n transport: slot.transport as unknown as ClientTransport<TEvent, TMessage>,\n };\n }\n // Provider exists but construction failed.\n return {\n transport: SKIPPED_TRANSPORT as unknown as ClientTransport<TEvent, TMessage>,\n transportError: slot.error,\n };\n }\n return {\n transport: SKIPPED_TRANSPORT as unknown as ClientTransport<TEvent, TMessage>,\n transportError: new Ably.ErrorInfo(\n `unable to use transport; no TransportProvider found for channelName \"${channelName}\"`,\n ErrorCode.BadRequest,\n 400,\n ),\n };\n }\n\n if (nearestSlot) {\n if (nearestSlot.transport) {\n // CAST: NearestTransportContext stores transport with erased generics; types fixed at call site.\n return {\n transport: nearestSlot.transport as unknown as ClientTransport<TEvent, TMessage>,\n };\n }\n // Nearest provider exists but construction failed.\n return {\n transport: SKIPPED_TRANSPORT as unknown as ClientTransport<TEvent, TMessage>,\n transportError: nearestSlot.error,\n };\n }\n\n return {\n transport: SKIPPED_TRANSPORT as unknown as ClientTransport<TEvent, TMessage>,\n transportError: new Ably.ErrorInfo(\n 'unable to use transport; no TransportProvider found in the tree',\n ErrorCode.BadRequest,\n 400,\n ),\n };\n};\n","/**\n * useView — reactive paginated view of the conversation.\n *\n * Subscribes to view updates and exposes the visible nodes, branch navigation,\n * write operations, pagination state, and a `loadOlder` callback. Pass `transport`\n * to use a transport's default view, or `view` to subscribe to a specific\n * {@link View} directly. When both are omitted, defaults to the nearest\n * {@link TransportProvider}'s transport via context.\n */\n\nimport * as Ably from 'ably';\nimport { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';\n\nimport type { ActiveTurn, ClientTransport, MessageNode, SendOptions, View } from '../core/transport/types.js';\nimport { ErrorCode } from '../errors.js';\nimport { NearestTransportContext } from './contexts/transport-context.js';\n\n/** Options for configuring the view's initial load behavior. */\nexport interface UseViewOptions {\n /** Maximum number of older messages to load per page. Defaults to 100. */\n limit?: number;\n}\n\n/** Handle for the paginated, branch-aware conversation view. */\nexport interface ViewHandle<TEvent, TMessage> {\n /** The visible domain messages along the selected branch. */\n messages: TMessage[];\n /** Visible conversation nodes along the selected branch. */\n nodes: MessageNode<TMessage>[];\n /** Whether there are older messages that can be revealed via `loadOlder`. */\n hasOlder: boolean;\n /** Whether a page load is currently in progress. */\n loading: boolean;\n /**\n * Set when the most recent `loadOlder` call failed.\n * Cleared automatically on the next successful load.\n * `undefined` when no error has occurred or when `skip` is `true`.\n */\n loadError: Ably.ErrorInfo | undefined;\n /**\n * Load older messages into the view. No-op if already loading.\n * On failure, `error` is set; on success, `error` is cleared.\n */\n loadOlder: () => Promise<void>;\n /** Select a sibling at a fork point by index. Triggers a view update with the new branch. */\n select: (msgId: string, index: number) => void;\n /** Index of the currently selected sibling at a fork point. */\n getSelectedIndex: (msgId: string) => number;\n /** Get all sibling messages at a fork point, ordered chronologically by serial. */\n getSiblings: (msgId: string) => TMessage[];\n /** Whether a message has sibling alternatives (i.e., show navigation arrows). */\n hasSiblings: (msgId: string) => boolean;\n /** Get a node by msgId, or undefined if not found. */\n getNode: (msgId: string) => MessageNode<TMessage> | undefined;\n /** Send one or more messages in the context of this view's selected branch. */\n send: (messages: TMessage | TMessage[], options?: SendOptions) => Promise<ActiveTurn<TEvent>>;\n /** Regenerate an assistant message, using this view's branch for history. */\n regenerate: (messageId: string, options?: SendOptions) => Promise<ActiveTurn<TEvent>>;\n /** Edit a user message, forking from this view's branch. */\n edit: (messageId: string, newMessages: TMessage | TMessage[], options?: SendOptions) => Promise<ActiveTurn<TEvent>>;\n /** Amend an existing message and start a continuation turn (e.g. tool results). */\n update: (msgId: string, events: TEvent[], options?: SendOptions) => Promise<ActiveTurn<TEvent>>;\n}\n\n/**\n * Subscribe to a view and return the visible node list with pagination, navigation, and write operations.\n *\n * `view` takes priority over `transport`. When neither is provided, the nearest\n * {@link TransportProvider}'s transport is used. When `limit` is provided, auto-loads\n * the first page on mount (SWR-style).\n * @param props - Options for selecting the view source and configuring auto-load.\n * @param props.transport - Client transport whose default view to subscribe to; defaults to the nearest provider.\n * @param props.view - A specific {@link View} to subscribe to directly. Takes priority over `transport`.\n * @param props.limit - Max older messages per page; when provided, auto-loads on mount.\n * @param props.skip - When `true`, skip all subscriptions and return an empty handle.\n * @returns A {@link ViewHandle} with nodes, pagination state, navigation, write operations, and loadOlder.\n */\nexport const useView = <TEvent, TMessage>({\n transport,\n view,\n limit,\n skip,\n}: {\n /** Client transport whose default view to subscribe to; defaults to the nearest provider when omitted. */\n transport?: ClientTransport<TEvent, TMessage> | null;\n /** A specific {@link View} to subscribe to directly. Takes priority over `transport`. */\n view?: View<TEvent, TMessage> | null;\n /** When provided, auto-loads the first page on mount. Omit for manual loading. */\n limit?: number;\n /** When `true`, skip all subscriptions and return an empty handle immediately. */\n skip?: boolean;\n} = {}): ViewHandle<TEvent, TMessage> => {\n const nearestSlot = useContext(NearestTransportContext);\n // CAST: NearestTransportContext stores transport with erased generics; types fixed at call site.\n const resolvedTransport = skip\n ? undefined\n : (transport ?? (nearestSlot?.transport as unknown as ClientTransport<TEvent, TMessage> | undefined));\n const resolvedView = skip ? undefined : (view ?? resolvedTransport?.view);\n\n const [nodes, setNodes] = useState<MessageNode<TMessage>[]>(() => resolvedView?.flattenNodes() ?? []);\n const [hasOlder, setHasOlder] = useState(() => resolvedView?.hasOlder() ?? false);\n const [loading, setLoading] = useState(false);\n const [loadError, setLoadError] = useState<Ably.ErrorInfo | undefined>();\n const loadingRef = useRef(false);\n\n // Auto-load first page on mount when limit is provided (SWR-style).\n // Fires once per view instance — subsequent changes to limit\n // only affect manual loadOlder() calls, not the initial auto-load.\n const autoLoad = limit !== undefined;\n const autoLoadedRef = useRef(false);\n\n // Subscribe to view updates\n useEffect(() => {\n if (!resolvedView) {\n setNodes([]);\n setHasOlder(false);\n setLoadError(undefined);\n return;\n }\n\n // Reset auto-load flag so the new view gets its first page loaded\n autoLoadedRef.current = false;\n\n // Sync initial state\n setNodes(resolvedView.flattenNodes());\n setHasOlder(resolvedView.hasOlder());\n setLoadError(undefined);\n\n const unsub = resolvedView.on('update', () => {\n setNodes(resolvedView.flattenNodes());\n setHasOlder(resolvedView.hasOlder());\n });\n return unsub;\n }, [resolvedView]);\n\n const loadOlder = useCallback(async () => {\n if (!resolvedView || loadingRef.current) return;\n loadingRef.current = true;\n setLoading(true);\n try {\n await resolvedView.loadOlder(limit);\n setLoadError(undefined);\n } catch (error) {\n if (error instanceof Ably.ErrorInfo) {\n setLoadError(error);\n } else {\n setLoadError(new Ably.ErrorInfo('Unknown error loading older messages', ErrorCode.BadRequest, 400));\n }\n } finally {\n loadingRef.current = false;\n setLoading(false);\n }\n }, [resolvedView, limit]);\n\n useEffect(() => {\n if (!autoLoad || autoLoadedRef.current || !resolvedView) return;\n autoLoadedRef.current = true;\n void loadOlder();\n }, [autoLoad, resolvedView, loadOlder]);\n\n const messages = useMemo(() => nodes.map((n) => n.message), [nodes]);\n\n // Branch navigation callbacks\n const select = useCallback(\n (msgId: string, index: number) => {\n resolvedView?.select(msgId, index);\n },\n [resolvedView],\n );\n\n const getSelectedIndex = useCallback((msgId: string) => resolvedView?.getSelectedIndex(msgId) ?? 0, [resolvedView]);\n\n const getSiblings = useCallback((msgId: string) => resolvedView?.getSiblings(msgId) ?? [], [resolvedView]);\n\n const hasSiblings = useCallback((msgId: string) => resolvedView?.hasSiblings(msgId) ?? false, [resolvedView]);\n\n const getNode = useCallback((msgId: string) => resolvedView?.getNode(msgId), [resolvedView]);\n\n // Write operation callbacks\n const send = useCallback(\n async (msgs: TMessage | TMessage[], opts?: SendOptions) => {\n if (!resolvedView)\n throw new Ably.ErrorInfo('unable to send; view is not available', ErrorCode.InvalidArgument, 400);\n return resolvedView.send(msgs, opts);\n },\n [resolvedView],\n );\n\n const regenerate = useCallback(\n async (messageId: string, opts?: SendOptions) => {\n if (!resolvedView)\n throw new Ably.ErrorInfo('unable to regenerate; view is not available', ErrorCode.InvalidArgument, 400);\n return resolvedView.regenerate(messageId, opts);\n },\n [resolvedView],\n );\n\n const edit = useCallback(\n async (messageId: string, newMessages: TMessage | TMessage[], opts?: SendOptions) => {\n if (!resolvedView)\n throw new Ably.ErrorInfo('unable to edit; view is not available', ErrorCode.InvalidArgument, 400);\n return resolvedView.edit(messageId, newMessages, opts);\n },\n [resolvedView],\n );\n\n const update = useCallback(\n async (msgId: string, events: TEvent[], opts?: SendOptions) => {\n if (!resolvedView)\n throw new Ably.ErrorInfo('unable to update; view is not available', ErrorCode.InvalidArgument, 400);\n return resolvedView.update(msgId, events, opts);\n },\n [resolvedView],\n );\n\n return {\n messages,\n nodes,\n hasOlder,\n loading,\n loadError,\n loadOlder,\n select,\n getSelectedIndex,\n getSiblings,\n hasSiblings,\n getNode,\n send,\n regenerate,\n edit,\n update,\n };\n};\n","/**\n * useCreateView — create an independent view with the same API as useView.\n *\n * Calls {@link ClientTransport.createView} to create an independent view over\n * the same conversation tree, then subscribes to it exactly like\n * {@link useView}. The view is closed automatically on unmount or when the\n * transport reference changes.\n *\n * Pass `null` or omit `transport` to defer creation (e.g. when a split pane is\n * collapsed). The returned handle has empty state until a transport is provided.\n * When `transport` is omitted entirely, defaults to the nearest\n * {@link TransportProvider}'s transport via context.\n * Pass `skip: true` to bypass all context reads and view creation entirely.\n */\n\nimport { useContext, useEffect, useState } from 'react';\n\nimport type { ClientTransport, View } from '../core/transport/types.js';\nimport { NearestTransportContext } from './contexts/transport-context.js';\nimport type { ViewHandle } from './use-view.js';\nimport { useView } from './use-view.js';\n\n/**\n * Create an independent {@link View} and subscribe to it.\n * Returns the same {@link ViewHandle} as {@link useView}, but backed by a\n * newly created view with its own branch selections and pagination state.\n * The view is closed on unmount or when the transport changes.\n * When `transport` is omitted, uses the nearest {@link TransportProvider}'s transport via context.\n * @param props - Options including optional `transport`, `limit` for auto-load, and `skip`.\n * @param props.transport - Transport to create a view from; defaults to the nearest provider.\n * @param props.limit - Max older messages per page; when provided, auto-loads on mount.\n * @param props.skip - When `true`, skip view creation and return an empty handle.\n * @returns A {@link ViewHandle} with nodes, pagination, navigation, and write operations.\n */\nexport const useCreateView = <TEvent, TMessage>({\n transport,\n limit,\n skip,\n}: {\n /** The transport to create a view from, or null/undefined to use the nearest provider. */\n transport?: ClientTransport<TEvent, TMessage> | null;\n /** When provided, auto-loads the first page on mount. Omit for manual load. */\n limit?: number;\n /** When `true`, skip view creation and return an empty handle immediately. */\n skip?: boolean;\n} = {}): ViewHandle<TEvent, TMessage> => {\n const nearestSlot = useContext(NearestTransportContext);\n // CAST: NearestTransportContext stores transport with erased generics; types fixed at call site.\n const resolved = skip\n ? undefined\n : ((transport ?? nearestSlot?.transport) as ClientTransport<TEvent, TMessage> | null | undefined);\n\n const [view, setView] = useState<View<TEvent, TMessage> | undefined>();\n\n useEffect(() => {\n if (!resolved) {\n setView(undefined);\n return;\n }\n const v = resolved.createView();\n setView(v);\n return () => {\n v.close();\n };\n }, [resolved]);\n\n return useView({ view, limit, skip });\n};\n","/**\n * useTree — stable structural query callbacks for a ClientTransport's tree.\n *\n * Returns a {@link TreeHandle} with methods to inspect the tree structure.\n * These are thin `useCallback` wrappers around `transport.tree` — no local\n * state or subscriptions. Branch navigation (select, getSelectedIndex) is\n * on {@link ViewHandle} from {@link useView}.\n *\n * When `transport` is omitted, defaults to the nearest\n * {@link TransportProvider}'s transport via context.\n */\n\nimport { useCallback, useContext } from 'react';\n\nimport type { ClientTransport, MessageNode } from '../core/transport/types.js';\nimport { NearestTransportContext } from './contexts/transport-context.js';\n\n/** Handle for querying the conversation tree structure. */\nexport interface TreeHandle<TMessage> {\n /** Get all sibling messages at a fork point, ordered chronologically by serial. */\n getSiblings: (msgId: string) => TMessage[];\n /** Whether a message has sibling alternatives (i.e., show navigation arrows). */\n hasSiblings: (msgId: string) => boolean;\n /** Get a node by msgId, or undefined if not found. */\n getNode: (msgId: string) => MessageNode<TMessage> | undefined;\n}\n\n/**\n * Provide stable structural query callbacks backed by the transport's tree.\n * When `transport` is omitted, uses the nearest {@link TransportProvider}'s transport via context.\n * @param props - Options including optional `transport`.\n * @param props.transport - Transport to read tree structure from; defaults to the nearest provider.\n * @returns A {@link TreeHandle} with structural query methods.\n */\nexport const useTree = <TEvent, TMessage>({\n transport,\n}: { transport?: ClientTransport<TEvent, TMessage> } = {}): TreeHandle<TMessage> => {\n const nearestSlot = useContext(NearestTransportContext);\n // CAST: NearestTransportContext stores transport with erased generics; types fixed at call site.\n const resolved = (transport ?? nearestSlot?.transport) as ClientTransport<TEvent, TMessage> | undefined;\n\n const getSiblings = useCallback((msgId: string) => resolved?.tree.getSiblings(msgId) ?? [], [resolved]);\n\n const hasSiblings = useCallback((msgId: string) => resolved?.tree.hasSiblings(msgId) ?? false, [resolved]);\n\n const getNode = useCallback((msgId: string) => resolved?.tree.getNode(msgId), [resolved]);\n\n return {\n getSiblings,\n hasSiblings,\n getNode,\n };\n};\n","/**\n * createTransportHooks: factory that captures TEvent and TMessage once and returns\n * a bundle of type-safe hooks + TransportProvider. Hook call sites need no type\n * parameters at every use — just call the hooks directly.\n * @example\n * // Once per app (e.g. in a shared transport.ts):\n * export const {\n * TransportProvider,\n * useClientTransport,\n * useView,\n * useActiveTurns,\n * } = createTransportHooks<UIMessageChunk, UIMessage>();\n *\n * // In page:\n * <TransportProvider channelName=\"ai:demo\" codec={UIMessageCodec}>\n * <Chat />\n * </TransportProvider>\n *\n * // In Chat — no type params needed, transport is implicit from nearest provider:\n * const { nodes } = useView({ limit: 30 });\n * const turns = useActiveTurns();\n */\n\nimport type * as Ably from 'ably';\nimport type { ComponentType } from 'react';\n\nimport type { ClientTransport, View } from '../core/transport/types.js';\nimport type { TransportProviderProps } from './contexts/transport-provider.js';\nimport { TransportProvider as _TransportProvider } from './contexts/transport-provider.js';\nimport { useAblyMessages as _useAblyMessages } from './use-ably-messages.js';\nimport { useActiveTurns as _useActiveTurns } from './use-active-turns.js';\nimport type { ClientTransportHandle } from './use-client-transport.js';\nimport { useClientTransport as _useClientTransport } from './use-client-transport.js';\nimport { useCreateView as _useCreateView } from './use-create-view.js';\nimport type { TreeHandle } from './use-tree.js';\nimport { useTree as _useTree } from './use-tree.js';\nimport type { ViewHandle } from './use-view.js';\nimport { useView as _useView } from './use-view.js';\n\n/**\n * Bundle of type-safe hooks and provider returned by {@link createTransportHooks}.\n *\n * `TEvent` and `TMessage` are baked in at factory creation time so no type params\n * are needed at hook call sites.\n */\nexport interface TransportHooks<TEvent, TMessage> {\n /**\n * `TransportProvider` narrowed to `TEvent`/`TMessage`. No JSX type params needed.\n */\n TransportProvider: ComponentType<TransportProviderProps<TEvent, TMessage>>;\n /**\n * Read the transport from context. No type params needed.\n *\n * Returns `{ transport, transportError }`. When no provider is found,\n * `transportError` is set and `transport` is a stub that throws on access —\n * the hook never throws during render.\n *\n * Pass `onError` to subscribe to post-construction transport errors\n * (e.g. send failures, channel continuity loss) without wiring\n * `transport.on('error', …)` manually.\n */\n useClientTransport: (props?: {\n /** Channel name to look up; omit to use the nearest {@link TransportProvider}. */\n channelName?: string;\n /** When `true`, return a stub transport that throws on any access. */\n skip?: boolean;\n /** Called whenever the resolved transport emits an error event. */\n onError?: (error: Ably.ErrorInfo) => void;\n }) => ClientTransportHandle<TEvent, TMessage>;\n /**\n * Subscribe to the nearest transport's view and return the visible node list with pagination.\n * Pass `transport` to use a transport's default view, `view` to subscribe to a specific view\n * directly. Pass `limit` to auto-load on mount. Pass `skip: true` for an empty handle.\n */\n useView: (props?: {\n /** Client transport whose default view to subscribe to; defaults to the nearest {@link TransportProvider}. */\n transport?: ClientTransport<TEvent, TMessage> | null;\n /** A specific {@link View} to subscribe to directly. Takes priority over `transport`. */\n view?: View<TEvent, TMessage> | null;\n /** When provided, auto-loads the first page on mount. */\n limit?: number;\n /** When `true`, skip all subscriptions and return an empty handle. */\n skip?: boolean;\n }) => ViewHandle<TEvent, TMessage>;\n /**\n * Track active turns across all clients on the channel.\n * Pass `transport` to override; defaults to the nearest {@link TransportProvider}.\n */\n useActiveTurns: (props?: {\n /** Override transport; defaults to the nearest {@link TransportProvider}. */\n transport?: ClientTransport<TEvent, TMessage> | null;\n }) => Map<string, Set<string>>;\n /**\n * Navigate conversation branches in the transport tree.\n * Pass `transport` to override; defaults to the nearest {@link TransportProvider}.\n */\n useTree: (props?: {\n /** Override transport; defaults to the nearest {@link TransportProvider}. */\n transport?: ClientTransport<TEvent, TMessage>;\n }) => TreeHandle<TMessage>;\n /**\n * Subscribe to raw Ably messages on the transport channel.\n * Pass `transport` to override; defaults to the nearest {@link TransportProvider}.\n * Pass `skip: true` to return an empty array without subscribing.\n */\n useAblyMessages: (props?: {\n /** Override transport; defaults to the nearest {@link TransportProvider}. */\n transport?: ClientTransport<TEvent, TMessage>;\n /** When `true`, skip all subscriptions and return an empty array. */\n skip?: boolean;\n }) => Ably.InboundMessage[];\n /**\n * Create an independent view over the same tree.\n * Pass `transport` to override; defaults to the nearest {@link TransportProvider}.\n * Pass `skip: true` to return an empty handle without creating a view.\n */\n useCreateView: (props?: {\n /** Override transport; defaults to the nearest {@link TransportProvider}. */\n transport?: ClientTransport<TEvent, TMessage> | null;\n /** When provided, auto-loads the first page on mount. */\n limit?: number;\n /** When `true`, skip view creation and return an empty handle. */\n skip?: boolean;\n }) => ViewHandle<TEvent, TMessage>;\n}\n\n/**\n * Create a bundle of type-safe hooks and provider for a given `TEvent`/`TMessage` pair.\n *\n * `TEvent` and `TMessage` are captured at factory creation time; hook call sites need\n * no type parameters. The returned hooks are thin wrappers around the standalone hooks\n * with the types resolved.\n * @returns A {@link TransportHooks} bundle.\n */\nexport const createTransportHooks = <TEvent, TMessage>(): TransportHooks<TEvent, TMessage> => ({\n // CAST: TransportProvider is generic; factory narrows it to TEvent/TMessage.\n TransportProvider: _TransportProvider as ComponentType<TransportProviderProps<TEvent, TMessage>>,\n useClientTransport: (props) => _useClientTransport<TEvent, TMessage>(props ?? {}),\n useView: (props) => _useView<TEvent, TMessage>(props ?? {}),\n useActiveTurns: (props) => _useActiveTurns<TEvent, TMessage>(props ?? {}),\n useTree: (props) => _useTree<TEvent, TMessage>(props ?? {}),\n useAblyMessages: (props) => _useAblyMessages<TEvent, TMessage>(props ?? {}),\n useCreateView: (props) => _useCreateView<TEvent, TMessage>(props ?? {}),\n});\n"],"mappings":";;;;;AA2BA,IAAa,IAAmB,EAAqC,EAAE,CAAC,EAS3D,IAA0B,EAAyC,KAAA,EAAU,ECjB7E,IAAgB,iBAahB,IAAiB,kBAGjB,IAAgB,iBAGhB,IAAwB,yBAGxB,IAAc,eAGd,IAAe,gBAOf,IAAwB,yBAGxB,IAAoB,qBAGpB,IAAoB,qBAGpB,IAA0B,2BAO1B,IAAgB,iBAGhB,IAAiB,kBAcjB,IAAe,iBAGf,IAAmB,qBAGnB,IAAiB,mBCrFlB,IAAL,yBAAA,GAAA;QAIL,EAAA,EAAA,aAAA,OAAA,cAKA,EAAA,EAAA,kBAAA,SAAA,mBAQA,EAAA,EAAA,wBAAA,SAAA,yBAKA,EAAA,EAAA,6BAAA,UAAA,8BAKA,EAAA,EAAA,sBAAA,UAAA,uBAKA,EAAA,EAAA,qBAAA,UAAA,sBAKA,EAAA,EAAA,kBAAA,UAAA,mBAKA,EAAA,EAAA,sBAAA,UAAA,uBAOA,EAAA,EAAA,wBAAA,UAAA,yBAMA,EAAA,EAAA,kBAAA,UAAA,mBAOA,EAAA,EAAA,cAAA,UAAA;KACD,ECEK,MAAgB,OAA6B;CACjD,YAAY,GAAgB,GAAgB,MAAqB;AAC/D,IAAO,MAAM,GAAQ,EAAE,QAAQ,GAAS,CAAC;;CAE3C,iBAAiB;CAClB,GAKK,KACJ,EAAK,SACL,cAYW,IAAb,cAA6C,GAAgC;CAK3E,YAAY,GAAgB;AAC1B,QAAM,GAAa,EAAO,CAAC;;GC5CnB,IAAL,yBAAA,GAAA;QAKL,EAAA,QAAA,SAMA,EAAA,QAAA,SAKA,EAAA,OAAA,QAMA,EAAA,OAAA,QAMA,EAAA,QAAA,SAKA,EAAA,SAAA;KACD,EAuBY,MAAiB,GAAiB,GAAiB,MAAyB;CACvF,IAAM,IAAgB,IAAU,cAAc,KAAK,UAAU,EAAQ,KAAK,IACpE,IAAmB,qBAAI,IAAI,MAAM,EAAC,aAAa,CAAC,IAAI,EAAM,SAAS,CAAC,aAAa,CAAC,sBAAsB,IAAU;AAExH,SAAQ,GAAR;EACE,KAAK,EAAS;EACd,KAAK,EAAS;AACZ,WAAQ,IAAI,EAAiB;AAC7B;EAEF,KAAK,EAAS;AACZ,WAAQ,KAAK,EAAiB;AAC9B;EAEF,KAAK,EAAS;AACZ,WAAQ,KAAK,EAAiB;AAC9B;EAEF,KAAK,EAAS;AACZ,WAAQ,MAAM,EAAiB;AAC/B;EAEF,KAAK,EAAS,OACZ;;GAaO,MAAc,MAGlB,IAAI,EAFQ,EAAQ,cAAc,IAEJ,EAAQ,SAAS,EAMnD,IAAL,yBAAA,GAAA;QACE,EAAA,EAAA,QAAA,KAAA,SACA,EAAA,EAAA,QAAA,KAAA,SACA,EAAA,EAAA,OAAA,KAAA,QACA,EAAA,EAAA,OAAA,KAAA,QACA,EAAA,EAAA,QAAA,KAAA,SACA,EAAA,EAAA,SAAA,KAAA;EANG,KAAA,EAAA,CAOJ,EAKK,IAAoB,IAAI,IAA8B;CAC1D,CAAC,EAAS,OAAO,EAAe,MAAM;CACtC,CAAC,EAAS,OAAO,EAAe,MAAM;CACtC,CAAC,EAAS,MAAM,EAAe,KAAK;CACpC,CAAC,EAAS,MAAM,EAAe,KAAK;CACpC,CAAC,EAAS,OAAO,EAAe,MAAM;CACtC,CAAC,EAAS,QAAQ,EAAe,OAAO;CACzC,CAAC,EAKI,IAAN,MAAM,EAAgC;CAKpC,YAAY,GAAqB,GAAiB,GAAsB;AAEtE,EADA,KAAK,WAAW,GAChB,KAAK,WAAW;EAEhB,IAAM,IAAc,EAAkB,IAAI,EAAM;AAChD,MAAI,MAAgB,KAAA,EAClB,OAAM,IAAI,EAAK,UAAU,+CAA+C,KAAS,EAAU,iBAAiB,IAAI;AAGlH,OAAK,eAAe;;CAGtB,MAAM,GAAiB,GAA4B;AACjD,OAAK,OAAO,GAAS,EAAS,OAAO,EAAe,OAAO,EAAQ;;CAGrE,MAAM,GAAiB,GAA4B;AACjD,OAAK,OAAO,GAAS,EAAS,OAAO,EAAe,OAAO,EAAQ;;CAGrE,KAAK,GAAiB,GAA4B;AAChD,OAAK,OAAO,GAAS,EAAS,MAAM,EAAe,MAAM,EAAQ;;CAGnE,KAAK,GAAiB,GAA4B;AAChD,OAAK,OAAO,GAAS,EAAS,MAAM,EAAe,MAAM,EAAQ;;CAGnE,MAAM,GAAiB,GAA4B;AACjD,OAAK,OAAO,GAAS,EAAS,OAAO,EAAe,OAAO,EAAQ;;CAGrE,YAAY,GAA6B;EAEvC,IAAM,IACJ,CAAC,GAAG,EAAkB,SAAS,CAAC,CAAC,MAAM,GAAG,OAAW,MAAU,KAAK,aAAa,GAAG,MAAM,EAAS;AAErG,SAAO,IAAI,EAAc,KAAK,UAAU,GAAe,KAAK,cAAc,EAAQ,CAAC;;CAGrF,OAAe,GAAiB,GAAiB,GAA6B,GAA4B;AACxG,EAAI,KAAe,KAAK,gBACtB,KAAK,SAAS,GAAS,GAAO,KAAK,cAAc,EAAQ,CAAC;;CAI9D,cAAsB,GAA8C;AAKlE,SAJK,KAAK,WAIH,IAAU;GAAE,GAAG,KAAK;GAAU,GAAG;GAAS,GAAG,KAAK,WAHhD,KAAW,KAAA;;GC1NX,KAAc,MAAyD;CAElF,IAAM,IAAS,EAAQ;AACvB,KAAI,CAAC,KAAU,OAAO,KAAW,SAAU,QAAO,EAAE;CACpD,IAAM,IAAW,EAAiC;AAIlD,QAHI,CAAC,KAAW,OAAO,KAAY,WAAiB,EAAE,GAG/C;GCKI,KAAyB,MAQR;CAC5B,IAAM,IAA4B;GAC/B,IAAc,EAAK;GACnB,IAAiB,EAAK;GACtB,IAAgB,EAAK;EACvB;AAKD,QAJI,EAAK,iBAAiB,KAAA,MAAW,EAAE,KAAyB,EAAK,eACjE,EAAK,WAAQ,EAAE,KAAiB,EAAK,SACrC,EAAK,WAAQ,EAAE,KAAkB,EAAK,SACtC,EAAK,UAAO,EAAE,KAAgB,EAAK,QAChC;GCXH,IAAN,MAAkE;CAKhE,YAAY,GAAwC,GAAgB;AAElE,gCANwB,IAAI,KAAgC,EAK5D,KAAK,cAAc,GACnB,KAAK,UAAU;;CAGjB,aAAa,GAAwC;AACnD,OAAK,QAAQ,MAAM,gCAAgC,EAAE,WAAQ,CAAC;EAI9D,IAAM,IAAkE,EAAE,EACpE,IAAS,IAAI,eAAuB,EACxC,MAAM,GAAY;AAChB,KAAM,aAAa;KAEtB,CAAC;AACF,MAAI,CAAC,EAAM,WACT,OAAM,IAAI,EAAK,UACb,gFACA,EAAU,4BACV,IACD;AAGH,SADA,KAAK,OAAO,IAAI,GAAQ;GAAE,YAAY,EAAM;GAAY;GAAQ,CAAC,EAC1D;;CAIT,YAAY,GAAyB;EACnC,IAAM,IAAO,KAAK,OAAO,IAAI,EAAO;AACpC,MAAI,CAAC,EAAM,QAAO;AAElB,OAAK,QAAQ,MAAM,8CAA8C,EAAE,WAAQ,CAAC;AAC5E,MAAI;AACF,KAAK,WAAW,OAAO;UACjB;AAIR,SADA,KAAK,OAAO,OAAO,EAAO,EACnB;;CAIT,YAAY,GAAgB,GAAgC;EAC1D,IAAM,IAAO,KAAK,OAAO,IAAI,EAAO;AACpC,MAAI,CAAC,EAAM,QAAO;AAElB,OAAK,QAAQ,MAAM,+CAA+C,EAAE,WAAQ,CAAC;AAC7E,MAAI;AACF,KAAK,WAAW,MAAM,EAAM;UACtB;AAIR,SADA,KAAK,OAAO,OAAO,EAAO,EACnB;;CAIT,MAAM,GAAgB,GAAwB;EAC5C,IAAM,IAAO,KAAK,OAAO,IAAI,EAAO;AACpC,MAAI,CAAC,EAAM,QAAO;AAElB,MAAI;AACF,KAAK,WAAW,QAAQ,EAAM;UACxB;AAEN,UADA,KAAK,OAAO,OAAO,EAAO,EACnB;;AAMT,SAHI,KAAK,YAAY,EAAM,IACzB,KAAK,YAAY,EAAO,EAEnB;;CAGT,IAAI,GAAyB;AAC3B,SAAO,KAAK,OAAO,IAAI,EAAO;;GAcrB,KACX,GACA,MACyB,IAAI,EAAoB,GAAY,EAAO,EC3CzD,IAAb,MAAqE;CA6BnE,IAAI,oBAA4B;AAC9B,SAAO,KAAK;;CAGd,YAAY,GAAgB;AAE1B,oCAjC4B,IAAI,KAAqC,qBAOd,EAAE,sCAM3B,IAAI,KAAsC,wCAMxC,IAAI,KAAqB,qBAGrC,6BAGO,GAO3B,KAAK,UAAU,GACf,KAAK,WAAW,IAAI,EAA4B,EAAO;;CAiBzD,cAAsB,GAA2B,GAAmC;EAClF,IAAM,IAAK,EAAE,KAAK,QACZ,IAAK,EAAE,KAAK;AAMlB,SALI,MAAO,KAAA,KAAa,MAAO,KAAA,IAAkB,EAAE,YAAY,EAAE,YAC7D,MAAO,KAAA,IAAkB,IACzB,MAAO,KAAA,KACP,IAAK,IAAW,KAChB,IAAK,IAAW,IACb,EAAE,YAAY,EAAE;;CAOzB,cAAsB,GAAwC;AAI5D,MAHe,EAAS,KAAK,WAGd,KAAA,GAAW;AACxB,QAAK,YAAY,KAAK,EAAS;AAC/B;;EAIF,IAAI,IAAK,GACL,IAAK,KAAK,YAAY;AAC1B,SAAO,IAAK,IAAI;GACd,IAAM,IAAO,IAAK,MAAQ,GACpB,IAAU,KAAK,YAAY;AACjC,OAAI,CAAC,EAAS;AACd,GAAI,KAAK,cAAc,GAAS,EAAS,IAAI,IAC3C,IAAK,IAAM,IAEX,IAAK;;AAGT,OAAK,YAAY,OAAO,GAAI,GAAG,EAAS;;CAO1C,cAAsB,GAAwC;EAC5D,IAAM,IAAM,KAAK,YAAY,QAAQ,EAAS;AAC9C,EAAI,MAAQ,MAAI,KAAK,YAAY,OAAO,GAAK,EAAE;;CAOjD,kBAA0B,GAA8B,GAAqB;EAC3E,IAAI,IAAM,KAAK,aAAa,IAAI,EAAS;AAKzC,EAJK,MACH,oBAAM,IAAI,KAAK,EACf,KAAK,aAAa,IAAI,GAAU,EAAI,GAEtC,EAAI,IAAI,EAAM;;CAGhB,uBAA+B,GAA8B,GAAqB;EAChF,IAAM,IAAM,KAAK,aAAa,IAAI,EAAS;AAC3C,EAAI,MACF,EAAI,OAAO,EAAM,EACb,EAAI,SAAS,KAAG,KAAK,aAAa,OAAO,EAAS;;CAmB1D,iBAAyB,GAAwC;EAC/D,IAAM,IAAQ,KAAK,WAAW,IAAI,EAAM;AACxC,MAAI,CAAC,EAAO,QAAO,EAAE;EAIrB,IAAI,IAAW,EAAM,MACf,IAAe,IAAI,IAAY,CAAC,EAAS,MAAM,CAAC;AACtD,SAAO,EAAS,UACV,GAAa,IAAI,EAAS,OAAO,GADf;GAEtB,IAAM,IAAa,KAAK,WAAW,IAAI,EAAS,OAAO;AACvD,OAAI,CAAC,KAAc,EAAW,KAAK,aAAa,EAAS,SAAU;AAEnE,GADA,IAAW,EAAW,MACtB,EAAa,IAAI,EAAS,MAAM;;EAKlC,IAAM,IAAW,EAAS,UACpB,IAAa,EAAS,OACtB,IAAqC,EAAE,EAEvC,IAAe,KAAK,aAAa,IAAI,EAAS;AACpD,MAAI,EACF,MAAK,IAAM,KAAW,GAAc;GAClC,IAAM,IAAa,KAAK,WAAW,IAAI,EAAQ;AAC/C,GAAI,KAAc,KAAK,aAAa,EAAW,MAAM,EAAW,IAC9D,EAAS,KAAK,EAAW;;AAS/B,SADA,EAAS,MAAM,GAAG,MAAM,KAAK,cAAc,GAAG,EAAE,CAAC,EAC1C,EAAS,KAAK,MAAM,EAAE,KAAK;;CAWpC,aAAqB,GAA6B,GAA6B;AAC7E,MAAI,EAAK,UAAU,EAAY,QAAO;EACtC,IAAI,IAAU,GACR,IAAU,IAAI,IAAY,CAAC,EAAQ,MAAM,CAAC;AAChD,SAAO,EAAQ,SAAQ;AACrB,OAAI,EAAQ,WAAW,EAAY,QAAO;AAC1C,OAAI,EAAQ,IAAI,EAAQ,OAAO,CAAE;GACjC,IAAM,IAAS,KAAK,WAAW,IAAI,EAAQ,OAAO;AAClD,OAAI,CAAC,EAAQ;AAEb,GADA,IAAU,EAAO,MACjB,EAAQ,IAAI,EAAQ,MAAM;;AAE5B,SAAO;;CAST,aAAa,GAAuB;EAClC,IAAM,IAAQ,KAAK,WAAW,IAAI,EAAM;AACxC,MAAI,CAAC,EAAO,QAAO;EAEnB,IAAI,IAAU,EAAM,MACd,IAAU,IAAI,IAAY,CAAC,EAAQ,MAAM,CAAC;AAChD,SAAO,EAAQ,UACT,GAAQ,IAAI,EAAQ,OAAO,GADV;GAErB,IAAM,IAAa,KAAK,WAAW,IAAI,EAAQ,OAAO;AACtD,OAAI,CAAC,KAAc,EAAW,KAAK,aAAa,EAAQ,SAAU;AAElE,GADA,IAAU,EAAW,MACrB,EAAQ,IAAI,EAAQ,MAAM;;AAE5B,SAAO,EAAQ;;CAOjB,aAAa,GAA0D;AACrE,OAAK,QAAQ,MAAM,8BAA8B;EACjD,IAAM,IAAkC,EAAE,EACpC,oBAAc,IAAI,KAAa,EAG/B,oBAAiB,IAAI,KAAqB;AAEhD,OAAK,IAAM,KAAY,KAAK,aAAa;GACvC,IAAM,IAAO,EAAS,MAChB,EAAE,UAAO,gBAAa;AAG5B,OAAI,MAAa,KAAA,KAAa,CAAC,EAAY,IAAI,EAAS,CACtD;GAIF,IAAM,IAAQ,KAAK,iBAAiB,EAAM;AAC1C,OAAI,EAAM,SAAS,GAAG;IACpB,IAAM,IAAc,KAAK,aAAa,EAAM,EACxC,IAAa,EAAe,IAAI,EAAY;AAChD,QAAI,MAAe,KAAA,GAAW;KAC5B,IAAM,IAAc,EAAW,IAAI,EAAY;AAE/C,SAAI,KAAe,EAAM,MAAM,MAAM,EAAE,UAAU,EAAY,CAC3D,KAAa;UACR;MACL,IAAM,IAAS,EAAM,GAAG,GAAG;AAC3B,UAAI,CAAC,EAAQ;AACb,UAAa,EAAO;;AAEtB,OAAe,IAAI,GAAa,EAAW;;AAE7C,QAAI,MAAU,EACZ;;AAKJ,GADA,EAAY,IAAI,EAAM,EACtB,EAAO,KAAK,EAAK;;AAGnB,SAAO;;CAGT,YAAY,GAA2B;AAErC,SADA,KAAK,QAAQ,MAAM,8BAA8B,EAAE,UAAO,CAAC,EACpD,KAAK,iBAAiB,EAAM,CAAC,KAAK,MAAM,EAAE,QAAQ;;CAG3D,gBAAgB,GAAwC;AACtD,SAAO,KAAK,iBAAiB,EAAM;;CAGrC,YAAY,GAAwB;AAClC,SAAO,KAAK,iBAAiB,EAAM,CAAC,SAAS;;CAG/C,QAAQ,GAAkD;AAExD,SADA,KAAK,QAAQ,MAAM,0BAA0B,EAAE,UAAO,CAAC,EAChD,KAAK,WAAW,IAAI,EAAM,EAAE;;CAGrC,WAAW,GAAmD;AAE5D,SADA,KAAK,QAAQ,MAAM,6BAA6B,EAAE,UAAO,CAAC,EACnD,KAAK,WAAW,IAAI,EAAM,EAAE,KAAK;;CAO1C,OAAO,GAAe,GAAmB,GAAiC,GAAuB;EAC/F,IAAM,IAAW,EAAA,oBAA0B,KAAA,GACrC,IAAS,EAAA,qBAA2B,KAAA,GAEpC,IAAW,KAAK,WAAW,IAAI,EAAM;AAC3C,MAAI,GAAU;AAkBZ,GAdA,EAAS,KAAK,UAAU,GACpB,OAAO,KAAK,EAAQ,CAAC,SAAS,MAChC,EAAS,KAAK,UAAU,EAAE,GAAG,GAAS,GAIpC,KAAU,CAAC,EAAS,KAAK,WAC3B,KAAK,QAAQ,MAAM,mCAAmC;IAAE;IAAO;IAAQ,CAAC,EACxE,EAAS,KAAK,SAAS,GAEvB,KAAK,cAAc,EAAS,EAC5B,KAAK,cAAc,EAAS,EAC5B,KAAK,uBAEP,KAAK,SAAS,KAAK,SAAS;AAC5B;;AAGF,OAAK,QAAQ,MAAM,qCAAqC;GAAE;GAAO;GAAU;GAAQ,CAAC;EAYpF,IAAM,IAAmC;GAAE,MAVP;IAClC,MAAM;IACN;IACA;IACA;IACA;IACA,SAAS,EAAE,GAAG,GAAS;IACvB;IACD;GAEgD,WAAW,KAAK;GAAe;AAKhF,EAJA,KAAK,WAAW,IAAI,GAAO,EAAS,EACpC,KAAK,kBAAkB,GAAU,EAAM,EACvC,KAAK,cAAc,EAAS,EAC5B,KAAK,sBACL,KAAK,SAAS,KAAK,SAAS;;CAG9B,OAAO,GAAqB;EAC1B,IAAM,IAAQ,KAAK,WAAW,IAAI,EAAM;AACxC,MAAI,CAAC,EAAO;AAEZ,OAAK,QAAQ,MAAM,kBAAkB,EAAE,UAAO,CAAC;EAE/C,IAAM,EAAE,YAAS;AAcjB,EAXA,KAAK,uBAAuB,EAAK,UAAU,EAAM,EAGjD,KAAK,cAAc,EAAM,EAGzB,KAAK,WAAW,OAAO,EAAM,EAI7B,KAAK,sBACL,KAAK,SAAS,KAAK,SAAS;;CAQ9B,mBAA6C;AAC3C,OAAK,QAAQ,MAAM,kCAAkC;EACrD,IAAM,oBAAS,IAAI,KAA0B;AAC7C,OAAK,IAAM,CAAC,GAAQ,MAAa,KAAK,gBAAgB;GACpD,IAAI,IAAM,EAAO,IAAI,EAAS;AAK9B,GAJK,MACH,oBAAM,IAAI,KAAa,EACvB,EAAO,IAAI,GAAU,EAAI,GAE3B,EAAI,IAAI,EAAO;;AAEjB,SAAO;;CAOT,GACE,GACA,GACY;EAEZ,IAAM,IAAK;AAEX,SADA,KAAK,SAAS,GAAG,GAAO,EAAG,QACd;AACX,QAAK,SAAS,IAAI,GAAO,EAAG;;;CAYhC,gBAAgB,GAAgC;AAE9C,EADA,KAAK,QAAQ,MAAM,iCAAiC,EACpD,KAAK,SAAS,KAAK,gBAAgB,EAAI;;CAOzC,SAAS,GAAiC;AAExC,EADA,KAAK,QAAQ,MAAM,2BAA2B,EAAE,QAAQ,EAAM,QAAQ,CAAC,EACvE,KAAK,SAAS,KAAK,QAAQ,EAAM;;CAQnC,UAAU,GAAgB,GAAwB;AAEhD,EADA,KAAK,QAAQ,MAAM,4BAA4B;GAAE;GAAQ;GAAU,CAAC,EACpE,KAAK,eAAe,IAAI,GAAQ,EAAS;;CAO3C,YAAY,GAAsB;AAEhC,EADA,KAAK,QAAQ,MAAM,8BAA8B,EAAE,WAAQ,CAAC,EAC5D,KAAK,eAAe,OAAO,EAAO;;GAezB,KAAwB,MAA0C,IAAI,EAAY,EAAO,EC7bhG,KAA+B,MAAmE;CAEtG,IAAM,IAAgB,CAAC,GAAG,EAAM,YAAY,CAAC,YAAY,EAGnD,IAAU,EAAM,MAAM,eAAe,EACrC,oBAAQ,IAAI,KAUf,EACG,IAAqB,EAAM,MAAM,mBAAmB,EACtD,IAAe,GAGb,oBAAkB,IAAI,KAAqC,EAC3D,oBAAkB,IAAI,KAAqB,EAE3C,IAA2B,EAAE,EAK7B,IAAkG,EAAE;AAE1G,MAAK,IAAM,KAAO,GAAe;EAC/B,IAAM,IAA6C,EAAQ,OAAO,EAAI,EAChE,IAAU,EAAW,EAAI,EACzB,IAAS,EAAQ,IACjB,IAAQ,EAAQ,IAChB,IAAS,EAAI,QACb,IAAc,EAAQ;AAI5B,MAAI,GAAa;AACf,QAAK,IAAM,KAAQ,EAAM,QAAQ,CAC/B,KAAI,EAAK,WAAW,IAAI,EAAY,EAAE;IAEpC,IAAM,IADa,CAAC,GAAG,EAAK,WAAW,MAAM,CAAC,CAClB,QAAQ,EAAY,EAC1C,IAAa,MAAa,KAAK,KAAA,IAAY,EAAK,YAAY,SAAS;AAK3E,IAJI,KACF,EAAK,YAAY,YAAY,GAAa,EAAW,EAEvD,EAAK,YAAY,eAAe,EAAQ,EACxC,EAAoB,KAAK;KAAE,aAAa,EAAK;KAAa,WAAW;KAAa,CAAC;AACnF;;AAGJ;;AAGF,MAAI,GAAQ;GACV,IAAI,IAAO,EAAM,IAAI,EAAO;AAc5B,OAbK,MACH,IAAO;IACL,aAAa,EAAM,MAAM,mBAAmB;IAC5C,WAAW;IACX,4BAAY,IAAI,KAAK;IACrB,4BAAY,IAAI,KAAK;IACtB,EACD,EAAM,IAAI,GAAQ,EAAK,GAMrB,GAAO;IACT,IAAM,IAAW,EAAK,WAAW,IAAI,EAAM;AAC3C,IAAK,IAGM,OAAO,KAAK,EAAQ,CAAC,SAAS,KACvC,OAAO,OAAO,GAAU,EAAQ,IAHhC,EAAK,WAAW,IAAI,GAAO,EAAE,GAAG,GAAS,CAAC,EACtC,KAAQ,EAAK,WAAW,IAAI,GAAO,EAAO;;AAKlD,KAAK,YAAY,eAAe,EAAQ;SACnC;AACL,KAAmB,eAAe,EAAQ;AAG1C,QAAK,IAAM,KAAU,EACnB,KAAI,EAAO,SAAS,aAAa,GAAO;AACtC,MAAe,KAAK,EAAM;IAC1B,IAAM,IAAmB,EAAgB,IAAI,EAAM;AACnD,IAAK,IAGM,OAAO,KAAK,EAAQ,CAAC,SAAS,KACvC,OAAO,OAAO,GAAkB,EAAQ,IAHxC,EAAgB,IAAI,GAAO,EAAE,GAAG,GAAS,CAAC,EACtC,KAAQ,EAAgB,IAAI,GAAO,EAAO;;;;AAYxD,MAAK,IAAM,EAAE,gBAAa,kBAAe,EACvC,GAAY,gBAAgB,EAAU;CAIxC,IAAM,IAAqC,EAAE;AAG7C,MAAK,IAAM,CAAC,GAAG,MAAQ,EAAmB,kBAAkB,SAAS,EAAE;EACrE,IAAM,IAAM,EAAe;AAC3B,IAAU,KAAK;GACb,SAAS;GACT,SAAS,IAAO,EAAgB,IAAI,EAAI,IAAI,EAAE,GAAI,EAAE;GACpD,QAAQ,IAAO,EAAgB,IAAI,EAAI,IAAI,KAAM;GAClD,CAAC;;CAGJ,IAAM,IAAS,CAAC,GAAG,EAAM,QAAQ,CAAC,CAAC,UAAU,GAAG,MAAM,EAAE,YAAY,EAAE,UAAU;AAChF,MAAK,IAAM,KAAQ,GAAQ;EAIzB,IAAM,IAAgB,CAAC,GAAG,EAAK,WAAW,SAAS,CAAC,EAChD,IAAY;AAEhB,OAAK,IAAM,KAAO,EAAK,YAAY,mBAAmB;GACpD,IAAM,IAAQ,EAAc;AAC5B,OAAI,GAAO;IACT,IAAM,CAAC,GAAK,KAAQ;AAMpB,IALA,EAAU,KAAK;KACb,SAAS;KACT,SAAS;KACT,QAAQ,EAAK,WAAW,IAAI,EAAI,IAAI;KACrC,CAAC,EACF;SAEA,GAAU,KAAK;IAAE,SAAS;IAAK,SAAS,EAAE;IAAE,QAAQ;IAAI,CAAC;;;AAO/D,QAAO,EAAU,YAAY;GAWzB,MAAqC,MAAmE;AAC5G,KAAI,EAAM,gBAAgB,EAAM,sBAAsB,EAAM,YAAY,OACtE,QAAO,EAAM;CAEf,IAAM,IAAS,EAAU,EAAM;AAG/B,QAFA,EAAM,eAAe,GACrB,EAAM,oBAAoB,EAAM,YAAY,QACrC;GAgDH,KACJ,GACA,MACS;AACT,MAAK,IAAM,KAAO,GAAa;EAC7B,IAAM,IAAU,EAAW,EAAI,EACzB,IAAQ,EAAQ;AAKtB,MAJI,CAAC,KAID,EAAA,gBAAuB;EAE3B,IAAM,IAAS,EAAI,QACb,IAAmB,MAAW,oBAAA,qBAAuC,GAKrE,IACJ,EAAA,qBAA2B,WAC1B,MAAW,oBAAoB,MAAW,oBAAoB,MAAW,mBACtE,IAAS,EAAQ,IACjB,IAAa,MAAW,cAAc,MAAW;AAIvD,GAFI,KAAoB,MAAkB,EAAM,cAAc,IAAI,EAAM,GACpE,KAAoB,MAAY,EAAM,iBAAiB,IAAI,EAAM,EACjE,EAAM,cAAc,IAAI,EAAM,IAAI,EAAM,iBAAiB,IAAI,EAAM,IACrE,EAAM,gBAAgB,IAAI,EAAM;;GAoBhC,IAAkB,OACtB,GACA,GACA,MACkB;AAGlB,CAFA,EAAM,YAAY,KAAK,GAAG,EAAS,MAAM,EACzC,EAAM,eAAe,GACrB,EAAoB,GAAO,EAAS,MAAM;CAE1C,IAAM,IAAS,EAAM,gBAAgB;AACrC,QAAO,EAAM,gBAAgB,OAAO,KAAU,EAAS,SAAS,GAAE;AAChE,IAAM,OAAO,MAAM,uDAAuD;GACxE,WAAW,EAAM,YAAY;GAC7B,WAAW,EAAM,gBAAgB;GAClC,CAAC;EACF,IAAM,IAAW,MAAM,EAAS,MAAM;AACtC,MAAI,CAAC,EAAU;AAIf,EAHA,IAAW,GACX,EAAM,YAAY,KAAK,GAAG,EAAS,MAAM,EACzC,EAAM,eAAe,GACrB,EAAoB,GAAO,EAAS,MAAM;;GAcxC,KAAiC,GAAuC,MAAyC;CAGrH,IAAM,IAAe,GAAgB,EAAM,EAErC,IAAY,EAAa,MAAM,EAAM,eAAe,EAAM,gBAAgB,EAAM,EAChF,IAAa,CAAC,GAAG,EAAU,CAAC,YAAY;AAC9C,GAAM,iBAAiB,EAAU;CAEjC,IAAM,IAAgB,EAAa,SAAS,EAAM,eAC5C,IAAgB,EAAM,cAAc,SAAS,IAAI,IAIjD,IADc,EAAM,YAAY,SAAS,EAAM,mBACtB,IAAI,EAAM,YAAY,MAAM,EAAM,iBAAiB,CAAC,YAAY,GAAG,EAAE;AAGpG,QAFA,EAAM,mBAAmB,EAAM,YAAY,QAEpC;EACL,OAAO,EAAW,KAAK,OAAO;GAAE,SAAS,EAAE;GAAS,SAAS,EAAE;GAAS,QAAQ,EAAE;GAAQ,EAAE;EAC5F,aAAa;EACb,eAAe,KAAiB;EAChC,MAAM,YAAY;AAChB,OAAI,EACF,QAAO,EAAY,GAAO,EAAM;AAElC,OAAI,CAAC,KAAiB,CAAC,EAAM,aAAc;GAC3C,IAAM,IAAW,MAAM,EAAM,aAAa,MAAM;AAC3C,SAEL,QADA,MAAM,EAAgB,GAAO,GAAU,EAAM,EACtC,EAAY,GAAO,EAAM;;EAEnC;GAuBU,KAAgB,OAC3B,GACA,GACA,GACA,MACmC;CACnC,IAAM,IAAQ,GAAS,SAAS,KAC1B,IAAwC;EAC5C;EACA,aAAa,EAAE;EACf,eAAe;EACf,kBAAkB;EAClB,cAAc,KAAA;EACd,cAAc,KAAA;EACd,mBAAmB;EACnB,+BAAe,IAAI,KAAa;EAChC,kCAAkB,IAAI,KAAa;EACnC,iCAAiB,IAAI,KAAa;EAClC;EACD;AAED,GAAO,MAAM,oBAAoB,EAAE,UAAO,CAAC;CAI3C,IAAM,IAAY,IAAQ;AAK1B,QAHA,MAAM,EAAQ,QAAQ,EAEtB,MAAM,EAAgB,GADL,MAAM,EAAQ,QAAQ;EAAE,aAAa;EAAM,OAAO;EAAW,CAAC,EACxC,EAAM,EACtC,EAAY,GAAO,EAAM;GC1XrB,KAAb,MAA6E;CAwD3E,YAAY,GAAwC;AAgBlD,2CAxDmC,IAAI,KAA8B,yCAGpC,IAAI,KAAa,yBAGhB,EAAE,8BAGK,EAAE,6CAGf,IAAI,KAAa,yBAGrB,2BAMkC,EAAE,iBAGnB,EAAE,sBAOG,EAAE,gCAGjB,yBAET,8BACK,mBACX,IAGhB,KAAK,QAAQ,EAAQ,MACrB,KAAK,WAAW,EAAQ,SACxB,KAAK,SAAS,EAAQ,OACtB,KAAK,gBAAgB,EAAQ,cAC7B,KAAK,WAAW,EAAQ,SACxB,KAAK,UAAU,EAAQ,OAAO,YAAY,EAAE,WAAW,QAAQ,CAAC,EAChE,KAAK,QAAQ,MAAM,iBAAiB,EACpC,KAAK,WAAW,IAAI,EAA4B,KAAK,QAAQ,EAG7D,KAAK,eAAe,KAAK,mBAAmB,EAC5C,KAAK,yBAAyB,KAAK,MAAM,mBACzC,KAAK,uBAAuB,KAAK,aAAa,EAG9C,KAAK,QAAQ,KACX,KAAK,MAAM,GAAG,gBAAgB;AAC5B,QAAK,eAAe;IACpB,EACF,KAAK,MAAM,GAAG,iBAAiB,MAAQ;AACrC,QAAK,mBAAmB,EAAI;IAC5B,EACF,KAAK,MAAM,GAAG,SAAS,MAAU;AAC/B,QAAK,YAAY,EAAM;IACvB,CACH;;CAOH,cAA0B;AACxB,SAAO,KAAK,cAAc,CAAC,KAAK,MAAM,EAAE,QAAQ;;CAIlD,eAAwC;AACtC,SAAO,KAAK;;CAUd,oBAAqD;EACnD,IAAM,IAAQ,KAAK,MAAM,aAAa,KAAK,oBAAoB,CAAC;AAEhE,SADI,KAAK,gBAAgB,SAAS,IAAU,IACrC,EAAM,QAAQ,MAAM,CAAC,KAAK,gBAAgB,IAAI,EAAE,MAAM,CAAC;;CAGhE,WAAoB;AAClB,SAAO,KAAK,gBAAgB,SAAS,KAAK,KAAK;;CAGjD,MAAM,UAAU,IAAQ,KAAoB;AACtC,aAAK,WAAW,KAAK,gBAEzB;GADA,KAAK,gBAAgB,IACrB,KAAK,QAAQ,MAAM,4BAA4B,EAAE,UAAO,CAAC;AAEzD,OAAI;AAEF,QAAI,KAAK,gBAAgB,SAAS,GAAG;KACnC,IAAM,IAAQ,KAAK,gBAAgB,OAAO,CAAC,GAAO,EAAM;AACxD,UAAK,iBAAiB,EAAM;AAC5B;;AAIF,QAAI,CAAC,KAAK,mBAAmB,CAAC,KAAK,kBAAkB;AAEnD,WAAM,KAAK,eAAe,EAAM;AAChC;;AAGF,QAAI,CAAC,KAAK,gBAAiB;AAG3B,QAAI,CAAC,KAAK,kBAAkB,SAAS,EAAE;AACrC,UAAK,kBAAkB;AACvB;;IAGF,IAAM,IAAW,MAAM,KAAK,iBAAiB,MAAM;AAGnD,QAAI,KAAK,WAAW,CAAC,GAAU;AAC7B,KAAK,MAAU,KAAK,kBAAkB;AACtC;;AAGF,UAAM,KAAK,eAAe,GAAU,EAAM;YACnC,GAAO;AAEd,UADA,KAAK,QAAQ,MAAM,mCAAmC,EAAE,UAAO,CAAC,EAC1D;aACE;AACR,SAAK,gBAAgB;;;;CASzB,OAAO,GAAe,GAAqB;AACzC,OAAK,QAAQ,MAAM,yBAAyB;GAAE;GAAO;GAAO,CAAC;EAC7D,IAAM,IAAQ,KAAK,MAAM,gBAAgB,EAAM;AAC/C,MAAI,EAAM,UAAU,EAAG;EACvB,IAAM,IAAc,KAAK,MAAM,aAAa,EAAM,EAC5C,IAAU,KAAK,IAAI,GAAG,KAAK,IAAI,GAAO,EAAM,SAAS,EAAE,CAAC,EACxD,IAAW,EAAM;AAClB,QACL,KAAK,kBAAkB,IAAI,GAAa;GAAE,MAAM;GAAQ,YAAY,EAAS;GAAO,CAAC,EACrF,KAAK,QAAQ,MAAM,yBAAyB;GAAE;GAAO,OAAO;GAAS,YAAY,EAAS;GAAO,CAAC,EAClG,KAAK,eAAe,KAAK,mBAAmB,EAC5C,KAAK,uBAAuB,KAAK,aAAa,EAC9C,KAAK,SAAS,KAAK,SAAS;;CAG9B,iBAAiB,GAAuB;AACtC,OAAK,QAAQ,MAAM,mCAAmC,EAAE,UAAO,CAAC;EAChE,IAAM,IAAQ,KAAK,MAAM,gBAAgB,EAAM;AAC/C,MAAI,EAAM,UAAU,EAAG,QAAO;EAC9B,IAAM,IAAc,KAAK,MAAM,aAAa,EAAM,EAC5C,IAAM,KAAK,kBAAkB,IAAI,EAAY;AACnD,MAAI,CAAC,KAAO,EAAI,SAAS,UAAW,QAAO,EAAM,SAAS;EAC1D,IAAM,IAAM,EAAM,WAAW,MAAM,EAAE,UAAU,EAAI,WAAW;AAE9D,SADI,MAAQ,KAAW,EAAM,SAAS,IAC/B;;CAGT,YAAY,GAA2B;AACrC,SAAO,KAAK,MAAM,YAAY,EAAM;;CAGtC,YAAY,GAAwB;AAClC,SAAO,KAAK,MAAM,YAAY,EAAM;;CAGtC,QAAQ,GAAkD;AACxD,SAAO,KAAK,MAAM,QAAQ,EAAM;;CAQlC,MAAM,KAAK,GAA8B,GAAoD;AAE3F,MADA,KAAK,QAAQ,MAAM,sBAAsB,EACrC,KAAK,QACP,OAAM,IAAI,EAAK,UAAU,kCAAkC,EAAU,iBAAiB,IAAI;EAK5F,IAAM,IAAU,KAAK,cAAc,EAC7B,IAAS,MAAM,KAAK,cAAc,GAAO,GAAS,EAAQ;AAIhE,MAAI,GAAS,QAAQ;GACnB,IAAM,IAAY,KAAK,MAAM,aAAa,EAAQ,OAAO;AAEzD,OAAI,EAAO,iBAAiB,SAAS,GAAG;IAItC,IAAM,IAAY,EAAO,iBAAiB,GAAG,GAAG;AAChD,IAAI,MACF,KAAK,kBAAkB,IAAI,GAAW;KAAE,MAAM;KAAQ,YAAY;KAAW,CAAC,EAC9E,KAAK,eAAe,KAAK,mBAAmB,EAC5C,KAAK,uBAAuB,KAAK,aAAa,EAC9C,KAAK,SAAS,KAAK,SAAS;UAEzB;AAML,IADA,KAAK,kBAAkB,IAAI,GAAW;KAAE,MAAM;KAAW,QAAQ,EAAO;KAAQ,CAAC,EACjF,KAAK,QAAQ,MAAM,qDAAqD;KACtE,QAAQ,EAAQ;KAChB;KACA,QAAQ,EAAO;KAChB,CAAC;IAGF,IAAM,IAAY,KAAK,MAAM,GAAG,SAAS,MAAQ;AAC/C,SAAI,EAAI,SAAA,qBAA2B,EAAI,WAAW,EAAO,OAAQ;KACjE,IAAM,IAAM,KAAK,kBAAkB,IAAI,EAAU;AAIjD,KAHI,GAAK,SAAS,aAAa,EAAI,WAAW,EAAO,UACnD,KAAK,kBAAkB,OAAO,EAAU,EAE1C,GAAW;KACX,IAAM,IAAM,KAAK,QAAQ,QAAQ,EAAU;AAC3C,KAAI,MAAQ,MAAI,KAAK,QAAQ,OAAO,GAAK,EAAE;MAC3C;AACF,SAAK,QAAQ,KAAK,EAAU;;;AAIhC,SAAO;;CAIT,MAAM,WAAW,GAAmB,GAAoD;AACtF,OAAK,QAAQ,MAAM,6BAA6B,EAAE,cAAW,CAAC;EAE9D,IAAM,IAAO,KAAK,MAAM,QAAQ,EAAU;AAC1C,MAAI,CAAC,EACH,OAAM,IAAI,EAAK,UACb,oDAAoD,KACpD,EAAU,iBACV,IACD;EAEH,IAAM,IAAW,EAAK;AAEtB,SAAO,KAAK,KAAK,EAAE,EAAE;GACnB,GAAG;GACH,MAAM;IACJ,SAAS,KAAK,kBAAkB,EAAU;IAC1C,GAAG,GAAS;IACb;GACD,QAAQ;GACR,QAAQ;GACT,CAAC;;CAIJ,MAAM,KACJ,GACA,GACA,GAC6B;AAC7B,OAAK,QAAQ,MAAM,uBAAuB,EAAE,cAAW,CAAC;EAExD,IAAM,IAAO,KAAK,MAAM,QAAQ,EAAU;AAC1C,MAAI,CAAC,EACH,OAAM,IAAI,EAAK,UACb,8CAA8C,KAC9C,EAAU,iBACV,IACD;EAEH,IAAM,IAAW,EAAK;AAEtB,SAAO,KAAK,KAAK,GAAa;GAC5B,GAAG;GACH,MAAM;IACJ,SAAS,KAAK,kBAAkB,EAAU;IAC1C,GAAG,GAAS;IACb;GACD,QAAQ;GACR,QAAQ;GACT,CAAC;;CAGJ,MAAM,OAAO,GAAe,GAAkB,GAAoD;AAChG,MAAI,KAAK,QACP,OAAM,IAAI,EAAK,UAAU,oCAAoC,EAAU,iBAAiB,IAAI;AAE9F,OAAK,QAAQ,MAAM,yBAAyB;GAAE;GAAO,YAAY,EAAO;GAAQ,CAAC;EACjF,IAAM,IAAmC,CAAC;GAAE,MAAM;GAAS;GAAO;GAAQ,CAAC;AAC3E,SAAO,KAAK,cAAc,EAAE,EAAE,GAAS,KAAK,cAAc,EAAE,EAAW;;CAGzE,kBAA0B,GAA4C;AACpE,OAAK,QAAQ,MAAM,oCAAoC,EAAE,cAAW,CAAC;EACrE,IAAM,IAAM,KAAK,cAAc,EACzB,IAAM,EAAI,WAAW,MAAM,EAAE,UAAU,EAAU;AAOvD,SANI,MAAQ,MACV,KAAK,QAAQ,KAAK,qFAAqF,EACrG,cACD,CAAC,EACK,KAEF,EAAI,MAAM,GAAG,EAAI;;CAQ1B,mBAA6C;AAC3C,OAAK,QAAQ,MAAM,kCAAkC;EACrD,IAAM,IAAW,KAAK,MAAM,kBAAkB;AAC9C,MAAI,KAAK,gBAAgB,SAAS,EAAG,QAAO;EAG5C,IAAM,oBAAS,IAAI,KAA0B;AAC7C,OAAK,IAAM,CAAC,GAAU,MAAY,GAAU;GAC1C,IAAM,oBAAW,IAAI,KAAa;AAClC,QAAK,IAAM,KAAU,EACnB,CAAI,KAAK,oBAAoB,IAAI,EAAO,IAAE,EAAS,IAAI,EAAO;AAEhE,GAAI,EAAS,OAAO,KAAG,EAAO,IAAI,GAAU,EAAS;;AAEvD,SAAO;;CAWT,GACE,GACA,GACY;EAEZ,IAAM,IAAK;AAEX,SADA,KAAK,SAAS,GAAG,GAAO,EAAG,QACd;AACX,QAAK,SAAS,IAAI,GAAO,EAAG;;;CAWhC,QAAc;AAGZ,EAFA,KAAK,QAAQ,KAAK,uBAAuB,EACzC,KAAK,UAAU,IACf,KAAK,gBAAgB;AACrB,OAAK,IAAM,KAAS,KAAK,QAAS,IAAO;AAMzC,EALA,KAAK,QAAQ,SAAS,GACtB,KAAK,SAAS,KAAK,EACnB,KAAK,kBAAkB,OAAO,EAC9B,KAAK,gBAAgB,OAAO,EAC5B,KAAK,gBAAgB,SAAS,GAC9B,KAAK,YAAY;;CAOnB,MAAc,eAAe,GAA8B;EAEzD,IAAM,IAAe,IAAI,IAAI,KAAK,MAAM,aAAa,KAAK,oBAAoB,CAAC,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,EAE9F,IAAY,MAAM,GAAc,KAAK,UAAU,KAAK,QAAQ,EAAE,UAAO,EAAE,KAAK,QAAQ;AAC1F,MAAI,KAAK,QAAS;EAClB,IAAM,EAAE,eAAY,gBAAa,MAAM,KAAK,kBAAkB,GAAW,GAAO,EAAa;AAE7F,MAAI,KAAK,QAAS;AAGlB,EADA,KAAK,mBAAmB,GACxB,KAAK,kBAAkB,EAAS,SAAS;EAMzC,IAAM,IAAW,EAAW,MAAM,CAAC,EAAM,EACnC,IAAW,EAAW,MAAM,GAAG,CAAC,EAAM;AAC5C,OAAK,IAAM,KAAK,EACd,MAAK,gBAAgB,IAAI,EAAE,MAAM;AAGnC,EADA,KAAK,gBAAgB,KAAK,GAAG,EAAS,EACtC,KAAK,iBAAiB,EAAS;;CAGjC,MAAc,eAAe,GAA6B,GAA8B;EAEtF,IAAM,IAAe,IAAI,IAAI,KAAK,MAAM,aAAa,KAAK,oBAAoB,CAAC,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,EAE9F,EAAE,eAAY,gBAAa,MAAM,KAAK,kBAAkB,GAAM,GAAO,EAAa;AACxF,MAAI,KAAK,QAAS;AAElB,EADA,KAAK,mBAAmB,GACxB,KAAK,kBAAkB,EAAS,SAAS;EAMzC,IAAM,IAAQ,EAAW,MAAM,CAAC,EAAM,EAChC,IAAW,EAAW,MAAM,GAAG,CAAC,EAAM;AAC5C,OAAK,IAAM,KAAK,EACd,MAAK,gBAAgB,IAAI,EAAE,MAAM;AAGnC,EADA,KAAK,gBAAgB,KAAK,GAAG,EAAS,EACtC,KAAK,iBAAiB,EAAM;;CAG9B,oBAA4B,GAAmC;AAC7D,OAAK,qBAAqB;AAC1B,MAAI;AACF,QAAK,IAAM,KAAQ,EAAK,OAAO;IAC7B,IAAM,IAAQ,EAAK,QAAQ;AACtB,SACL,KAAK,MAAM,OAAO,GAAO,EAAK,SAAS,EAAK,SAAS,EAAK,OAAO;;AAGnE,QAAK,IAAM,KAAO,EAAK,YACrB,MAAK,MAAM,gBAAgB,EAAI;YAEzB;AACR,QAAK,qBAAqB;;;CAI9B,MAAc,kBACZ,GACA,GACA,GACmF;AACnF,OAAK,oBAAoB,EAAU;EACnC,IAAI,IAAO,GAEL,UAAgC;GACpC,IAAI,IAAQ;AACZ,QAAK,IAAM,KAAK,KAAK,MAAM,aAAa,KAAK,oBAAoB,CAAC,CAChE,CAAK,EAAa,IAAI,EAAE,MAAM,IAAE;AAElC,UAAO;;AAGT,SAAO,GAAiB,GAAG,KAAU,EAAK,SAAS,GAAE;GACnD,IAAM,IAAW,MAAM,EAAK,MAAM;AAClC,OAAI,CAAC,KAAY,KAAK,QAAS;AAE/B,GADA,KAAK,oBAAoB,EAAS,EAClC,IAAO;;AAIT,SAAO;GAAE,YADU,KAAK,MAAM,aAAa,KAAK,oBAAoB,CAAC,CAAC,QAAQ,MAAM,CAAC,EAAa,IAAI,EAAE,MAAM,CAAC;GAC1F,UAAU;GAAM;;CAIvC,iBAAyB,GAAsC;AAC7D,OAAK,IAAM,KAAK,EACd,MAAK,gBAAgB,OAAO,EAAE,MAAM;AAEtC,EAAI,EAAM,SAAS,MACjB,KAAK,eAAe,KAAK,mBAAmB,EAC5C,KAAK,uBAAuB,KAAK,aAAa,EAC9C,KAAK,SAAS,KAAK,SAAS;;CAQhC,uBAA+B,GAAuC;EACpE,IAAM,IAAW,KAAS,KAAK,cAAc;AAG7C,EAFA,KAAK,kBAAkB,EAAS,KAAK,MAAM,EAAE,MAAM,EACnD,KAAK,uBAAuB,EAAS,KAAK,MAAM,EAAE,QAAQ,EAC1D,KAAK,sCAAsB,IAAI,KAAa;AAC5C,OAAK,IAAM,KAAK,GAAU;GACxB,IAAM,IAAS,EAAE,QAAQ;AACzB,GAAI,KAAQ,KAAK,oBAAoB,IAAI,EAAO;;;CAIpD,gBAA8B;AAS5B,MAAI,KAAK,mBAAoB;EAE7B,IAAM,IAAiB,KAAK,MAAM;AAQlC,MAAI,MAAmB,KAAK,wBAAwB;AAElD,GADgB,KAAK,aAAa,MAAM,GAAM,MAAM,EAAK,YAAY,KAAK,qBAAqB,GAAG,KAEhG,KAAK,uBAAuB,KAAK,aAAa,KAAK,MAAM,EAAE,QAAQ,EACnE,KAAK,eAAe,CAAC,GAAG,KAAK,aAAa,EAC1C,KAAK,SAAS,KAAK,SAAS;AAE9B;;AAUF,EANA,KAAK,yBAAyB,GAK9B,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B;EAEhC,IAAM,IAAQ,KAAK,mBAAmB,EAChC,IAAS,EAAM,KAAK,MAAM,EAAE,MAAM,EAClC,IAAc,EAAM,KAAK,MAAM,EAAE,QAAQ;AAC/C,EAAI,KAAK,gBAAgB,GAAQ,EAAY,KAC3C,KAAK,eAAe,GACpB,KAAK,uBAAuB,EAAM,EAClC,KAAK,SAAS,KAAK,SAAS;;CAUhC,qBAAkD;EAChD,IAAM,oBAAW,IAAI,KAAqB;AAC1C,OAAK,IAAM,CAAC,GAAW,MAAQ,KAAK,kBAC9B,GAAI,SAAS,aACjB,EAAS,IAAI,GAAW,EAAI,WAAW;AAEzC,SAAO;;CAcT,uBAAqC;AACnC,OAAK,IAAM,KAAS,KAAK,iBAAiB;AACxC,OAAI,CAAC,KAAK,MAAM,YAAY,EAAM,CAAE;GACpC,IAAM,IAAY,KAAK,MAAM,aAAa,EAAM,EAC1C,IAAW,KAAK,kBAAkB,IAAI,EAAU;AAOtD,OAAI,GAAU,SAAS,WAAW;IAEhC,IAAM,IADQ,KAAK,MAAM,gBAAgB,EAAM,CAC1B,GAAG,GAAG;AAC3B,IAAI,KAAU,EAAO,UAAU,KACR,EAAO,QAAA,sBACP,EAAS,WAC5B,KAAK,QAAQ,MAAM,mEAAmE;KACpF;KACA,UAAU,EAAO;KACjB,QAAQ,EAAS;KAClB,CAAC,EACF,KAAK,kBAAkB,IAAI,GAAW;KAAE,MAAM;KAAQ,YAAY,EAAO;KAAO,CAAC;AAGrF;;AAKE,QACJ,KAAK,kBAAkB,IAAI,GAAW;IAAE,MAAM;IAAU,YAAY;IAAO,CAAC;;;CAUhF,4BAA0C;AACxC,OAAK,IAAM,CAAC,GAAW,MAAQ,KAAK,mBAAmB;AACrD,OAAI,EAAI,SAAS,UAAW;GAC5B,IAAM,IAAQ,KAAK,MAAM,gBAAgB,EAAU;AACnD,OAAI,EAAM,UAAU,EAAG;GACvB,IAAM,IAAS,EAAM,GAAG,GAAG;AACvB,IAAC,KAAU,EAAO,UAAU,KACX,EAAO,QAAA,sBACP,EAAI,WACvB,KAAK,QAAQ,MAAM,yEAAyE;IAC1F;IACA,UAAU,EAAO;IACjB,QAAQ,EAAI;IACb,CAAC,EACF,KAAK,kBAAkB,IAAI,GAAW;IAAE,MAAM;IAAQ,YAAY,EAAO;IAAO,CAAC;;;CAKvF,mBAA2B,GAAgC;EAGzD,IAAM,IADU,EAAW,EAAI,CACT;AACtB,MAAI,CAAC,GAAO;AAEV,QAAK,SAAS,KAAK,gBAAgB,EAAI;AACvC;;AAGF,EAAI,KAAK,gBAAgB,SAAS,EAAM,IACtC,KAAK,SAAS,KAAK,gBAAgB,EAAI;;CAI3C,YAAoB,GAAiC;AAEnD,MAAI,KAAK,oBAAoB,IAAI,EAAM,OAAO,EAAE;AAC9C,QAAK,SAAS,KAAK,QAAQ,EAAM;AACjC;;AAMF,EAAI,EAAM,SAAA,uBAA6B,KAAK,oBAAoB,EAAM,KAGpE,KAAK,oBAAoB,IAAI,EAAM,OAAO,EAC1C,KAAK,SAAS,KAAK,QAAQ,EAAM;;CAUrC,oBAA4B,GAAwE;EAClG,IAAM,EAAE,cAAW;AAOnB,SAHI,MAAW,KAAA,IAAkB,KAG1B,KAAK,gBAAgB,SAAS,EAAO;;CAG9C,gBAAwB,GAAkB,GAAkC;AAC1E,MAAI,EAAO,WAAW,KAAK,gBAAgB,OAAQ,QAAO;AAC1D,OAAK,IAAM,CAAC,GAAG,MAAU,EAAO,SAAS,CACvC,KAAI,MAAU,KAAK,gBAAgB,GAAI,QAAO;AAGhD,OAAK,IAAM,CAAC,GAAG,MAAQ,EAAY,SAAS,CAC1C,KAAI,MAAQ,KAAK,qBAAqB,GAAI,QAAO;AAEnD,SAAO;;GAaE,KAAgC,MAC3C,IAAI,GAAY,EAAQ,ECzwBpB,WAA8B,IAM/B,IAAL,yBAAA,GAAA;QACE,EAAA,QAAA,SACA,EAAA,SAAA;EAFG,KAAA,EAAA,CAGJ,EAyBK,KAAN,MAA4F;CAmD1F,YAAY,GAAmD;AAgD7D,wCApF4B,IAAI,KAAa,qCAChB,IAAI,KAAa,qCAGjB,IAAI,KAA0B,wCAI3B,IAAI,KAAkD,yBAGrC,EAAE,gCAK3B,IAAI,KAAoC,gBAYjD,EAAqB,kCAMc,EAAE,EAGpD,KAAK,WAAW,EAAQ,SACxB,KAAK,SAAS,EAAQ,OACtB,KAAK,YAAY,EAAQ,UACzB,KAAK,OAAO,EAAQ,KACpB,KAAK,eAAe,EAAQ,aAG5B,KAAK,aACH,OAAO,EAAQ,WAAY,aACvB,EAAQ,UACR,EAAQ,gBACA,EAAQ,UACd,KAAA,GACR,KAAK,UACH,OAAO,EAAQ,QAAS,aACpB,EAAQ,OACR,EAAQ,aACA,EAAQ,OACd,KAAA,GACR,KAAK,WAAW,EAAQ,SAAS,WAAW,MAAM,KAAK,WAAW,EAClE,KAAK,WAAW,EAAQ,UAAU,GAAW,EAAE,UAAU,EAAS,QAAQ,CAAC,EAAE,YAAY,EACvF,WAAW,mBACZ,CAAC,EAEF,KAAK,WAAW,IAAI,EAAuC,KAAK,QAAQ,EACxE,KAAK,mBAAmB,KAAK,SAAS,UAAU,YAGhD,KAAK,QAAQ,EAAqB,KAAK,QAAQ,EAC/C,KAAK,QAAQ,EAA6B;GACxC,MAAM,KAAK;GACX,SAAS,KAAK;GACd,OAAO,KAAK;GACZ,cAAc,KAAK,cAAc,KAAK,KAAK;GAC3C,QAAQ,KAAK;GACb,eAAe,KAAK,OAAO,OAAO,KAAK,MAAM;GAC9C,CAAC,EACF,KAAK,UAAU,EAA2B,KAAK,OAAO,WAAW,KAAK,KAAK,OAAO,EAAE,KAAK,QAAQ,EACjG,KAAK,WAAW,KAAK,OAAO,eAAe,EAE3C,KAAK,OAAO,IAAI,KAAK,MAAM,EAG3B,KAAK,OAAO,KAAK,OACjB,KAAK,OAAO,KAAK,OAGb,EAAQ,UAAU;GACpB,IAAI;AACJ,QAAK,IAAM,KAAO,EAAQ,UAAU;IAClC,IAAM,IAAQ,OAAO,YAAY,EAC3B,IAAsC,GAAG,IAAgB,GAAO;AAGtE,IAFI,MAAW,EAAY,KAAiB,IAC5C,KAAK,MAAM,OAAO,GAAO,GAAK,EAAY,EAC1C,IAAY;;;AAkBhB,EAZA,KAAK,cAAc,MAAqC;AACtD,QAAK,eAAe,EAAY;KAElC,KAAK,iBAAiB,KAAK,SAAS,UAAU,KAAK,WAAW,EAM9D,KAAK,yBAAyB,MAAyC;AACrE,QAAK,0BAA0B,EAAY;KAE7C,KAAK,SAAS,GAAG,KAAK,sBAAsB;;CAO9C,eAAuB,GAAwC;AACzD,WAAK,WAAW,EAAqB,OAEzC,KAAI;AAGF,OAAI,EAAY,SAAA,qBAA2B;IACzC,IAAM,IAAU,EAAW,EAAY,EACjC,IAAS,EAAQ,IACjB,IAAU,EAAA,4BAAkC;AAClD,QAAI,GAAQ;AACV,UAAK,MAAM,UAAU,GAAQ,EAAQ;KACrC,IAAM,IAAY,EAAQ,IACpB,IAAS,EAAQ;AACvB,UAAK,MAAM,SAAS;MAClB,MAAM;MACN;MACA,UAAU;MACV,GAAI,MAAc,KAAA,KAAa,EAAE,QAAQ,GAAW;MACpD,GAAI,MAAW,KAAA,KAAa,EAAE,WAAQ;MACvC,CAAC;;AAEJ,SAAK,MAAM,gBAAgB,EAAY;AACvC;;AAGF,OAAI,EAAY,SAAA,mBAAyB;IACvC,IAAM,IAAU,EAAW,EAAY,EACjC,IAAS,EAAQ,IACjB,IAAU,EAAA,4BAAkC,IAE5C,IAAU,EAAA,yBAA+B;AAC/C,QAAI,GAAQ;AAGV,KAFA,KAAK,QAAQ,YAAY,EAAO,EAChC,KAAK,eAAe,OAAO,EAAO,EAClC,KAAK,MAAM,YAAY,EAAO;KAE9B,IAAM,IAAS,KAAK,YAAY,IAAI,EAAO;AAC3C,SAAI,GAAQ;AACV,WAAK,IAAM,KAAO,EAAQ,MAAK,WAAW,OAAO,EAAI;AACrD,WAAK,YAAY,OAAO,EAAO;;AAGjC,KADA,KAAK,YAAY,OAAO,EAAO,EAC/B,KAAK,MAAM,SAAS;MAAE,MAAM;MAAgB;MAAQ,UAAU;MAAS;MAAQ,CAAC;;AAElF,SAAK,MAAM,gBAAgB,EAAY;AACvC;;GAIF,IAAM,IAAU,KAAK,SAAS,OAAO,EAAY,EAC3C,IAAU,EAAW,EAAY,EACjC,IAAS,EAAY,QAIrB,IAAc,EAAQ;AAC5B,OAAI,GAAa;AACf,SAAK,IAAM,KAAU,EACnB,CAAI,EAAO,SAAS,WAClB,KAAK,sBAAsB,GAAa,EAAO;AAGnD;;GAOF,IAAM,IAAS,EAAQ;AACvB,GAAI,KACF,KAAK,2BAA2B,GAAQ,GAAS,EAAO;AAG1D,QAAK,IAAM,KAAU,EACnB,CAAI,EAAO,SAAS,YAClB,KAAK,qBAAqB,EAAO,SAAS,GAAS,GAAQ,EAAY,OAAO,GAE9E,KAAK,mBAAmB,GAAQ,EAAQ;AAO5C,QAAK,MAAM,gBAAgB,EAAY;WAChC,GAAO;GACd,IAAM,IAAQ,aAAiB,EAAK,YAAY,IAAQ,KAAA;AACxD,QAAK,SAAS,KACZ,SACA,IAAI,EAAK,UACP,sCAAsC,aAAiB,QAAQ,EAAM,UAAU,OAAO,EAAM,IAC5F,EAAU,4BACV,KACA,EACD,CACF;;;CAWL,qBACE,GACA,GACA,GACA,GACM;EAEN,IAAM,IAAQ,EAAQ;AACtB,MAAI,KAAS,KAAK,WAAW,IAAI,EAAM,EAAE;AAEvC,QAAK,iBAAiB,GAAS,GAAS,EAAO;AAC/C;;AAGF,EAAI,MAAW,oBACb,KAAK,iBAAiB,GAAS,GAAS,EAAO;;CASnD,mBAA2B,GAAyC,GAAuC;AACzG,MAAI,EAAO,SAAS,QAAS;EAC7B,IAAM,IAAQ,EAAO,OACf,IAAS,EAAQ;AAClB,SAOL;OAAI,KAAK,QAAQ,MAAM,GAAQ,EAAM,EAAE;AAErC,IADA,KAAK,mBAAmB,GAAQ,EAAO,EACnC,KAAK,OAAO,WAAW,EAAM,IAAE,KAAK,eAAe,OAAO,EAAO;AACrE;;AAIE,QAAK,YAAY,IAAI,EAAO,IAAI,CAAC,KAAK,eAAe,IAAI,EAAO,KAIpE,KAAK,mBAAmB,GAAQ,EAAO,EACnC,KAAK,OAAO,WAAW,EAAM,IAAE,KAAK,eAAe,OAAO,EAAO;;;CAUvE,sBAA8B,GAAqB,GAA+C;AAChG,OAAK,QAAQ,MAAM,4CAA4C,EAAE,gBAAa,CAAC;EAE/E,IAAM,IAAe,KAAK,MAAM,QAAQ,EAAY;AACpD,MAAI,CAAC,GAAc;AACjB,QAAK,QAAQ,MAAM,uEAAuE,EAAE,gBAAa,CAAC;AAC1G;;EAGF,IAAM,IAAc,KAAK,OAAO,mBAAmB;AAEnD,EADA,EAAY,YAAY,GAAa,EAAa,QAAQ,EAC1D,EAAY,eAAe,CAAC,EAAO,CAAC;EAEpC,IAAM,IAAa,EAAY,SAAS,GAAG,GAAG;AAC9C,EAAI,KACF,KAAK,MAAM,OAAO,GAAa,GAAY,EAAa,SAAS,EAAa,OAAO;;CASzF,0BAAkC,GAA4C;AAC5E,MAAI,KAAK,WAAW,EAAqB,OAAQ;EAEjD,IAAM,EAAE,YAAS,eAAY;AAG7B,MAAI,MAAY,cAAc,CAAC,KAAK,kBAAkB;AACpD,QAAK,mBAAmB;AACxB;;AASF,MAAI,EAFF,MAAY,YAAY,MAAY,eAAe,MAAY,cAAe,MAAY,cAAc,CAAC,GAEtF;AAErB,OAAK,QAAQ,MAAM,wEAAwE;GACzF;GACA;GACA,UAAU,EAAY;GACvB,CAAC;EAEF,IAAM,IAAM,IAAI,EAAK,UACnB,sDAAsD,IAAU,MAAY,aAAa,qBAAqB,GAAG,IACjH,EAAU,uBACV,KACA,EAAY,OACb;AAKD,OAAK,IAAM,KAAU,KAAK,YACxB,MAAK,QAAQ,YAAY,GAAQ,EAAI;AAGvC,OAAK,SAAS,KAAK,SAAS,EAAI;;CAalC,iBAAyB,GAAmB,GAAiC,GAAuB;EAClG,IAAM,IAAQ,EAAQ;AACjB,OACL,KAAK,MAAM,OAAO,GAAO,GAAS,GAAS,EAAO;;CAapD,2BACE,GACA,GACA,GACM;EACN,IAAM,IAAW,KAAK,eAAe,IAAI,EAAO;AAChD,EAAI,KACE,OAAO,KAAK,EAAQ,CAAC,SAAS,KAChC,OAAO,OAAO,EAAS,SAAS,EAAQ,EAKtC,MAAW,KAAA,MACb,EAAS,SAAS,MAGpB,KAAK,eAAe,IAAI,GAAQ;GAC9B,SAAS,EAAE,GAAG,GAAS;GACvB;GACA,aAAa,KAAK,OAAO,mBAAmB;GAC7C,CAAC;;CASN,mBAA2B,GAAgB,GAA+C;EACxF,IAAM,IAAW,KAAK,eAAe,IAAI,EAAO;AAChD,MAAI,CAAC,EAAU;EAMf,IAAM,IAAQ,EAAS,QAAQ;AAC/B,MAAI,GAAO;GACT,IAAM,IAAW,KAAK,MAAM,QAAQ,EAAM;AAC1C,GAAI,KACF,EAAS,YAAY,YAAY,GAAO,EAAS,QAAQ;;AAI7D,IAAS,YAAY,eAAe,CAAC,EAAO,CAAC;EAE7C,IAAM,IAAW,EAAS,YAAY;AACtC,MAAI,EAAS,WAAW,EAAG;EAE3B,IAAI;AACJ,MAAI;AACF,OAAU,gBAAgB,EAAS,GAAG,GAAG,CAAC;UACpC;AAMN,OAAU,EAAS,GAAG,GAAG;;AAG3B,MAAI,GAAS;GACX,IAAM,IAAQ,EAAS,QAAQ;AAC/B,GAAI,KACF,KAAK,MAAM,OAAO,GAAO,GAAS,EAAE,GAAG,EAAS,SAAS,EAAE,EAAS,OAAO;;;CASjF,MAAc,eAAe,GAAqC;AAChE,OAAK,QAAQ,MAAM,qCAAqC,EAAE,WAAQ,CAAC;EAEnE,IAAM,IAAkC,EAAE;AAW1C,EAVI,EAAO,SACT,EAAQ,KAAyB,EAAO,SAC/B,EAAO,MAChB,EAAQ,KAAqB,SACpB,EAAO,WAChB,EAAQ,KAA2B,EAAO,WACjC,EAAO,QAChB,EAAQ,KAAqB,SAG/B,MAAM,KAAK,SAAS,QAAQ;GAC1B,MAAM;GACN,QAAQ,EAAE,YAAS;GACpB,CAAC;;CAGJ,0BAAkC,GAA4B;AAK5D,OAAK,IAAM,KAAU,KAAK,oBAAoB,EAAO,CACnD,MAAK,QAAQ,YAAY,EAAO;;CAIpC,oBAA4B,GAAmC;EAC7D,IAAM,oBAAU,IAAI,KAAa,EAC3B,IAAc,KAAK,MAAM,kBAAkB;AAEjD,MAAI,EAAO,IACT,MAAK,IAAM,KAAW,EAAY,QAAQ,CACxC,MAAK,IAAM,KAAU,EAAS,GAAQ,IAAI,EAAO;WAE1C,EAAO,KAAK;GACrB,IAAM,IAAW,EAAY,IAAI,KAAK,aAAa,GAAG;AACtD,OAAI,EACF,MAAK,IAAM,KAAU,EAAU,GAAQ,IAAI,EAAO;aAE3C,EAAO,UAAU;GAC1B,IAAM,IAAc,EAAY,IAAI,EAAO,SAAS;AACpD,OAAI,EACF,MAAK,IAAM,KAAU,EAAa,GAAQ,IAAI,EAAO;aAE9C,EAAO;QAEX,IAAM,KAAW,EAAY,QAAQ,CACxC,KAAI,EAAQ,IAAI,EAAO,OAAO,EAAE;AAC9B,MAAQ,IAAI,EAAO,OAAO;AAC1B;;;AAIN,SAAO;;CAYT,aAAqC;AACnC,MAAI,KAAK,WAAW,EAAqB,OACvC,OAAM,IAAI,EAAK,UAAU,8CAA8C,EAAU,iBAAiB,IAAI;AAExG,OAAK,QAAQ,MAAM,uCAAuC;EAC1D,IAAM,IAAO,EAA6B;GACxC,MAAM,KAAK;GACX,SAAS,KAAK;GACd,OAAO,KAAK;GACZ,cAAc,KAAK,cAAc,KAAK,KAAK;GAC3C,QAAQ,KAAK;GACb,eAAe,KAAK,OAAO,OAAO,EAAK;GACxC,CAAC;AAEF,SADA,KAAK,OAAO,IAAI,EAAK,EACd;;CAIT,MAAc,cACZ,GACA,GACA,GACA,GAC6B;AAQ7B,MAPI,KAAK,WAAW,EAAqB,WAGzC,MAAM,KAAK,gBAIN,KAAK,WAAoC,EAAqB,QACjE,OAAM,IAAI,EAAK,UAAU,uCAAuC,EAAU,iBAAiB,IAAI;EAIjG,IAAM,IAAQ,KAAK,SAAS;AAC5B,MAAI,MAAU,cAAc,MAAU,YACpC,OAAM,IAAI,EAAK,UAAU,8BAA8B,KAAS,EAAU,iBAAiB,IAAI;AAGjG,OAAK,QAAQ,MAAM,mCAAmC;EAEtD,IAAM,IAAO,MAAM,QAAQ,EAAM,GAAG,IAAQ,CAAC,EAAM,EAC7C,IAAS,OAAO,YAAY;AAElC,EADA,KAAK,YAAY,IAAI,EAAO,EAC5B,KAAK,MAAM,UAAU,GAAQ,KAAK,aAAa,GAAG;EAMlD,IAAM,IAAgB,KAAK;AAM3B,EALA,KAAK,sBAAsB,EAAE,EAKzB,KAAc,EAAW,SAAS,KACpC,KAAK,mBAAmB,EAAW;EAGrC,IAAM,IAAsC,CAAC,GAAG,GAAe,GAAI,KAAc,EAAE,CAAE,EAE/E,oBAAS,IAAI,KAAa,EAC1B,IAAwC,EAAE,EAI1C,IAAmB,GAIrB;AACJ,MAAI,GAAa,WAAW,KAAA,KAAa,CAAC,GAAa,QAAQ;GAC7D,IAAM,IAAW,EAAiB,GAAG,GAAG;AACxC,GAAI,MACF,IAAa,EAAS;;EAK1B,IAAM,IAAa,GAAa,WAAW,KAAA,IAAY,IAAa,EAAY;AAEhF,OAAK,IAAM,KAAW,GAAM;GAC1B,IAAM,IAAQ,OAAO,YAAY;AAEjC,GADA,KAAK,WAAW,IAAI,EAAM,EAC1B,EAAO,IAAI,EAAM;GAEjB,IAAM,IAAiB,GAAa,WAAW,KAAA,IAAY,IAAa,EAAY,QAE9E,IAAoB,EAAsB;IAC9C,MAAM;IACN;IACA;IACA,cAAc,KAAK;IACnB,QAAQ;IACR,QAAQ,GAAa;IACtB,CAAC;AAmBF,GAhBA,KAAK,iBAAiB,GAAS,EAAkB,EAGjD,EAAa,KAAK;IAChB,MAAM;IACN;IACA;IACA,UAAU;IACV,QAAQ,GAAa;IACrB,SAAS;IACT,QAAQ,KAAA;IACT,CAAC,EAKE,GAAa,WAAW,KAAA,KAAa,CAAC,GAAa,WACrD,IAAa;;AAIjB,OAAK,YAAY,IAAI,GAAQ,EAAO;EAGpC,IAAM,IAAS,KAAK,QAAQ,aAAa,EAAO,EAG1C,IAAkB,KAAK,cAAc,IAAI,EAAE,EAG3C,IAAoC;GACxC,GAHmB,KAAK,WAAW,IAAI,EAAE;GAIzC,SAAS;GACT,GAAG,GAAa;GAChB;GACA,UAAU,KAAK;GACf,UAAU;GACV,GAAI,GAAa,WAAW,KAAA,KAAa,EAAE,QAAQ,EAAY,QAAQ;GACvE,GAAI,MAAe,KAAA,KAAa,EAAE,QAAQ,GAAY;GACtD,GAAI,EAAc,SAAS,KAAK,EAAE,QAAQ,GAAe;GAC1D,EAEK,IAAsC;GAC1C,GAAG;GACH,GAAG,GAAa;GACjB;AAqCD,SAhCA,KAAK,SAAS,KAAK,MAAM;GACvB,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,GAAG;IACJ;GACD,MAAM,KAAK,UAAU,EAAS;GAC9B,GAAI,KAAK,eAAe,EAAE,aAAa,KAAK,cAAc,GAAG,EAAE;GAChE,CAAC,CACC,MAAM,MAAa;AAClB,OAAI,CAAC,EAAS,IAAI;IAChB,IAAM,IAAM,IAAI,EAAK,UACnB,gCAAgC,KAAK,KAAK,YAAY,OAAO,EAAS,OAAO,CAAC,GAAG,EAAS,cAC1F,EAAU,qBACV,EAAS,OACV;AAED,IADA,KAAK,SAAS,KAAK,SAAS,EAAI,EAChC,KAAK,QAAQ,YAAY,GAAQ,EAAI;;IAEvC,CACD,OAAO,MAAmB;GACzB,IAAM,IAAQ,aAAiB,EAAK,YAAY,IAAQ,KAAA,GAClD,IAAM,IAAI,EAAK,UACnB,gCAAgC,KAAK,KAAK,WAAW,aAAiB,QAAQ,EAAM,UAAU,OAAO,EAAM,IAC3G,EAAU,qBACV,KACA,EACD;AAED,GADA,KAAK,SAAS,KAAK,SAAS,EAAI,EAChC,KAAK,QAAQ,YAAY,GAAQ,EAAI;IACrC,EAEG;GACL;GACA;GACA,QAAQ,YAAY,KAAK,OAAO,EAAE,WAAQ,CAAC;GAC3C,kBAAkB,CAAC,GAAG,EAAO;GAC9B;;CAIH,MAAM,OAAO,GAAsC;AACjD,MAAI,KAAK,WAAW,EAAqB,OAAQ;EACjD,IAAM,IAAW,KAAU,EAAE,KAAK,IAAM;AAGxC,EAFA,KAAK,QAAQ,MAAM,6BAA6B,EAAE,QAAQ,GAAU,CAAC,EACrE,MAAM,KAAK,eAAe,EAAS,EACnC,KAAK,0BAA0B,EAAS;;CAG1C,YAAY,GAAe,GAAwB;AAEjD,MADA,KAAK,QAAQ,MAAM,kCAAkC;GAAE;GAAO,YAAY,EAAO;GAAQ,CAAC,EACtF,KAAK,WAAW,EAAqB,QAAQ;AAC/C,QAAK,QAAQ,KAAK,sDAAsD,EAAE,UAAO,CAAC;AAClF;;AAEF,MAAI,CAAC,KAAK,MAAM,QAAQ,EAAM,EAAE;AAC9B,QAAK,QAAQ,KAAK,0DAA0D,EAAE,UAAO,CAAC;AACtF;;AAEF,MAAI,EAAO,WAAW,EAAG;EACzB,IAAM,IAA2B;GAAE,MAAM;GAAS;GAAO;GAAQ;AAKjE,EADA,KAAK,mBAAmB,CAAC,EAAK,CAAC,EAC/B,KAAK,oBAAoB,KAAK,EAAK;;CAGrC,aAAa,GAAe,GAAyB;AAEnD,MADA,KAAK,QAAQ,MAAM,mCAAmC,EAAE,UAAO,CAAC,EAC5D,KAAK,WAAW,EAAqB,QAAQ;AAC/C,QAAK,QAAQ,KAAK,uDAAuD,EAAE,UAAO,CAAC;AACnF;;EAEF,IAAM,IAAW,KAAK,MAAM,QAAQ,EAAM;AAC1C,MAAI,CAAC,GAAU;AACb,QAAK,QAAQ,KAAK,2DAA2D,EAAE,UAAO,CAAC;AACvF;;AAGF,OAAK,MAAM,OAAO,GAAO,GAAS,EAAS,SAAS,EAAS,OAAO;;CAMtE,mBAA2B,GAAwC;AACjE,OAAK,IAAM,KAAQ,GAAY;GAC7B,IAAM,IAAe,KAAK,MAAM,QAAQ,EAAK,MAAM;AACnD,OAAI,CAAC,EAAc;GACnB,IAAM,IAAU,EAAK,OAAO,KAAK,OAAW;IAC1C,MAAM;IACN;IACA,WAAW,EAAK;IACjB,EAAE,EACG,IAAc,KAAK,OAAO,mBAAmB;AAEnD,GADA,EAAY,YAAY,EAAK,OAAO,EAAa,QAAQ,EACzD,EAAY,eAAe,EAAQ;GACnC,IAAM,IAAa,EAAY,SAAS,GAAG,GAAG;AAC9C,GAAI,KACF,KAAK,MAAM,OAAO,EAAK,OAAO,GAAY,EAAa,SAAS,EAAa,OAAO;;;CAM1F,MAAM,YAAY,GAAsC;AACtD,MAAI,KAAK,WAAW,EAAqB,OAAQ;EACjD,IAAM,IAAW,KAAU,EAAE,KAAK,IAAM,EAClC,IAAY,KAAK,oBAAoB,EAAS;AAChD,QAAU,SAAS,EAIvB,QAFA,KAAK,QAAQ,MAAM,kCAAkC,EAAE,SAAS,CAAC,GAAG,EAAU,EAAE,CAAC,EAE1E,IAAI,SAAe,MAAY;GACpC,IAAI,IAAW,IACT,UAAmB;AACvB,QAAI,EAAU;AAEd,IADA,IAAW,IACX,GAAO;IACP,IAAM,IAAM,KAAK,gBAAgB,QAAQ,EAAK;AAE9C,IADI,MAAQ,MAAI,KAAK,gBAAgB,OAAO,GAAK,EAAE,EACnD,GAAS;MAGL,IAAQ,KAAK,MAAM,GAAG,SAAS,MAA8B;AAC7D,MAAM,SAAA,sBACV,EAAU,OAAO,EAAM,OAAO,EAC1B,EAAU,SAAS,KAAG,GAAM;KAChC;AAGF,QAAK,gBAAgB,KAAK,EAAK;IAC/B;;CAIJ,GAAG,GAAgB,GAAsD;AACvE,MAAI,KAAK,WAAW,EAAqB,OAAQ,QAAO;EAExD,IAAM,IAAK;AAEX,SADA,KAAK,SAAS,GAAG,GAAO,EAAG,QACd;AACX,QAAK,SAAS,IAAI,GAAO,EAAG;;;CAKhC,MAAM,MAAM,GAAuC;AAC7C,WAAK,WAAW,EAAqB,QAKzC;OAJA,KAAK,SAAS,EAAqB,QACnC,KAAK,QAAQ,KAAK,2BAA2B,EAGzC,GAAS,QAAQ;AACnB,QAAI;AACF,WAAM,KAAK,eAAe,EAAQ,OAAO;YACnC;AAGR,SAAK,0BAA0B,EAAQ,OAAO;;AAIhD,GADA,KAAK,SAAS,YAAY,KAAK,WAAW,EAC1C,KAAK,SAAS,IAAI,KAAK,sBAAsB;AAG7C,QAAK,IAAM,KAAU,KAAK,YACxB,MAAK,QAAQ,YAAY,EAAO;AAIlC,GADA,KAAK,eAAe,OAAO,EAC3B,KAAK,SAAS,KAAK;AACnB,QAAK,IAAM,KAAK,KAAK,OAAQ,GAAE,OAAO;AACtC,QAAK,OAAO,OAAO;AACnB,QAAK,IAAM,KAAW,KAAK,gBAAiB,IAAS;AAIrD,GAHA,KAAK,gBAAgB,SAAS,GAC9B,KAAK,YAAY,OAAO,EACxB,KAAK,WAAW,OAAO,EACvB,KAAK,YAAY,OAAO;;;GAgBf,MACX,MACsC,IAAI,GAAuB,EAAQ,ECl6BrE,MAA4C,EAChD,gBACA,aACA,GAAG,QAC2C;CAC9C,IAAM,EAAE,eAAY,EAAW,EAAE,gBAAa,CAAC,EACzC,IAAe,EAAsD,KAAA,EAAU,EAC/E,IAAsB,EAAe,EAAY,EACjD,IAAyB,EAA4C,EAAE,CAAC,EACxE,IAAkB,EAAO,GAAM,EAC/B,IAAuB,EAAmC,KAAA,EAAU;AAI1E,KAAI,EAF6B,EAAa,WAAa,EAAqB,YAEjD,EAAoB,YAAY,GAAa;AAE1E,EADA,EAAoB,UAAU,GAC1B,EAAa,WAAS,EAAuB,QAAQ,KAAK,EAAa,QAAQ;AACnF,MAAI;AAEF,GADA,EAAa,UAAU,GAAsB;IAAE,GAAG;IAAkB;IAAS,CAAC,EAC9E,EAAqB,UAAU,KAAA;WACxB,GAAO;AAEd,GADA,EAAa,UAAU,KAAA,GACvB,EAAqB,UACnB,aAAiB,EAAK,YAClB,IACA,IAAI,EAAK,UAAU,0CAA0C,EAAU,YAAY,IAAI;;;CAIjG,IAAM,IAAY,EAAW,EAAiB,EAKxC,IAAmB,EAAa,SAChC,IAAe,EAAqB,SAEpC,IAAO,SACJ;EAAE,WAAW;EAAkB,OAAO;EAAc,GAC3D,CAAC,GAAkB,EAAa,CACjC,EAEK,IAAe,SAAe;EAAE,GAAG;GAAY,IAAc;EAAM,GAAG;EAAC;EAAa;EAAW;EAAK,CAAC;AA2B3G,QAzBA,cACc;AACV,OAAK,IAAM,KAAa,EAAuB,QAAc,GAAU,OAAO;IAEhF,CAAC,EAAY,CACd,EAQD,SACE,EAAgB,UAAU,UACb;AAEN,EADL,EAAgB,UAAU,IACrB,QAAQ,SAAS,CAAC,WAAW;AAChC,GAAI,EAAgB,WACb,EAAa,SAAS,OAAO;IAEpC;KAEH,EAAE,CAAC,EAGJ,kBAAC,EAAiB,UAAlB;EAA2B,OAAO;YAChC,kBAAC,EAAwB,UAAzB;GAAkC,OAAO;GAAO;GAA4C,CAAA;EAClE,CAAA;GAyCnB,KAAuC,MAClD,kBAAC,GAAD;CAAiB,aAAa,EAAM;WAClC,kBAAC,IAAD,EAAwB,GAAI,GAAS,CAAA;CACrB,CAAA,ECzIP,KAAqC,EAChD,cACA,YACqE,EAAE,KAA4B;CACnG,IAAM,IAAc,EAAW,EAAwB,EAEjD,IAAW,IACb,KAAA,IACE,KAAa,GAAa,WAE1B,CAAC,GAAU,KAAe,EAAgC,EAAE,CAAC,EAC7D,IAAc,EAA8B,EAAE,CAAC;AAiBrD,QAfA,QAAgB;AAEd,QAAY,UAAU,EAAE,EACxB,EAAY,EAAE,CAAC,EAEV,EAOL,QALc,EAAS,KAAK,GAAG,iBAAiB,MAA6B;GAC3E,IAAM,IAAO,CAAC,GAAG,EAAY,SAAS,EAAI;AAE1C,GADA,EAAY,UAAU,GACtB,EAAY,EAAK;IACjB;IAED,CAAC,EAAS,CAAC,EAEP;GC1BI,KAAoC,EAC/C,iBAC4D,EAAE,KAA+B;CAC7F,IAAM,IAAc,EAAW,EAAwB,EAEjD,IAAY,KAAa,GAAa,WAEtC,CAAC,GAAO,KAAY,wBAAyC,IAAI,KAAK,CAAC;AAoC7E,QAlCA,QAAgB;AACT,QA8BL,QA3BA,EAAS,EAAS,KAAK,kBAAkB,CAAC,EAEtB,EAAS,KAAK,GAAG,SAAS,MAA8B;AAC1E,MAAU,MAAS;IACjB,IAAM,IAAO,IAAI,IAAI,EAAK;AAE1B,QAAI,EAAM,SAAA,qBAA2B;KACnC,IAAM,IAAM,IAAI,IAAI,EAAK,IAAI,EAAM,SAAS,CAAC;AAE7C,KADA,EAAI,IAAI,EAAM,OAAO,EACrB,EAAK,IAAI,EAAM,UAAU,EAAI;WACxB;KACL,IAAM,IAAW,EAAK,IAAI,EAAM,SAAS;AACzC,SAAI,GAAU;MACZ,IAAM,IAAU,IAAI,IAAI,EAAS;AAEjC,MADA,EAAQ,OAAO,EAAM,OAAO,EACxB,EAAQ,SAAS,IACnB,EAAK,OAAO,EAAM,SAAS,GAE3B,EAAK,IAAI,EAAM,UAAU,EAAQ;;;AAKvC,WAAO;KACP;IACF;IAGD,CAAC,EAAS,CAAC,EAEP;GC1CH,IAAuD;CAC3D,IAAI,OAAsB;AACxB,QAAM,IAAI,EAAK,UAAU,0CAA0C,EAAU,iBAAiB,IAAI;;CAEpG,IAAI,OAA+B;AACjC,QAAM,IAAI,EAAK,UAAU,0CAA0C,EAAU,iBAAiB,IAAI;;CAEpG,kBAA0C;AACxC,QAAM,IAAI,EAAK,UAAU,0CAA0C,EAAU,iBAAiB,IAAI;;CAEpG,cAAc;AACZ,QAAM,IAAI,EAAK,UAAU,qCAAqC,EAAU,iBAAiB,IAAI;;CAE/F,mBAAmB;AACjB,QAAM,IAAI,EAAK,UAAU,2CAA2C,EAAU,iBAAiB,IAAI;;CAErG,oBAAoB;AAClB,QAAM,IAAI,EAAK,UAAU,4CAA4C,EAAU,iBAAiB,IAAI;;CAEtG,mBAAmB;AACjB,QAAM,IAAI,EAAK,UAAU,4CAA4C,EAAU,iBAAiB,IAAI;;CAEtG,UAAU;AACR,QAAM,IAAI,EAAK,UAAU,wCAAwC,EAAU,iBAAiB,IAAI;;CAElG,aAAa;AACX,QAAM,IAAI,EAAK,UAAU,oCAAoC,EAAU,iBAAiB,IAAI;;CAE/F,EA2CY,KAAwC,EACnD,gBACA,SACA,eAkBE,EAAE,KAA8C;CAClD,IAAM,IAAW,EAAW,EAAiB,EACvC,IAAc,EAAW,EAAwB,EACjD,IAAmB,EAAO,EAAQ;AACxC,GAAiB,UAAU;CAK3B,IAAM,IAAmE,IACrE,KAAA,IACA,MAAgB,KAAA,IACd,GAAa,YACb,EAAS,IAAc;AAS7B,KAPA,QAAgB;AACT,QACL,QAAO,EAAkB,GAAG,UAAU,MAAc;AAClD,KAAiB,UAAU,EAAU;IACrC;IACD,CAAC,EAAkB,CAAC,EAEnB,EACF,QAAO,EACL,WAAW,GACZ;AAGH,KAAI,MAAgB,KAAA,GAAW;EAC7B,IAAM,IAAO,EAAS;AAetB,SAdI,IACE,EAAK,YAGA,EACL,WAAW,EAAK,WACjB,GAGI;GACL,WAAW;GACX,gBAAgB,EAAK;GACtB,GAEI;GACL,WAAW;GACX,gBAAgB,IAAI,EAAK,UACvB,wEAAwE,EAAY,IACpF,EAAU,YACV,IACD;GACF;;AAiBH,QAdI,IACE,EAAY,YAEP,EACL,WAAW,EAAY,WACxB,GAGI;EACL,WAAW;EACX,gBAAgB,EAAY;EAC7B,GAGI;EACL,WAAW;EACX,gBAAgB,IAAI,EAAK,UACvB,mEACA,EAAU,YACV,IACD;EACF;GCtHU,KAA6B,EACxC,cACA,SACA,UACA,YAUE,EAAE,KAAmC;CACvC,IAAM,IAAc,EAAW,EAAwB,EAEjD,IAAoB,IACtB,KAAA,IACC,KAAc,GAAa,WAC1B,IAAe,IAAO,KAAA,IAAa,KAAQ,GAAmB,MAE9D,CAAC,GAAO,KAAY,QAAwC,GAAc,cAAc,IAAI,EAAE,CAAC,EAC/F,CAAC,GAAU,KAAe,QAAe,GAAc,UAAU,IAAI,GAAM,EAC3E,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAW,KAAgB,GAAsC,EAClE,IAAa,EAAO,GAAM,EAK1B,IAAW,MAAU,KAAA,GACrB,IAAgB,EAAO,GAAM;AAGnC,SAAgB;AACd,MAAI,CAAC,GAAc;AAGjB,GAFA,EAAS,EAAE,CAAC,EACZ,EAAY,GAAM,EAClB,EAAa,KAAA,EAAU;AACvB;;AAeF,SAXA,EAAc,UAAU,IAGxB,EAAS,EAAa,cAAc,CAAC,EACrC,EAAY,EAAa,UAAU,CAAC,EACpC,EAAa,KAAA,EAAU,EAET,EAAa,GAAG,gBAAgB;AAE5C,GADA,EAAS,EAAa,cAAc,CAAC,EACrC,EAAY,EAAa,UAAU,CAAC;IACpC;IAED,CAAC,EAAa,CAAC;CAElB,IAAM,IAAY,EAAY,YAAY;AACpC,SAAC,KAAgB,EAAW,UAEhC;GADA,EAAW,UAAU,IACrB,EAAW,GAAK;AAChB,OAAI;AAEF,IADA,MAAM,EAAa,UAAU,EAAM,EACnC,EAAa,KAAA,EAAU;YAChB,GAAO;AACd,IAAI,aAAiB,EAAK,YACxB,EAAa,EAAM,GAEnB,EAAa,IAAI,EAAK,UAAU,wCAAwC,EAAU,YAAY,IAAI,CAAC;aAE7F;AAER,IADA,EAAW,UAAU,IACrB,EAAW,GAAM;;;IAElB,CAAC,GAAc,EAAM,CAAC;AA+DzB,QA7DA,QAAgB;AACV,GAAC,KAAY,EAAc,WAAW,CAAC,MAC3C,EAAc,UAAU,IACnB,GAAW;IACf;EAAC;EAAU;EAAc;EAAU,CAAC,EAyDhC;EACL,UAxDe,QAAc,EAAM,KAAK,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAM,CAAC;EAyDlE;EACA;EACA;EACA;EACA;EACA,QA3Da,GACZ,GAAe,MAAkB;AAChC,MAAc,OAAO,GAAO,EAAM;KAEpC,CAAC,EAAa,CACf;EAuDC,kBArDuB,GAAa,MAAkB,GAAc,iBAAiB,EAAM,IAAI,GAAG,CAAC,EAAa,CAAC;EAsDjH,aApDkB,GAAa,MAAkB,GAAc,YAAY,EAAM,IAAI,EAAE,EAAE,CAAC,EAAa,CAAC;EAqDxG,aAnDkB,GAAa,MAAkB,GAAc,YAAY,EAAM,IAAI,IAAO,CAAC,EAAa,CAAC;EAoD3G,SAlDc,GAAa,MAAkB,GAAc,QAAQ,EAAM,EAAE,CAAC,EAAa,CAAC;EAmD1F,MAhDW,EACX,OAAO,GAA6B,MAAuB;AACzD,OAAI,CAAC,EACH,OAAM,IAAI,EAAK,UAAU,yCAAyC,EAAU,iBAAiB,IAAI;AACnG,UAAO,EAAa,KAAK,GAAM,EAAK;KAEtC,CAAC,EAAa,CACf;EA0CC,YAxCiB,EACjB,OAAO,GAAmB,MAAuB;AAC/C,OAAI,CAAC,EACH,OAAM,IAAI,EAAK,UAAU,+CAA+C,EAAU,iBAAiB,IAAI;AACzG,UAAO,EAAa,WAAW,GAAW,EAAK;KAEjD,CAAC,EAAa,CACf;EAkCC,MAhCW,EACX,OAAO,GAAmB,GAAoC,MAAuB;AACnF,OAAI,CAAC,EACH,OAAM,IAAI,EAAK,UAAU,yCAAyC,EAAU,iBAAiB,IAAI;AACnG,UAAO,EAAa,KAAK,GAAW,GAAa,EAAK;KAExD,CAAC,EAAa,CACf;EA0BC,QAxBa,EACb,OAAO,GAAe,GAAkB,MAAuB;AAC7D,OAAI,CAAC,EACH,OAAM,IAAI,EAAK,UAAU,2CAA2C,EAAU,iBAAiB,IAAI;AACrG,UAAO,EAAa,OAAO,GAAO,GAAQ,EAAK;KAEjD,CAAC,EAAa,CACf;EAkBA;GCrMU,KAAmC,EAC9C,cACA,UACA,YAQE,EAAE,KAAmC;CACvC,IAAM,IAAc,EAAW,EAAwB,EAEjD,IAAW,IACb,KAAA,IACE,KAAa,GAAa,WAE1B,CAAC,GAAM,KAAW,GAA8C;AActE,QAZA,QAAgB;AACd,MAAI,CAAC,GAAU;AACb,KAAQ,KAAA,EAAU;AAClB;;EAEF,IAAM,IAAI,EAAS,YAAY;AAE/B,SADA,EAAQ,EAAE,QACG;AACX,KAAE,OAAO;;IAEV,CAAC,EAAS,CAAC,EAEP,EAAQ;EAAE;EAAM;EAAO;EAAM,CAAC;GChC1B,KAA6B,EACxC,iBACqD,EAAE,KAA2B;CAClF,IAAM,IAAc,EAAW,EAAwB,EAEjD,IAAY,KAAa,GAAa;AAQ5C,QAAO;EACL,aAPkB,GAAa,MAAkB,GAAU,KAAK,YAAY,EAAM,IAAI,EAAE,EAAE,CAAC,EAAS,CAAC;EAQrG,aANkB,GAAa,MAAkB,GAAU,KAAK,YAAY,EAAM,IAAI,IAAO,CAAC,EAAS,CAAC;EAOxG,SALc,GAAa,MAAkB,GAAU,KAAK,QAAQ,EAAM,EAAE,CAAC,EAAS,CAAC;EAMxF;GCmFU,YAAkF;CAE1E;CACnB,qBAAqB,MAAU,EAAsC,KAAS,EAAE,CAAC;CACjF,UAAU,MAAU,EAA2B,KAAS,EAAE,CAAC;CAC3D,iBAAiB,MAAU,EAAkC,KAAS,EAAE,CAAC;CACzE,UAAU,MAAU,EAA2B,KAAS,EAAE,CAAC;CAC3D,kBAAkB,MAAU,EAAmC,KAAS,EAAE,CAAC;CAC3E,gBAAgB,MAAU,EAAiC,KAAS,EAAE,CAAC;CACxE"}