Plugins
@aparte/core is presentational and carries no third-party dependency: it renders raw text, exposes seams, and asks
nothing of you. Plugins fill those seams — each is an opt-in @aparte/plugin-* package that you install
and wire in one call, so core stays small and you pay only for what you use.
| You want… | Package | Seam it fills |
|---|---|---|
| Render finished Markdown messages | marked | setMarkdownProvider |
| Stream Markdown token-by-token | streaming-markdown | setStreamingMarkdownProvider |
| Highlight code blocks | shiki | setHighlightProvider |
| Let the user pick a provider + model | model-selector | <aparte-model-selector> element |
| Let the AI ask the user a question | ask-user | registerTool + elicitation |
| Switch approval modes — plan, ask, auto-edit, auto | approval | setApprovalPolicy + <aparte-approval-mode> |
| Let the AI produce a document — a page, a component, a spreadsheet — shown as a Code/Preview card | artifacts | registerTool + registerToolRenderer + registerStreamBlock + registerSegmentRenderer |
| Summarise a long conversation so it fits the model’s window | compaction | the aparte-compact command + the compaction: true notice |
| Title each conversation from its first message, with a 40–133 KB model in the browser | titler | the conversation manager’s setTitleProvider |
Every plugin lists @aparte/core as a peer dependency and, where it wraps a third-party library
(marked, streaming-markdown, shiki), that library too — so you control its version and it is never
bundled into core.
The shape of a plugin
Section titled “The shape of a plugin”Most plugins register something on the config once, at startup:
import { setupMarkedProvider } from '@aparte/plugin-marked';import { setupShikiProvider } from '@aparte/plugin-shiki';
setupMarkedProvider(); // finished messages → HTMLawait setupShikiProvider(); // code blocks → highlighted HTMLThe two Markdown plugins are complementary: streaming-markdown renders each token as it arrives, and
marked re-renders the finished message. Register both for the best of both.
Scoping a plugin to one chat
Section titled “Scoping a plugin to one chat”Every setup* takes the config instance as its last argument, defaulting to the global
aparteGlobalConfig. Pass your own instance and the plugin registers there instead — which is what makes
two independently configured chats on one page actually work:
import { AparteConfig } from '@aparte/core';import { setupMarkedProvider } from '@aparte/plugin-marked';
// Markdown for the support chat only; the other chat on the page keeps plain text.const supportConfig = new AparteConfig();setupMarkedProvider(undefined, supportConfig);Then hand that same instance to the chat — the config prop on the React/Vue/Svelte/Angular
wrappers, or new AparteClient({ config: supportConfig }) in vanilla.
Localization
Section titled “Localization”Translating the built-in UI strings is its own seam — see the
Localization guide and the @aparte/locale-fr package.