Skip to content

Messages and segments — core API

Generated reference for @aparte/core. A message and the segments it is made of, the parser that builds them from a stream, the renderers that draw them, and the errors.

On this page

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:93

ts
new AparteStreamParser(options?): AparteStreamParser;

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:109

ParameterType
optionsAparteStreamParserOptions

AparteStreamParser

ts
parse(chunk): AparteParserResult;

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:131

Parse a chunk of streamed content Call this for each chunk received from the LLM

ParameterType
chunkstring

AparteParserResult

ts
finalize(): AparteSegment[];

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:164

Finalize parsing - flush any remaining content as text

AparteSegment[]

ts
reset(): void;

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:210

Reset parser state

void

ts
getState(): Readonly<AparteParserState>;

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:218

Get current parser state (for debugging/inspection)

Readonly<AparteParserState>


Defined in: packages/core/src/types/errors.ts:28

  • Error
ts
new AparteError(
   message, 
   code, 
   data?, 
   originalError?, 
   httpStatus?): AparteError;

Defined in: packages/core/src/types/errors.ts:29

ParameterType
messagestring
codeAparteErrorCode
data?Record<string, unknown>
originalError?unknown
httpStatus?number

AparteError

ts
Error.constructor
ts
message: string;

Defined in: packages/core/src/types/errors.ts:30

ts
Error.message
ts
code: AparteErrorCode;

Defined in: packages/core/src/types/errors.ts:31

ts
optional data?: Record<string, unknown>;

Defined in: packages/core/src/types/errors.ts:32

ts
optional originalError?: unknown;

Defined in: packages/core/src/types/errors.ts:33

ts
optional httpStatus?: number;

Defined in: packages/core/src/types/errors.ts:34

ts
static codeForStatus(status): AparteErrorCode | undefined;

Defined in: packages/core/src/types/errors.ts:45

The code an HTTP status stands for — the table the enum’s own comments promise. undefined for a status the enum does not name (a 404 can be a wrong model or a wrong URL; the message says which, a code would guess).

ParameterType
statusnumber | undefined

AparteErrorCode | undefined

ts
static from(
   error, 
   defaultCode?, 
   defaultStatus?): AparteError;

Defined in: packages/core/src/types/errors.ts:57

ParameterTypeDefault value
errorunknownundefined
defaultCodeAparteErrorCodeAparteErrorCode.UNKNOWN_ERROR
defaultStatus?numberundefined

AparteError

ts
function parseAparteEventStream(stream): ReadableStream<AparteStreamEvent>;

Defined in: packages/core/src/parsers/aparte-event-stream.ts:15

ParameterType
streamReadableStream<Uint8Array<ArrayBufferLike>>

ReadableStream<AparteStreamEvent>


ts
function parseMarkdownToSegments(content, options?): AparteSegment[];

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:713

Parse a complete markdown string into segments Use this for non-streaming scenarios

ParameterType
contentstring
options?AparteStreamParserOptions

AparteSegment[]


ts
function registerSegmentRenderer<T>(renderer, config?): void;

Defined in: packages/core/src/renderers/segment-renderers.ts:114

Register a segment renderer.

config defaults to the ambient render config, which at app startup — no element, no active render — is the global singleton. That is the right default: a chat with its own config inherits global registrations. Pass a config explicitly only to give ONE chat a renderer the others must not have.

Type Parameter
T extends AparteSegmentBase
ParameterType
rendererAparteSegmentRenderer<T>
configAparteConfig

void


ts
function declineDefaultRenderers(config?): void;

Defined in: packages/core/src/renderers/segment-renderers.ts:129

Remember that the app declined the built-in renderers, so installDefaultRenderersOnce stays out of the way for good.

AparteClient({ autoRegister: false }) is the one caller. Without this latch the lazy install below would quietly turn that option into a no-op.

ParameterType
configAparteConfig

void


ts
function installDefaultRenderersOnce(config?): void;

Defined in: packages/core/src/renderers/segment-renderers.ts:147

Fill in the built-in renderers for the types nobody has claimed — called by the bubble the first time a segment has no renderer.

Why lazily and not at import time: registerDefaultRenderers() used to have exactly one caller, new AparteClient(). An app on the bring-your-own-loop path — the one the guide tells you not to construct a client on — rendered [Unknown segment type: text] for every reply. Bubbles, streaming and scrolling all worked; only the content was missing, which reads as a bug in the consumer’s own loop, not as a missing call.

Strictly additive: a type someone registered themselves is never replaced, so a custom text renderer survives the sweep triggered by a code segment.

ParameterType
configAparteConfig

void


ts
function unregisterSegmentRenderer(type, config?): void;

Defined in: packages/core/src/renderers/segment-renderers.ts:160

Unregister a segment renderer

ParameterType
typestring
configAparteConfig

void


ts
function getSegmentRenderer(type, config?): 
  | AparteSegmentRenderer<AparteSegmentBase>
  | undefined;

Defined in: packages/core/src/renderers/segment-renderers.ts:167

Get renderer for a segment type

ParameterType
typestring
configAparteConfig

| AparteSegmentRenderer<AparteSegmentBase> | undefined


ts
function getAllRenderers(config?): readonly AparteSegmentRenderer<AparteSegmentBase>[];

Defined in: packages/core/src/renderers/segment-renderers.ts:177

Get all registered renderers

ParameterType
configAparteConfig

readonly AparteSegmentRenderer<AparteSegmentBase>[]


ts
function collectRendererStyles(config?): string;

Defined in: packages/core/src/renderers/segment-renderers.ts:187

Collect all renderer styles for one config.

Its OWN renderers, not the inherited view: a config that inherits the global’s renderers also inherits their stylesheet, which the global already injected.

ParameterType
configAparteConfig

string


ts
function registerDefaultRenderers(config?): void;

Defined in: packages/core/src/renderers/segment-renderers.ts:266

ParameterType
configAparteConfig

void


ts
function contentToText(content): string;

Defined in: packages/core/src/types/chat.ts:44

Extract plain text from a string | AparteContentPart[] content value. Use this in providers and app-layer code whenever you need the text-only representation.

ParameterType
contentstring | AparteContentPart[]

string


Defined in: packages/core/src/parsers/aparte-stream-parser.ts:27

One inline thinking-block delimiter pair (open/close tag).

ts
start: string;

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:28

ts
end: string;

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:29


Defined in: packages/core/src/parsers/aparte-stream-parser.ts:32

ts
optional thinkingDelimiters?: 
  | AparteThinkingDelimiterPair
  | AparteThinkingDelimiterPair[];

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:41

Inline thinking-block delimiters — for a model that streams its reasoning inline in the content (rather than on a separate reasoning channel). Accepts one pair or several; the parser opens a thinking segment at whichever pair appears first. Default recognizes BOTH common conventions: <think>…</think> (DeepSeek-R1, QwQ, most local GGUF reasoning models) and <thinking>…</thinking> (Claude-style). Pass your own to override.

ts
optional blocks?: AparteStreamBlock[];

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:48

Tagged blocks to recognise in the prose — <tag attr="…">…</tag> — each turned into the segment its grammar builds. The stream adapter passes the blocks registered on the config (registerStreamBlock); pass your own when you drive the parser yourself. See AparteStreamBlock.

ts
optional autoGenerateIds?: boolean;

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:50

Auto-generate segment IDs (default: true)

ts
optional idPrefix?: string;

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:52

ID prefix for generated segments


Defined in: packages/core/src/parsers/aparte-stream-parser.ts:61

ts
mode: "text" | "thinking" | "code" | "block";

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:63

Current parser mode

ts
buffer: string;

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:65

Buffer for incomplete patterns

ts
optional codeLanguage?: string;

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:67

Current code block language

ts
optional thinkingEnd?: string;

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:69

Close delimiter of the thinking pair currently open (multi-delimiter support)

ts
optional blockEnd?: string;

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:71

Closing tag of the registered block currently open (</tag>).

ts
segments: AparteSegment[];

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:73

Accumulated segments

ts
activeSegment: AparteSegment | null;

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:75

Current active segment being built

ts
segmentCounter: number;

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:77

Counter for generating IDs


Defined in: packages/core/src/parsers/aparte-stream-parser.ts:80

ts
segments: AparteSegment[];

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:82

Parsed segments

ts
remaining: string;

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:84

Remaining buffer (incomplete patterns)

ts
state: AparteParserState;

Defined in: packages/core/src/parsers/aparte-stream-parser.ts:86

Current parser state


Defined in: packages/core/src/types/chat.ts:13

Plain text content part

ts
type: "text";

Defined in: packages/core/src/types/chat.ts:14

ts
text: string;

Defined in: packages/core/src/types/chat.ts:15


Defined in: packages/core/src/types/chat.ts:23

Image content part. image must be a base64 data URL: data:image/png;base64,... The data URL encodes the MIME type, making it consumable by all providers.

ts
type: "image";

Defined in: packages/core/src/types/chat.ts:24

ts
image: string;

Defined in: packages/core/src/types/chat.ts:25

ts
optional mimeType?: string;

Defined in: packages/core/src/types/chat.ts:26


Defined in: packages/core/src/types/chat.ts:52

ts
role: "user" | "assistant" | "system" | "tool";

Defined in: packages/core/src/types/chat.ts:53

ts
content: string | AparteContentPart[];

Defined in: packages/core/src/types/chat.ts:61

Message content — either a plain string (backward compatible) or an array of typed content parts: text and images. A binary the model cannot read is not a content part; see the note on AparteContentPart.

Use contentToText(content) to extract the text-only representation.

ts
optional toolCalls?: AparteToolCall[];

Defined in: packages/core/src/types/chat.ts:66

On an assistant message: the tool calls the model made this turn, grouped. content is what it said before them — '' when it said nothing.

ts
optional toolCallId?: string;

Defined in: packages/core/src/types/chat.ts:68

On a tool message: the id of the call, in the preceding assistant message, that it answers.

ts
optional toolName?: string;

Defined in: packages/core/src/types/chat.ts:76

On a tool message: the name of the tool that ran.

Optional, and worth setting: a wire format that names the tool on the result (the AI SDK does) otherwise has to scan back for the call that declared the id, which a hand-built history need not contain.


Defined in: packages/core/src/types/chat.ts:88

The request one turn sends to a provider.

prefill, systemOverride and fastStream used to sit here, each documented “providers MAY ignore it” and read by none — one model family’s raw-completion vocabulary flattened onto every provider’s request. A provider reads its option off _meta, the namespaced bag, which reaches it untouched; the trigger to reconsider is a raw-completion provider in this repo (provider-llamacpp) that would actually read one.

ts
messages: AparteChatMessage[];

Defined in: packages/core/src/types/chat.ts:89

ts
modelId: string;

Defined in: packages/core/src/types/chat.ts:90

ts
optional temperature?: number;

Defined in: packages/core/src/types/chat.ts:91

ts
optional maxTokens?: number;

Defined in: packages/core/src/types/chat.ts:92

ts
optional stream?: boolean;

Defined in: packages/core/src/types/chat.ts:93

ts
optional tools?: AparteTool[];

Defined in: packages/core/src/types/chat.ts:95

Tools the AI is allowed to call

ts
optional seed?: number;

Defined in: packages/core/src/types/chat.ts:106

Random seed for reproducibility. Passed through verbatim by the providers that support it (OpenAI-compatible endpoints, the AI SDK bridge, the transformers worker); Anthropic exposes none, so it is ignored there.

Nothing sets it for you: a retry varies its answer by sampling (temperature), not by seeding — this line used to promise an automatic per-retry seed that no code had ever written. Set it from a requestInterceptor when you need reproducible runs.

ts
optional toolChoice?: 
  | "auto"
  | "none"
  | {
  name: string;
  input?: Record<string, unknown>;
};

Defined in: packages/core/src/types/chat.ts:118

Controls which tool the model must call — mirrors OpenAI tool_choice / Anthropic tool_choice.

  • ‘auto’ : model decides (default when tools are present)
  • ‘none’ : no tools injected this turn — model answers directly
  • { name } : model MUST call this tool (provider injects a strong directive)
  • { name, input }: synthetic call — the agent loop (runStreamAgent, or the runner you injected) bypasses the LLM entirely and runs the handler directly with the provided input, then re-calls the LLM with the tool message in history.

Defined in: packages/core/src/types/chat.ts:139

The well-known keys of AparteChatRequest._meta. One, read by core’s client and not by the loop; the rest of the bag is the consumer’s (index signature). pipeline, artifactRaw and artifactXml used to sit here too and were removed (audit 2026-08-28, D2), then artifactHint (D7): an artifact is a plugin’s convention now, and a promoted code fence had no consumer.

ts
[key: string]: unknown

Consumer-specific context (open channel).

ts
optional prefixSegments?: AparteSegment[];

Defined in: packages/core/src/types/chat.ts:146

Segments to show at the top of the assistant’s turn before the model has said anything — a “planning” thinking block a host wants visible from the first frame, a status row. Added right after the turn flips to streaming, in this order, and never sent to the model.

ts
optional compaction?: true;

Defined in: packages/core/src/types/chat.ts:152

Set by @aparte/plugin-compaction on the summarisation request, and on nothing else. A backend transport can route it — a cheaper model, a shorter timeout — without inspecting the messages to guess what the request is for.


Defined in: packages/core/src/types/chat.ts:160

Token usage reported by the AI provider after a completed response.

ts
inputTokens: number;

Defined in: packages/core/src/types/chat.ts:162

Tokens in the prompt / conversation history sent to the model

ts
outputTokens: number;

Defined in: packages/core/src/types/chat.ts:164

Tokens generated by the model

ts
optional totalTokens?: number;

Defined in: packages/core/src/types/chat.ts:166

Total tokens (inputTokens + outputTokens). Populated when the provider reports it.

ts
optional cacheReadTokens?: number;

Defined in: packages/core/src/types/chat.ts:168

Tokens read from the provider’s prompt cache (Anthropic, OpenAI).

ts
optional durationMs?: number;

Defined in: packages/core/src/types/chat.ts:170

Wall-clock generation time in milliseconds (provider-measured). Used to compute tokens/sec.

ts
optional ttftMs?: number;

Defined in: packages/core/src/types/chat.ts:174

Time to first token in ms — the prefill / TTFT phase.

ts
optional decodeMs?: number;

Defined in: packages/core/src/types/chat.ts:176

Decode-phase wall time in ms (≈ durationMs − ttftMs).

ts
optional decodeTokens?: number;

Defined in: packages/core/src/types/chat.ts:178

Tokens emitted during the decode phase (outputTokens minus the first).

ts
optional wallMs?: number;

Defined in: packages/core/src/types/chat.ts:180

Total turn wall-clock time in ms, measured client-side across all phases.

ts
optional modelId?: string;

Defined in: packages/core/src/types/chat.ts:182

Id of the model that produced this response.

ts
optional device?: string;

Defined in: packages/core/src/types/chat.ts:184

Compute device used for this response (‘webgpu’ | ‘wasm’ | …).

ts
optional phases?: AparteUsage[];

Defined in: packages/core/src/types/chat.ts:190

Per-call breakdown when a single turn used several provider calls (e.g. a tool-use round-trip or a mid-turn model swap). Absent for a plain single-call turn. Each entry is the AparteUsage of one provider call.


Defined in: packages/core/src/types/chat.ts:204

Normalized stream event map — extensible via declaration merging.

ts
// In a plugin or app:
declare module '@aparte/core' {
  interface AparteStreamEventMap {
    tool_call: { name: string; arguments: string }
  }
}
ts
text: {
  delta: string;
};

Defined in: packages/core/src/types/chat.ts:205

ts
delta: string;
ts
thinking: {
  delta: string;
};

Defined in: packages/core/src/types/chat.ts:206

ts
delta: string;
ts
done: {
  usage?: AparteUsage;
};

Defined in: packages/core/src/types/chat.ts:208

Emitted when the stream ends. May carry token usage reported by the provider.

ts
optional usage?: AparteUsage;
ts
error: {
  message: string;
};

Defined in: packages/core/src/types/chat.ts:209

ts
message: string;
ts
tool_use: AparteToolCall;

Defined in: packages/core/src/types/chat.ts:210


Defined in: packages/core/src/types/events.ts:19

Detail payload for aparte-send custom event Emitted when user sends a message via the input component

ts
content: string;

Defined in: packages/core/src/types/events.ts:21

The message content being sent

ts
timestamp: number;

Defined in: packages/core/src/types/events.ts:24

Timestamp of the send action

ts
optional targetId?: string;

Defined in: packages/core/src/types/events.ts:33

ID of the chat this send belongs to: the composer’s target attribute, or the id of the <aparte-chat> / [data-aparte-chat] host above it — the same resolution the composer’s receive side (_isForThisComposer) uses, so the two halves of the channel cannot disagree about which chat is which. Used by AparteClient to find the host without DOM traversal.

ts
optional files?: File[];

Defined in: packages/core/src/types/events.ts:36

Files attached to this message (populated by composer attachments)

ts
optional modelId?: string;

Defined in: packages/core/src/types/events.ts:50

Send THIS message to a specific model, overriding the config’s default for one turn — a per-message model picker, the pattern several products now ship.

AparteClient has honoured both fields for as long as it has read event.detail, while nothing declared them: the composer never sends them and this type did not name them, so the capability existed only for someone who read the client’s source. A capability cited nowhere is invisible (ratified decision #4); declaring it is what makes it real. Dispatch your own aparte-send with them, or set them on the detail from a listener that runs before the client’s (capture phase).

ts
optional providerId?: string;

Defined in: packages/core/src/types/events.ts:52

The provider that modelId belongs to; defaults to the config’s defaultProvider.


Defined in: packages/core/src/types/events.ts:119

Per-message sibling metadata carried by aparte-path-changed. index is 0-based; UI renders it as index + 1 / count.

ts
id: string;

Defined in: packages/core/src/types/events.ts:121

Message id

ts
count: number;

Defined in: packages/core/src/types/events.ts:123

Total number of siblings (including this one). 1 = no branch picker.

ts
index: number;

Defined in: packages/core/src/types/events.ts:125

0-based position of this message among its siblings.


Defined in: packages/core/src/types/imperative-api.ts:14

The canonical imperative surface a framework <AparteChat> exposes (React’s ref handle, Vue/Svelte’s instance, Angular’s component). All four delegate to the framework-agnostic AparteChatHost, so this ONE contract is the single source of truth, and each wrapper enforces it against the compiler — React via the ref-handle type on a return-annotated useImperativeHandle, Angular via implements, Vue via satisfies on defineExpose, Svelte via a type-checked parity factory — so a missing or mistyped method is a build error in that wrapper, not a silent divergence.

ts
appendMessage: (message, options?) => void;

Defined in: packages/core/src/types/imperative-api.ts:23

Append a message. { historical: true } says it is RESTORED, not live: its segments are adopted as they are — no fresh timing stamps, isStreaming forced off — the way a stored conversation is loaded, so a tool call read back from your own backend renders settled rather than spinning. Use it when you replay history one message at a time; a live turn passes nothing.

ParameterType
messageAparteMessage
options?{ historical?: boolean; }
options.historical?boolean

void

ts
updateMessage: (messageId, updates) => void;

Defined in: packages/core/src/types/imperative-api.ts:24

ParameterType
messageIdstring
updatesPartial<AparteMessage>

void

ts
updateLastMessage: (content, options?) => void;

Defined in: packages/core/src/types/imperative-api.ts:25

ParameterType
contentstring
options?{ append?: boolean; }
options.append?boolean

void

ts
addSegment: (segment) => void;

Defined in: packages/core/src/types/imperative-api.ts:26

ParameterType
segmentAparteSegment

void

ts
updateSegment: (segmentId, updates) => void;

Defined in: packages/core/src/types/imperative-api.ts:27

ParameterType
segmentIdstring
updatesPartial<AparteSegment>

void

ts
removeSegment: (segmentId) => void;

Defined in: packages/core/src/types/imperative-api.ts:28

ParameterType
segmentIdstring

void

ts
appendToSegment: (segmentId, content) => void;

Defined in: packages/core/src/types/imperative-api.ts:42

Append text to a segment of the last message — the streaming path for a thinking block, a tool pill, or any segment that grows token by token.

Each chunk is written straight into the bubble, and the framework’s message list is synced once per frame rather than per chunk, so streaming a segment costs about one render per frame instead of one per token.

Note that segments and content are mutually exclusive at render time: as soon as a message has segments the bubble hides its plain content, so a message driven by injectTokenStream (which writes content) can’t also show a thinking segment — put the text in a segment of its own instead.

ParameterType
segmentIdstring
contentstring

void

ts
getMessages: () => AparteMessage[];

Defined in: packages/core/src/types/imperative-api.ts:43

AparteMessage[]

ts
clearMessages: (options?) => void;

Defined in: packages/core/src/types/imperative-api.ts:44

ParameterType
options?{ revokeAttachments?: boolean; }
options.revokeAttachments?boolean

void

ts
addBranch: (messageId) => number;

Defined in: packages/core/src/types/imperative-api.ts:46

ParameterType
messageIdstring

number

ts
addSiblingOf: (existingId, message) => string | null;

Defined in: packages/core/src/types/imperative-api.ts:47

ParameterType
existingIdstring
messageAparteMessage

string | null

ts
truncateFrom: (messageId) => void;

Defined in: packages/core/src/types/imperative-api.ts:48

ParameterType
messageIdstring

void

ts
truncateResponsesAfter: (userMessageId) => void;

Defined in: packages/core/src/types/imperative-api.ts:49

ParameterType
userMessageIdstring

void

ts
injectTokenStream: (messageId, tokens) => Promise<void>;

Defined in: packages/core/src/types/imperative-api.ts:66

Stream tokens into a message from your own loop (no AparteClient involved). It sends the turn’s lifecycle events itself — aparte-message-start, then -done, -aborted or -error — so the composer shows Stop, its Stop cancels the stream, and the conversation manager saves the reply. Resolves when the iterable completes; the message is then marked complete.

If messageId doesn’t exist the viewport auto-creates an empty assistant message — but only in its internal repo, NOT in the framework’s message state. In a wrapper, call appendMessage({ id, role: 'assistant', content: '' , timestamp: Date.now() }) first so both stay in sync. Starting a new stream cancels the previous one; stopTokenStream cancels explicitly. See the “Bring your own loop” guide.

ParameterType
messageIdstring
tokensAsyncIterable<string>

Promise<void>

ts
stopTokenStream: () => void;

Defined in: packages/core/src/types/imperative-api.ts:68

Abort an in-flight injectTokenStream loop (the source iterable is return()ed).

void

ts
setConversationId: (id) => Promise<void>;

Defined in: packages/core/src/types/imperative-api.ts:70

ParameterType
idstring | null

Promise<void>

ts
scrollToBottom: () => void;

Defined in: packages/core/src/types/imperative-api.ts:72

void

ts
focusInput: () => void;

Defined in: packages/core/src/types/imperative-api.ts:73

void

ts
isStreaming: () => boolean;

Defined in: packages/core/src/types/imperative-api.ts:74

boolean

ts
getViewport: () => HTMLElement | null;

Defined in: packages/core/src/types/imperative-api.ts:76

The <aparte-chat-viewport> element — same getViewport() on all four wrappers.

HTMLElement | null


Defined in: packages/core/src/types/imperative-api.ts:89

The imperative surface every framework <AparteUi> proxy exposes — React’s ref handle, Vue’s template ref, Svelte’s bind:this, Angular’s @ViewChild.

It lives here for the same reason AparteChatImperativeApi does: it was hand-copied into all four wrappers, each docblock promising “the same contract on all four”, and it had already drifted once (two of them returned HTMLElement | null / unknown instead of the generic form). One declaration, four re-exports, and a divergence is a compile error rather than a promise in prose.

ts
getElement<T>(): T | null;

Defined in: packages/core/src/types/imperative-api.ts:91

The proxied custom element, once mounted.

Type ParameterDefault type
T extends HTMLElementHTMLElement

T | null

ts
callMethod<T>(methodName, ...args): T | undefined;

Defined in: packages/core/src/types/imperative-api.ts:93

Call a method on it — the escape hatch for an element-specific API.

Type ParameterDefault type
Tunknown
ParameterType
methodNamestring
…argsunknown[]

T | undefined


Defined in: packages/core/src/types/model-provider.ts:30

Represents an AI model that can be selected by the user.

typescript
const model: AparteAIModel = {
  id: 'gpt-4-turbo',
  name: 'GPT-4 Turbo',
  contextWindow: 128000,
  capabilities: ['vision', 'function_calling', 'streaming']
};
ts
id: string;

Defined in: packages/core/src/types/model-provider.ts:32

Unique identifier for the model (e.g., ‘gpt-4-turbo’, ‘claude-3-opus’)

ts
name: string;

Defined in: packages/core/src/types/model-provider.ts:35

Human-readable display name

ts
optional contextWindow?: number;

Defined in: packages/core/src/types/model-provider.ts:38

Optional context window size in tokens

ts
optional pricing?: {
  input: number;
  output: number;
};

Defined in: packages/core/src/types/model-provider.ts:41

Optional pricing information (per 1M tokens)

ts
input: number;
ts
output: number;
ts
optional capabilities?: ("streaming" | "code" | "vision" | "function_calling" | "reasoning")[];

Defined in: packages/core/src/types/model-provider.ts:47

Optional capability flags

ts
optional description?: string;

Defined in: packages/core/src/types/model-provider.ts:50

Optional short description shown in model pickers

ts
optional metadata?: Record<string, unknown>;

Defined in: packages/core/src/types/model-provider.ts:53

Optional metadata for custom properties


Defined in: packages/core/src/types/model-provider.ts:75

Progress update emitted during model preparation (download + load). Passed to the onProgress callback of AparteAIProvider.prepareModel().

ts
status: "error" | "downloading" | "loading" | "cached" | "ready";

Defined in: packages/core/src/types/model-provider.ts:77

Current phase of the preparation

ts
optional file?: string;

Defined in: packages/core/src/types/model-provider.ts:79

File currently being processed (e.g. “model.safetensors”)

ts
optional progress?: number;

Defined in: packages/core/src/types/model-provider.ts:81

Progress percentage 0–100

ts
optional message?: string;

Defined in: packages/core/src/types/model-provider.ts:83

Optional human-readable message


Defined in: packages/core/src/types/model-provider.ts:93

Brand metadata for an AI provider, used for visual selection and onboarding.

ts
name: string;

Defined in: packages/core/src/types/model-provider.ts:95

Human-readable display name

ts
id: string;

Defined in: packages/core/src/types/model-provider.ts:98

Provider ID

ts
optional icon?: string;

Defined in: packages/core/src/types/model-provider.ts:104

Brand icon. Can be an SVG string, a data URI, or a key for AparteIconProvider.

ts
optional color?: string;

Defined in: packages/core/src/types/model-provider.ts:107

Brand primary color (hex or CSS variable)

ts
optional description?: string;

Defined in: packages/core/src/types/model-provider.ts:110

Short tag line or description

ts
optional hasFreeModels?: boolean;

Defined in: packages/core/src/types/model-provider.ts:113

Whether the provider offers free models or Tiers

ts
optional isLocal?: boolean;

Defined in: packages/core/src/types/model-provider.ts:116

Whether the provider runs locally on the user machine

ts
optional helpUrl?: string;

Defined in: packages/core/src/types/model-provider.ts:119

URL to the provider’s API key management page or documentation

ts
optional configSchema?: AparteAIProviderConfigSchema;

Defined in: packages/core/src/types/model-provider.ts:122

Configuration schema for the onboarding UI


Defined in: packages/core/src/types/model-provider.ts:128

Definition of a configuration field for the provider

ts
id: string;

Defined in: packages/core/src/types/model-provider.ts:130

Unique identifier for the field (e.g., ‘apiKey’, ‘endpoint’)

ts
type: "text" | "password" | "url";

Defined in: packages/core/src/types/model-provider.ts:132

Basic type for the input field

ts
label: string;

Defined in: packages/core/src/types/model-provider.ts:134

Human readable label (e.g., ‘API Key’, ‘Sever URL’)

ts
optional placeholder?: string;

Defined in: packages/core/src/types/model-provider.ts:136

Placeholder hint

ts
optional defaultValue?: string;

Defined in: packages/core/src/types/model-provider.ts:138

Initial value

ts
optional required?: boolean;

Defined in: packages/core/src/types/model-provider.ts:140

Whether the field is mandatory to start

ts
optional isAdvanced?: boolean;

Defined in: packages/core/src/types/model-provider.ts:142

If true, the field is hidden behind an ‘Advanced’ toggle in UI


Defined in: packages/core/src/types/model-provider.ts:148

Encapsulates all configuration fields for a provider

ts
fields: AparteAIProviderConfigField[];

Defined in: packages/core/src/types/model-provider.ts:149


Defined in: packages/core/src/types/model-provider.ts:321

Configuration options for model selection behavior.

ts
optional enabledProviders?: string[];

Defined in: packages/core/src/types/model-provider.ts:323

Which providers to enable (all if omitted)

ts
optional modelFilters?: Record<string, string[]>;

Defined in: packages/core/src/types/model-provider.ts:326

Filter models by provider

ts
optional defaultProvider?: string;

Defined in: packages/core/src/types/model-provider.ts:329

Default provider ID

ts
optional defaultModel?: string;

Defined in: packages/core/src/types/model-provider.ts:332

Default model ID


Defined in: packages/core/src/types/models.ts:34

Attachment interface for media and file support Future-proof structure for images, audio, documents, etc.

ts
id: string;

Defined in: packages/core/src/types/models.ts:36

Unique identifier for the attachment

ts
name: string;

Defined in: packages/core/src/types/models.ts:39

Display name of the attachment

ts
type: string;

Defined in: packages/core/src/types/models.ts:42

MIME type (e.g., ‘image/png’, ‘application/pdf’)

ts
url: string;

Defined in: packages/core/src/types/models.ts:45

URL or data URI to access the attachment

ts
optional size?: number;

Defined in: packages/core/src/types/models.ts:48

Optional file size in bytes

ts
optional thumbnailUrl?: string;

Defined in: packages/core/src/types/models.ts:51

Optional thumbnail URL for previews

ts
optional metadata?: Record<string, unknown>;

Defined in: packages/core/src/types/models.ts:54

Optional metadata for custom properties

ts
optional blob?: Blob;

Defined in: packages/core/src/types/models.ts:62

Binary payload. Set at message-creation time (File from upload) so the persistence adapter can save it to its attachments table. Stripped from the serialised message row — the adapter reconstructs url via URL.createObjectURL(blob) on hydration. Not serialised to JSON.


Defined in: packages/core/src/types/models.ts:82

A single branch (alternative response) within an assistant message.

The active branching system is tree-based via AparteMessageRepository (siblings under the same parent node). This in-message versioning struct is kept only for backwards compatibility with existing serialised payloads. New code should rely on viewport methods addSiblingOf / navigateBranch and on the aparte-path-changed event payload to drive the branch picker UI.

ts
id: string;

Defined in: packages/core/src/types/models.ts:84

Unique identifier for this branch

ts
optional content?: string;

Defined in: packages/core/src/types/models.ts:86

Text content of this branch

ts
optional segments?: AparteSegment[];

Defined in: packages/core/src/types/models.ts:88

Rich segments of this branch

ts
optional status?: AparteStatus;

Defined in: packages/core/src/types/models.ts:90

Branch status

ts
timestamp: number;

Defined in: packages/core/src/types/models.ts:92

Creation timestamp


Defined in: packages/core/src/types/models.ts:125

Controls which action buttons are rendered in message bubbles. Pass to aparteGlobalConfig.setBubbleActions() to customise or disable actions.

Only copy is on by default. Core can copy text on its own; every other button needs someone outside core to honor it — AparteClient (retry, edit) or your own listener (feedback, info). A button nobody answers is a lie told to the user, so aparté ships them off and you turn on what you handle:

ts
aparteGlobalConfig.setBubbleActions({ retry: true, edit: true });   // you run AparteClient

Two ways to configure:

  • Global flags (copy/retry/edit/feedback/info) — role-aware defaults.
  • Explicit per-role ordered sets (user/assistant) — when provided for a role, they fully replace the flag-derived set for that role: buttons render in exactly the given order, and naming a button there IS the opt-in (the flag is not consulted). Lets a theme match a specific product 1:1.
ts
optional copy?: boolean;

Defined in: packages/core/src/types/models.ts:127

Copy message text to clipboard — the one action core honors alone. Default: true

ts
optional retry?: boolean;

Defined in: packages/core/src/types/models.ts:129

Retry / regenerate the response (assistant bubbles). Needs a host to re-send. Default: false

ts
optional edit?: boolean;

Defined in: packages/core/src/types/models.ts:131

Edit the sent message inline (user bubbles). Needs a host to keep the new text. Default: false

ts
optional feedback?: boolean;

Defined in: packages/core/src/types/models.ts:133

Thumbs-up / thumbs-down feedback (assistant bubbles). Needs a listener. Default: false

ts
optional info?: boolean;

Defined in: packages/core/src/types/models.ts:139

The details (“i”) button, which opens the app-owned stats popover by emitting aparte-message-info (assistant bubbles). Rendered only when the message also carries a usage — no numbers, nothing to show. Default: false

ts
optional user?: AparteBubbleActionName[];

Defined in: packages/core/src/types/models.ts:141

Explicit, ordered action set for USER bubbles. Example: ['edit', 'copy'].

ts
optional assistant?: AparteBubbleActionName[];

Defined in: packages/core/src/types/models.ts:143

Explicit, ordered action set for ASSISTANT bubbles. Example: ['copy', 'thumbUp', 'thumbDown', 'retry'].


Defined in: packages/core/src/types/models.ts:159

The affordances core RENDERS but cannot COMPLETE — each one only asks, through a DOM event, and the app does the work. Declare what your app actually handles with aparteGlobalConfig.setHostHandlers(); everything else stays out of the UI rather than showing a control that answers to nobody.

ts
aparteGlobalConfig.setHostHandlers({ attachmentPreview: true });   // you open a lightbox

These are declarations, not callbacks: the event (aparte-attachment-preview) is unchanged, and you keep listening for it wherever you already do.

ts
optional attachmentPreview?: boolean;

Defined in: packages/core/src/types/models.ts:165

Clicking an image attachment asks for a full-size preview (aparte-attachment-preview) — core owns no lightbox. Until declared, the tile is inert and is not signalled as a button. Default: false


Defined in: packages/core/src/types/models.ts:172

Core message structure for the chat Supports both simple content and rich multi-segment content

ts
id: string;

Defined in: packages/core/src/types/models.ts:174

Unique identifier for the message

ts
role: AparteBubbleRole;

Defined in: packages/core/src/types/models.ts:177

Role of the message sender

ts
optional content?: string;

Defined in: packages/core/src/types/models.ts:183

Simple text content (for basic messages) Use segments for rich content with multiple parts

ts
optional segments?: AparteSegment[];

Defined in: packages/core/src/types/models.ts:189

Rich content segments (thinking, code, tool calls, artifacts, …) Takes precedence over content if provided

ts
timestamp: number;

Defined in: packages/core/src/types/models.ts:192

Unix timestamp of message creation

ts
optional isStreaming?: boolean;

Defined in: packages/core/src/types/models.ts:195

Whether the message is still being streamed

ts
optional status?: AparteStatus;

Defined in: packages/core/src/types/models.ts:198

Current status of the message

ts
optional attachments?: AparteAttachment[];

Defined in: packages/core/src/types/models.ts:201

Optional attachments (images, files, etc.)

ts
optional usage?: AparteUsage;

Defined in: packages/core/src/types/models.ts:204

Token usage + timing reported by the provider for assistant messages.

ts
optional model?: {
  providerId: string;
  modelId: string;
  temperature?: number;
};

Defined in: packages/core/src/types/models.ts:211

The provider, model and temperature an assistant turn ran on, written by AparteClient when the turn starts. A turn a server paused is resumed on them after a reload (resumeDeferred), not on whatever model is selected by then.

ts
providerId: string;
ts
modelId: string;
ts
optional temperature?: number;
ts
optional compaction?: true;

Defined in: packages/core/src/types/models.ts:221

This message is the summary @aparte/plugin-compaction injected in place of the turns it summarised (a host that summarises by other means sets the flag too). Its role is user — the summary is context handed to the model, and a system message mid-conversation is refused by some providers — but it is not something the user said: the viewport renders it as a notice (data-kind="compaction" on the bubble: centred, no avatar, no actions) and the history sends it under a fixed preamble saying what it is.

ts
optional branches?: AparteMessageBranch[];

Defined in: packages/core/src/types/models.ts:230

Alternative responses generated by retrying.

Use the tree-based branching exposed by AparteMessageRepository (siblings under the same parent). The bubble no longer reads this field — sibling counts come from aparte-path-changed event metadata.

ts
optional activeBranchIndex?: number;

Defined in: packages/core/src/types/models.ts:236

Index of the currently displayed branch (default 0).

See AparteMessage.branches.

ts
optional metadata?: Record<string, unknown>;

Defined in: packages/core/src/types/models.ts:239

Optional metadata for custom properties


Defined in: packages/core/src/types/models.ts:247

Configuration options for AparteChatViewport

ts
optional scrollThreshold?: number;

Defined in: packages/core/src/types/models.ts:249

Threshold in pixels for smart scroll detection

ts
optional maxRenderedBubbles?: number;

Defined in: packages/core/src/types/models.ts:257

DOM render cap: the max number of bubbles kept in the DOM at once (a perf ceiling for very long conversations). This does NOT evict messages from the conversation model — the full tree and its persistence snapshot stay intact. Defaults to 1000.

ts
optional layoutTransitionMs?: number;

Defined in: packages/core/src/types/models.ts:265

Duration in milliseconds to freeze spacer recalculation after resetSpacer(). Set this to the duration of any CSS layout transition in the host app (e.g. the flex animation that moves the composer from center to bottom). Defaults to 0 (no freeze). Example: 350 when transition: flex 0.3s ease is used.


Defined in: packages/core/src/types/models.ts:269

Configuration options for the composer input.

ts
optional placeholder?: string;

Defined in: packages/core/src/types/models.ts:271

Placeholder text for the input

ts
optional maxHeight?: number;

Defined in: packages/core/src/types/models.ts:274

Maximum height in pixels for auto-expand

ts
optional minHeight?: number;

Defined in: packages/core/src/types/models.ts:277

Minimum height in pixels


Defined in: packages/core/src/types/parsers.ts:15

Content parser plugin interface for external parsers Allows integration of Markdown, code highlighting, LaTeX, etc. without adding dependencies to the core

ts
id: string;

Defined in: packages/core/src/types/parsers.ts:17

Unique identifier for the parser

ts
priority: number;

Defined in: packages/core/src/types/parsers.ts:20

Priority for parser execution (higher = first)

ts
canParse(content): boolean;

Defined in: packages/core/src/types/parsers.ts:27

Check if this parser can handle the content

ParameterTypeDescription
contentstringRaw content to check

boolean

true if parser should process this content

ts
parse(content): string;

Defined in: packages/core/src/types/parsers.ts:34

Parse content and return transformed output

ParameterTypeDescription
contentstringRaw content to parse

string

Parsed HTML string or transformed content


Defined in: packages/core/src/types/segments.ts:11

Base segment interface - all segments extend this

ts
id: string;

Defined in: packages/core/src/types/segments.ts:13

Unique segment identifier

ts
type: string;

Defined in: packages/core/src/types/segments.ts:16

Segment type discriminator

ts
optional isStreaming?: boolean;

Defined in: packages/core/src/types/segments.ts:19

Whether segment is currently being streamed

ts
optional fallback?: string;

Defined in: packages/core/src/types/segments.ts:36

One sentence standing in for the segment when no renderer claims its type — DRAWN for every type, not only for custom.

It is the answer to a real moment: a conversation replayed in another app, a client that loads its views lazily, an export. Core draws it as text (no markup) under .aparte-segment-fallback. Supplying one also silences the “no renderer” developer warning: an author who wrote a fallback has already said this can happen.

In the HISTORY sent back to the model it stands in for the segment only where core does not serialise the type itself — custom, and any type core does not know (a registered block grammar’s, a plugin’s). A text or a code segment contributes its content and nothing else; thinking and tool_call are left out of the history on purpose (_segmentsToText in AparteClient states why), and so is error.

ts
optional messageId?: string;

Defined in: packages/core/src/types/segments.ts:55

Id of the message this segment belongs to. Stamped on insertion.

ts
optional index?: number;

Defined in: packages/core/src/types/segments.ts:61

Position in the owning message’s segments[], maintained across insertions and removals.

ts
optional meta?: Record<string, unknown> & {
  aparte?: AparteSegmentTiming;
};

Defined in: packages/core/src/types/segments.ts:71

Extras the producer of the segment knows — token counts, cost, compute device — plus aparte, the one sub-object core writes.

Everything except meta.aparte is yours: fill it with updateSegment(segmentId, { meta }), which MERGES rather than replaces. Mirrors AparteMessage.metadata.

ts
optional aparte?: AparteSegmentTiming;

Defined in: packages/core/src/types/segments.ts:93

What core measured about a segment. Lives in meta.aparte, not on the segment.

These two were first-class fields, and moving them is not tidying. No protocol carries a timestamp on a content block — verified rather than assumed: Anthropic’s blocks have none (and neither does the message), OpenAI’s output_text part is {annotations, logprobs, text, type} with created_at on the item above it, and the AI SDK’s UIMessage.parts have none either. What the AI SDK does have is a metadata bag whose canonical example is literally { createdAt, model, totalTokens } — at the message level. A per-block id has industry precedent; per-block time has none.

So this is a local measurement, and the shape says so. It stays typed — the bag is where it belongs, opacity is not part of the deal — and it is namespaced under aparte because meta is the consumer’s: a flat startedAt there would collide with a key of their own.

Core reads it through segmentDuration and isSegmentSettled; it never renders either number.

ts
optional startedAt?: number;

Defined in: packages/core/src/types/segments.ts:95

Epoch ms when the segment entered a LIVE transcript. Absent for history.

ts
optional endedAt?: number;

Defined in: packages/core/src/types/segments.ts:111

Epoch ms when content last arrived on this segment.

It ADVANCES while the segment streams and freezes when the segment settles, so endedAt - startedAt is a live duration during a turn and a final one after it — ask isSegmentSettled(segment) which you are looking at.

Why not simply “when it finished”: the two obvious rules are both wrong, and measurably. Closing at the end of the turn makes a reasoning block span the whole answer that followed it (2s of thinking before a 20s reply reads ”22s”). Closing when the next segment opens is the same error, smaller — a gap of ten seconds before the next segment is counted as thinking, while the person watching knows nothing happened. The last delta is the only moment the segment itself can vouch for.


Defined in: packages/core/src/types/segments.ts:120

Text segment - plain text content

ts
{ id: 's1', type: 'text', content: 'A transport is the object that talks to the model.' }
ts
id: string;

Defined in: packages/core/src/types/segments.ts:13

Unique segment identifier

AparteSegmentBase.id

ts
optional isStreaming?: boolean;

Defined in: packages/core/src/types/segments.ts:19

Whether segment is currently being streamed

AparteSegmentBase.isStreaming

ts
optional fallback?: string;

Defined in: packages/core/src/types/segments.ts:36

One sentence standing in for the segment when no renderer claims its type — DRAWN for every type, not only for custom.

It is the answer to a real moment: a conversation replayed in another app, a client that loads its views lazily, an export. Core draws it as text (no markup) under .aparte-segment-fallback. Supplying one also silences the “no renderer” developer warning: an author who wrote a fallback has already said this can happen.

In the HISTORY sent back to the model it stands in for the segment only where core does not serialise the type itself — custom, and any type core does not know (a registered block grammar’s, a plugin’s). A text or a code segment contributes its content and nothing else; thinking and tool_call are left out of the history on purpose (_segmentsToText in AparteClient states why), and so is error.

AparteSegmentBase.fallback

ts
optional messageId?: string;

Defined in: packages/core/src/types/segments.ts:55

Id of the message this segment belongs to. Stamped on insertion.

AparteSegmentBase.messageId

ts
optional index?: number;

Defined in: packages/core/src/types/segments.ts:61

Position in the owning message’s segments[], maintained across insertions and removals.

AparteSegmentBase.index

ts
optional meta?: Record<string, unknown> & {
  aparte?: AparteSegmentTiming;
};

Defined in: packages/core/src/types/segments.ts:71

Extras the producer of the segment knows — token counts, cost, compute device — plus aparte, the one sub-object core writes.

Everything except meta.aparte is yours: fill it with updateSegment(segmentId, { meta }), which MERGES rather than replaces. Mirrors AparteMessage.metadata.

ts
optional aparte?: AparteSegmentTiming;

AparteSegmentBase.meta

ts
type: "text";

Defined in: packages/core/src/types/segments.ts:121

Segment type discriminator

AparteSegmentBase.type

ts
content: string;

Defined in: packages/core/src/types/segments.ts:122


Defined in: packages/core/src/types/segments.ts:155

Thinking/reasoning segment - collapsible.

A literal <think>…</think> in a reply is read as reasoning on every path — while it streams and when the reply is restored from storage — so a message that merely quotes the tag in its prose becomes this segment rather than text; and reasoning is deliberately not sent back to the model, so those words leave the wire history with it. Models whose reasoning is marked differently are handled by the parser’s thinkingDelimiters.

ts
// No `collapsed` key, so it renders CLOSED — see the field's own note below.
{
  id: 's1',
  type: 'thinking',
  label: 'Reasoning',
  content: 'The question is about transports, so name the two and say where the key lives.',
}
ts
id: string;

Defined in: packages/core/src/types/segments.ts:13

Unique segment identifier

AparteSegmentBase.id

ts
optional isStreaming?: boolean;

Defined in: packages/core/src/types/segments.ts:19

Whether segment is currently being streamed

AparteSegmentBase.isStreaming

ts
optional fallback?: string;

Defined in: packages/core/src/types/segments.ts:36

One sentence standing in for the segment when no renderer claims its type — DRAWN for every type, not only for custom.

It is the answer to a real moment: a conversation replayed in another app, a client that loads its views lazily, an export. Core draws it as text (no markup) under .aparte-segment-fallback. Supplying one also silences the “no renderer” developer warning: an author who wrote a fallback has already said this can happen.

In the HISTORY sent back to the model it stands in for the segment only where core does not serialise the type itself — custom, and any type core does not know (a registered block grammar’s, a plugin’s). A text or a code segment contributes its content and nothing else; thinking and tool_call are left out of the history on purpose (_segmentsToText in AparteClient states why), and so is error.

AparteSegmentBase.fallback

ts
optional messageId?: string;

Defined in: packages/core/src/types/segments.ts:55

Id of the message this segment belongs to. Stamped on insertion.

AparteSegmentBase.messageId

ts
optional index?: number;

Defined in: packages/core/src/types/segments.ts:61

Position in the owning message’s segments[], maintained across insertions and removals.

AparteSegmentBase.index

ts
optional meta?: Record<string, unknown> & {
  aparte?: AparteSegmentTiming;
};

Defined in: packages/core/src/types/segments.ts:71

Extras the producer of the segment knows — token counts, cost, compute device — plus aparte, the one sub-object core writes.

Everything except meta.aparte is yours: fill it with updateSegment(segmentId, { meta }), which MERGES rather than replaces. Mirrors AparteMessage.metadata.

ts
optional aparte?: AparteSegmentTiming;

AparteSegmentBase.meta

ts
type: "thinking";

Defined in: packages/core/src/types/segments.ts:156

Segment type discriminator

AparteSegmentBase.type

ts
content: string;

Defined in: packages/core/src/types/segments.ts:157

ts
optional collapsed?: boolean;

Defined in: packages/core/src/types/segments.ts:170

Open the block. Absent means CLOSED — a reasoning block is a disclosure, and the reader opens it.

It used to be the other way round: absent meant open, and core’s own parser emitted collapsed: false on every block it produced, so a reasoning block stayed unfolded for the whole conversation and buried the answer under it. No assistant on the market does that — the content is behind a click, streaming or settled.

false is still how you open one on purpose. Only ABSENT changed meaning.

ts
optional label?: string;

Defined in: packages/core/src/types/segments.ts:171


Defined in: packages/core/src/types/segments.ts:189

Code segment - syntax highlighted code block

Highlighting is a plugin you opt into; with none installed the block renders plain, which is what core’s zero-dependency promise costs here.

ts
{
 *   id: 's1',
 *   type: 'code',
 *   language: 'ts',
 *   filename: 'transport.ts',
 *   content: "const transport = new AparteBackendTransport({ endpoint: '/api/chat' });",
 * }
ts
id: string;

Defined in: packages/core/src/types/segments.ts:13

Unique segment identifier

AparteSegmentBase.id

ts
optional isStreaming?: boolean;

Defined in: packages/core/src/types/segments.ts:19

Whether segment is currently being streamed

AparteSegmentBase.isStreaming

ts
optional fallback?: string;

Defined in: packages/core/src/types/segments.ts:36

One sentence standing in for the segment when no renderer claims its type — DRAWN for every type, not only for custom.

It is the answer to a real moment: a conversation replayed in another app, a client that loads its views lazily, an export. Core draws it as text (no markup) under .aparte-segment-fallback. Supplying one also silences the “no renderer” developer warning: an author who wrote a fallback has already said this can happen.

In the HISTORY sent back to the model it stands in for the segment only where core does not serialise the type itself — custom, and any type core does not know (a registered block grammar’s, a plugin’s). A text or a code segment contributes its content and nothing else; thinking and tool_call are left out of the history on purpose (_segmentsToText in AparteClient states why), and so is error.

AparteSegmentBase.fallback

ts
optional messageId?: string;

Defined in: packages/core/src/types/segments.ts:55

Id of the message this segment belongs to. Stamped on insertion.

AparteSegmentBase.messageId

ts
optional index?: number;

Defined in: packages/core/src/types/segments.ts:61

Position in the owning message’s segments[], maintained across insertions and removals.

AparteSegmentBase.index

ts
optional meta?: Record<string, unknown> & {
  aparte?: AparteSegmentTiming;
};

Defined in: packages/core/src/types/segments.ts:71

Extras the producer of the segment knows — token counts, cost, compute device — plus aparte, the one sub-object core writes.

Everything except meta.aparte is yours: fill it with updateSegment(segmentId, { meta }), which MERGES rather than replaces. Mirrors AparteMessage.metadata.

ts
optional aparte?: AparteSegmentTiming;

AparteSegmentBase.meta

ts
type: "code";

Defined in: packages/core/src/types/segments.ts:190

Segment type discriminator

AparteSegmentBase.type

ts
content: string;

Defined in: packages/core/src/types/segments.ts:191

ts
optional language?: string;

Defined in: packages/core/src/types/segments.ts:192

ts
optional filename?: string;

Defined in: packages/core/src/types/segments.ts:193

ts
optional showLineNumbers?: boolean;

Defined in: packages/core/src/types/segments.ts:198

Read by nothing: no renderer and no stylesheet draws line numbers, so setting it changes nothing. It will be removed in a later minor.


Defined in: packages/core/src/types/segments.ts:215

Error segment

A failure card inside the turn that produced it, rather than a toast somewhere else on the page — so the reader can see which question failed.

ts
{
 *   id: 's1',
 *   type: 'error',
 *   content: 'The model did not answer.',
 *   details: 'HTTP 429 — rate limited. Retrying in 20s.',
 * }
ts
id: string;

Defined in: packages/core/src/types/segments.ts:13

Unique segment identifier

AparteSegmentBase.id

ts
optional isStreaming?: boolean;

Defined in: packages/core/src/types/segments.ts:19

Whether segment is currently being streamed

AparteSegmentBase.isStreaming

ts
optional fallback?: string;

Defined in: packages/core/src/types/segments.ts:36

One sentence standing in for the segment when no renderer claims its type — DRAWN for every type, not only for custom.

It is the answer to a real moment: a conversation replayed in another app, a client that loads its views lazily, an export. Core draws it as text (no markup) under .aparte-segment-fallback. Supplying one also silences the “no renderer” developer warning: an author who wrote a fallback has already said this can happen.

In the HISTORY sent back to the model it stands in for the segment only where core does not serialise the type itself — custom, and any type core does not know (a registered block grammar’s, a plugin’s). A text or a code segment contributes its content and nothing else; thinking and tool_call are left out of the history on purpose (_segmentsToText in AparteClient states why), and so is error.

AparteSegmentBase.fallback

ts
optional messageId?: string;

Defined in: packages/core/src/types/segments.ts:55

Id of the message this segment belongs to. Stamped on insertion.

AparteSegmentBase.messageId

ts
optional index?: number;

Defined in: packages/core/src/types/segments.ts:61

Position in the owning message’s segments[], maintained across insertions and removals.

AparteSegmentBase.index

ts
optional meta?: Record<string, unknown> & {
  aparte?: AparteSegmentTiming;
};

Defined in: packages/core/src/types/segments.ts:71

Extras the producer of the segment knows — token counts, cost, compute device — plus aparte, the one sub-object core writes.

Everything except meta.aparte is yours: fill it with updateSegment(segmentId, { meta }), which MERGES rather than replaces. Mirrors AparteMessage.metadata.

ts
optional aparte?: AparteSegmentTiming;

AparteSegmentBase.meta

ts
type: "error";

Defined in: packages/core/src/types/segments.ts:216

Segment type discriminator

AparteSegmentBase.type

ts
content: string;

Defined in: packages/core/src/types/segments.ts:217

ts
optional details?: string;

Defined in: packages/core/src/types/segments.ts:218

ts
optional stack?: string;

Defined in: packages/core/src/types/segments.ts:223

Set by nothing and read by nothing: the error renderer draws content and details only. Put a stack in details to show it. It will be removed in a later minor.


Defined in: packages/core/src/types/segments.ts:238

Tool call segment - rendered while waiting for a tool handler to resolve

ts
{
 *   id: 's1',
 *   type: 'tool_call',
 *   status: 'resolved',
 *   toolCall: { id: 'call_1', name: 'get_weather', input: { city: 'Lille' } },
 *   result: '11°C, overcast.',
 * }
ts
id: string;

Defined in: packages/core/src/types/segments.ts:13

Unique segment identifier

AparteSegmentBase.id

ts
optional isStreaming?: boolean;

Defined in: packages/core/src/types/segments.ts:19

Whether segment is currently being streamed

AparteSegmentBase.isStreaming

ts
optional fallback?: string;

Defined in: packages/core/src/types/segments.ts:36

One sentence standing in for the segment when no renderer claims its type — DRAWN for every type, not only for custom.

It is the answer to a real moment: a conversation replayed in another app, a client that loads its views lazily, an export. Core draws it as text (no markup) under .aparte-segment-fallback. Supplying one also silences the “no renderer” developer warning: an author who wrote a fallback has already said this can happen.

In the HISTORY sent back to the model it stands in for the segment only where core does not serialise the type itself — custom, and any type core does not know (a registered block grammar’s, a plugin’s). A text or a code segment contributes its content and nothing else; thinking and tool_call are left out of the history on purpose (_segmentsToText in AparteClient states why), and so is error.

AparteSegmentBase.fallback

ts
optional messageId?: string;

Defined in: packages/core/src/types/segments.ts:55

Id of the message this segment belongs to. Stamped on insertion.

AparteSegmentBase.messageId

ts
optional index?: number;

Defined in: packages/core/src/types/segments.ts:61

Position in the owning message’s segments[], maintained across insertions and removals.

AparteSegmentBase.index

ts
optional meta?: Record<string, unknown> & {
  aparte?: AparteSegmentTiming;
};

Defined in: packages/core/src/types/segments.ts:71

Extras the producer of the segment knows — token counts, cost, compute device — plus aparte, the one sub-object core writes.

Everything except meta.aparte is yours: fill it with updateSegment(segmentId, { meta }), which MERGES rather than replaces. Mirrors AparteMessage.metadata.

ts
optional aparte?: AparteSegmentTiming;

AparteSegmentBase.meta

ts
type: "tool_call";

Defined in: packages/core/src/types/segments.ts:239

Segment type discriminator

AparteSegmentBase.type

ts
toolCall: AparteToolCall;

Defined in: packages/core/src/types/segments.ts:240

ts
status: 
  | "pending"
  | "resolved"
  | "aborted"
  | "awaiting-approval"
  | "rejected"
  | "failed";

Defined in: packages/core/src/types/segments.ts:246

awaiting-approval — paused for a human decision; rejected — the human (or a policy) declined to run it; aborted — stopped, timed out, or nothing could run it; failed — the handler threw, and result carries the crash’s one line.

ts
optional result?: string;

Defined in: packages/core/src/types/segments.ts:247

ts
optional structuredResult?: unknown;

Defined in: packages/core/src/types/segments.ts:249

The handler’s structuredContent, when it returned one — the value behind result’s prose.

ts
optional deferred?: {
  reason: "approval" | "external" | "queued";
  ticket?: string;
};

Defined in: packages/core/src/types/segments.ts:257

Set when a server loop (createAparteAgentHandler) suspended its run on this call: why it waits — a person’s decision (approval), a tool the page runs (external), or nothing but its turn (queued) — and the server’s signed continuation. Saved with the conversation, so a reload can still answer it (AparteClient.resumeDeferred).

ts
reason: "approval" | "external" | "queued";
ts
optional ticket?: string;

Defined in: packages/core/src/types/segments.ts:296

Custom segment - one escape hatch for the views an app draws itself.

Renderers are keyed on type, so subType never reaches the registry: you register ONE renderer for custom and switch on subType inside it, with fallback as the default branch. Independent views take their own type instead — register a chart renderer for a chart — and custom is for a family of small views an app would rather keep behind one key.

ts
registerSegmentRenderer<AparteCustomSegment>({
  type: 'custom',
  render(segment) {
    const data = (segment.data ?? {}) as Record<string, unknown>;
    const el = document.createElement('div');
    switch (segment.subType) {
      case 'weather':
        el.className = 'weather';
        el.textContent = `${data['city']} — ${data['celsius']}°C`;
        break;
      case 'poll':
        el.className = 'poll';
        el.textContent = String(data['question']);
        break;
      default:
        // A subType this build does not know: the sentence, not nothing.
        el.textContent = segment.fallback ?? '';
    }
    return el;
  },
});

// The segments that renderer draws:
{ id: 's1', type: 'custom', subType: 'weather', data: { city: 'Lille', celsius: 11 } }
{ id: 's2', type: 'custom', subType: 'gantt', fallback: 'Three tasks, ending Friday.' }
ts
id: string;

Defined in: packages/core/src/types/segments.ts:13

Unique segment identifier

AparteSegmentBase.id

ts
optional isStreaming?: boolean;

Defined in: packages/core/src/types/segments.ts:19

Whether segment is currently being streamed

AparteSegmentBase.isStreaming

ts
optional fallback?: string;

Defined in: packages/core/src/types/segments.ts:36

One sentence standing in for the segment when no renderer claims its type — DRAWN for every type, not only for custom.

It is the answer to a real moment: a conversation replayed in another app, a client that loads its views lazily, an export. Core draws it as text (no markup) under .aparte-segment-fallback. Supplying one also silences the “no renderer” developer warning: an author who wrote a fallback has already said this can happen.

In the HISTORY sent back to the model it stands in for the segment only where core does not serialise the type itself — custom, and any type core does not know (a registered block grammar’s, a plugin’s). A text or a code segment contributes its content and nothing else; thinking and tool_call are left out of the history on purpose (_segmentsToText in AparteClient states why), and so is error.

AparteSegmentBase.fallback

ts
optional messageId?: string;

Defined in: packages/core/src/types/segments.ts:55

Id of the message this segment belongs to. Stamped on insertion.

AparteSegmentBase.messageId

ts
optional index?: number;

Defined in: packages/core/src/types/segments.ts:61

Position in the owning message’s segments[], maintained across insertions and removals.

AparteSegmentBase.index

ts
optional meta?: Record<string, unknown> & {
  aparte?: AparteSegmentTiming;
};

Defined in: packages/core/src/types/segments.ts:71

Extras the producer of the segment knows — token counts, cost, compute device — plus aparte, the one sub-object core writes.

Everything except meta.aparte is yours: fill it with updateSegment(segmentId, { meta }), which MERGES rather than replaces. Mirrors AparteMessage.metadata.

ts
optional aparte?: AparteSegmentTiming;

AparteSegmentBase.meta

ts
type: "custom";

Defined in: packages/core/src/types/segments.ts:297

Segment type discriminator

AparteSegmentBase.type

ts
subType: string;

Defined in: packages/core/src/types/segments.ts:299

Which view inside the custom renderer draws this segment (e.g. ‘weather’, ‘poll’, ‘onboarding’).

ts
optional data?: unknown;

Defined in: packages/core/src/types/segments.ts:301

Arbitrary data payload for the component


Defined in: packages/core/src/types/segments.ts:333

Segment renderer interface for custom segment types Implement this to add new segment renderers

Type ParameterDefault type
T extends AparteSegmentBaseAparteSegmentBase
ts
type: string;

Defined in: packages/core/src/types/segments.ts:335

Segment type this renderer handles

ts
render(segment): string | HTMLElement;

Defined in: packages/core/src/types/segments.ts:351

Render a segment to either an HTML string or a ready DOM element.

Return a string for simple markup (inserted via innerHTML) — the built-in renderers do this. Return an HTMLElement to skip innerHTML entirely: you can attach event listeners and framework nodes directly, and there is no innerHTML XSS surface for values you interpolate. Either way, setup() (if provided) runs afterwards with the resulting element. For in-place streaming updates to find your element, keep a data-segment-id="<segment.id>" on its root (or implement update()).

ParameterTypeDescription
segmentTSegment data to render

string | HTMLElement

HTML string or a DOM element

ts
optional getStyles(): string;

Defined in: packages/core/src/types/segments.ts:357

Optional: Get CSS styles for this segment type

string

CSS string to inject

ts
optional setup(element, segment): void;

Defined in: packages/core/src/types/segments.ts:364

Optional: Setup interactivity after render

ParameterTypeDescription
elementHTMLElementThe rendered DOM element
segmentTSegment data

void

ts
optional update(element, segment): void;

Defined in: packages/core/src/types/segments.ts:372

Optional: In-place DOM update during streaming. When provided, called instead of re-rendering the full element. Must not add/remove child nodes — only mutate attributes or textContent so the MutationObserver (childList) doesn’t fire and scroll stays stable.

ParameterType
elementHTMLElement
segmentT

void

ts
optional relabel(element, segment): void;

Defined in: packages/core/src/types/segments.ts:396

Optional: re-read the CONFIG-DERIVED text of an already-rendered segment — an icon from the icon provider, a label from the locale.

Called when the config changes (a language switch, a new icon set), on every segment already on screen. Bound by the same rule as update(): must not add or remove child nodes — only attributes and textContent — so the viewport’s childList observer stays quiet and scroll does not move.

This exists because the obvious alternative does not work. Re-rendering the segments container to pick up a new locale destroys state the DOM owns and the segment data does not: a mounted sandboxed artifact preview, a reasoning block the reader expanded by clicking <summary> (which never writes back to collapsed), scroll position inside a long terminal, the focus on an Approve/Reject gate, and the buffered lookahead of the incremental Markdown parser mid-stream. It also fires container-wide childList mutations, which is exactly what the rule above forbids.

Implement it only if the rendered output contains text or icons that came from the config. A renderer whose chrome is entirely its own data — progress, text — correctly does not, exactly as it does not implement update().

ParameterType
elementHTMLElement
segmentT

void

ts
optional cleanup(element): void;

Defined in: packages/core/src/types/segments.ts:402

Optional: Cleanup when segment is removed

ParameterTypeDescription
elementHTMLElementThe DOM element being removed

void


Defined in: packages/core/src/types/segments.ts:410

Event when segment content is updated during streaming

ts
messageId: string;

Defined in: packages/core/src/types/segments.ts:411

ts
segmentId: string;

Defined in: packages/core/src/types/segments.ts:412

ts
content: string;

Defined in: packages/core/src/types/segments.ts:413

ts
optional append?: boolean;

Defined in: packages/core/src/types/segments.ts:414


Defined in: packages/core/src/types/stream-blocks.ts:24

What the parser hands to AparteStreamBlock.toSegment when a block opens.

ts
attrs: Record<string, string>;

Defined in: packages/core/src/types/stream-blocks.ts:30

The opening tag’s attributes, as written: type="text/html", title='Notes' and lang=fr all parse; the values are verbatim, never trimmed or decoded. The model wrote them — treat them as untrusted, like every segment field.

ts
id: string;

Defined in: packages/core/src/types/stream-blocks.ts:32

The id the parser generated for the segment (its usual prefix + uuid).


Defined in: packages/core/src/types/stream-blocks.ts:45

A block grammar: the tag, and the segment its body becomes.

The parser owns everything after toSegment returns: it appends each delta to segment.content, sets isStreaming while the block is open and clears it at </tag> (or at the end of the stream). So the segment MUST carry a content string — the one field core touches — and the rest of its shape is yours: a type of your own, registered with registerSegmentRenderer, or a built-in one if the block is merely another way of spelling it.

ts
tag: string;

Defined in: packages/core/src/types/stream-blocks.ts:51

The tag name without brackets: artifact recognises <artifact …> … </artifact>. Matched exactly and case-sensitively, and only as a whole name — <artifacts> does not open an artifact block. Letters, digits, - and _.

ts
toSegment: (match) => AparteSegmentBase & {
  content: string;
};

Defined in: packages/core/src/types/stream-blocks.ts:56

Build the segment at the opening tag, with an empty content. Called once per block; the parser fills the body in afterwards.

ParameterType
matchAparteStreamBlockMatch

AparteSegmentBase & { content: string; }


Defined in: packages/core/src/types/tools.ts:9

Definition of a tool the AI can call

ts
name: string;

Defined in: packages/core/src/types/tools.ts:10

ts
description: string;

Defined in: packages/core/src/types/tools.ts:11

ts
inputSchema: Record<string, unknown>;

Defined in: packages/core/src/types/tools.ts:13

JSON Schema object describing the tool’s input parameters

ts
optional systemPrompt?: string;

Defined in: packages/core/src/types/tools.ts:15

System prompt injected automatically when this tool is registered — tells the AI when and why to use it

ts
optional maxTurns?: number;

Defined in: packages/core/src/types/tools.ts:20

Maximum number of agentic loop turns before aborting. Overrides the global maxTurns in AparteClientOptions for this specific tool.

ts
optional needsApproval?: boolean;

Defined in: packages/core/src/types/tools.ts:39

When true, the agent loop pauses before running this tool’s handler and waits for a human decision — “human in the loop”.

The decision is asked AT THE COMPOSER, through the same requestUserInput a tool handler calls: every request for the user is answered in one place. The row in the transcript is the anchor, naming which tool is waiting, and holds nothing clickable. An approvalResolver on AparteClientOptions answers it programmatically instead.

On a refusal the rest of the turn is skipped — no later tool call of the same turn runs — and then the model gets a turn to answer it, so it can say what it will do instead. The refusal may carry the user’s own words.

All three clauses of this comment used to say something else: the default renderer surfaced the buttons, an aparte-tool-decision event resolved them, and a refusal ended the turn. None of that is true any more.


Defined in: packages/core/src/types/tools.ts:65

A tool call emitted by the AI during streaming

ts
id: string;

Defined in: packages/core/src/types/tools.ts:66

ts
name: string;

Defined in: packages/core/src/types/tools.ts:67

ts
input: Record<string, unknown>;

Defined in: packages/core/src/types/tools.ts:68


Defined in: packages/core/src/types/tools.ts:79

What an approval policy says about one call.

allow runs it without asking; ask puts it to the person the way a needsApproval tool is put to them; deny refuses it on the spot, and reason is the sentence the model reads about that refusal — verbatim, because nobody said anything (“Plan mode: write_file changes files; only read-only tools run.”).

ts
verdict: "allow" | "ask" | "deny";

Defined in: packages/core/src/types/tools.ts:80

ts
optional reason?: string;

Defined in: packages/core/src/types/tools.ts:81


Defined in: packages/core/src/types/tools.ts:103

Result returned after a tool handler resolves

ts
toolCallId: string;

Defined in: packages/core/src/types/tools.ts:104

ts
content: string;

Defined in: packages/core/src/types/tools.ts:106

What the model reads.

ts
optional structuredContent?: unknown;

Defined in: packages/core/src/types/tools.ts:113

The same result as a value, for a renderer or the host — MCP’s name for exactly this field, beside content. Optional and additive: a handler that returns prose alone behaves as before, and the model never sees this. It lands on the transcript’s segment as AparteToolCallSegment.structuredResult.


Defined in: packages/core/src/types/tools.ts:147

Which chat a tool handler is running for.

Optional and third, so every existing handler keeps compiling and working — nothing has to be rewritten to ignore it.

It exists because a handler had NO way to know. @aparte/plugin-ask-user calls requestUserInput({ message, schema, signal }) with no target, and requestUserInput resolves its presenter with resolveConfig(request.target ?? null) — so with an instance config carrying the presenter, the call resolved against the GLOBAL config, found nothing, and returned { action: 'cancel' }. The model was told the user refused a question the user was never shown.

That failure is described in AparteConfig.requestUserInput as “a lie told quietly”, and the plugin walked straight into it because the handler signature gave it nothing to walk around with.

The loop supplies it — runStreamAgent, or the runner you injected — so a handler behaves the same whichever one is running.

ts
optional request?: Request;

Defined in: packages/core/src/types/tools.ts:153

On the server (createAparteAgentHandler): the request being served — the one that started the run, or, for a call answered after a pause, the one that resumed it — for a tool that checks who is calling. The model chose the arguments, not the caller.

ts
optional target?: HTMLElement;

Defined in: packages/core/src/types/tools.ts:159

The chat element this turn belongs to. Pass it as target to requestUserInput (or to anything else that resolves a config from the DOM) and the right instance answers.

ts
optional config?: unknown;

Defined in: packages/core/src/types/tools.ts:164

The resolved config for this chat, for a handler that needs it without going through the DOM — a handler running in a worker, or one reading a provider.


Defined in: packages/core/src/types/tools.ts:190

Per-tool segment renderer. Registered via aparteGlobalConfig.registerToolRenderer(toolName, renderer). When the AI calls a tool, this renderer controls what appears in the bubble for that specific tool instead of the generic tool_call renderer.

Return an empty string from render() to render nothing (e.g. for UI-only tools like ask_user).

ts
render: (segment) => string | HTMLElement;

Defined in: packages/core/src/types/tools.ts:221

Render the tool-call segment, as an HTML string or a ready DOM element.

The segment carries model-chosen data. segment.toolCall.input is whatever the model decided to pass, and segment.result is whatever the tool returned — on the SEGMENT, because AparteToolCall is { id, name, input } and nothing else. This line used to say segment.toolCall.result, which does not compile; the first renderer written against it found out. Both values are untrusted.

Return an HTMLElement and there is no innerHTML surface at all — set textContent, attach listeners, insert framework nodes. That is the safe default and the reason this arm exists.

Return a string and core inserts it with innerHTML, so every interpolated value must go through escapeHtml (text position) or escapeAttr (inside an attribute). The natural first thing to write here is `<div>Searching for ${s.toolCall.input.query}</div>`, and that is a direct model-to-DOM XSS in the host page’s origin.

Return an empty string to render nothing (e.g. a UI-only tool like ask_user).

The root you return is the segment’s element in the bubble: core stamps data-segment-id="<segment.id>" on it if you did not, so an update to the call (its result landing, an approval decision) is patched into that element rather than rebuilding every segment of the bubble — a rebuild destroys a mounted artifact preview, collapses an opened reasoning block and drops the focus. Keep your markup’s root a single element for that reason.

ParameterType
segmentAparteToolCallSegment

string | HTMLElement

ts
optional setup?: (element, segment) => void;

Defined in: packages/core/src/types/tools.ts:223

Optional DOM setup (event listeners etc.) called after HTML is injected

ParameterType
elementHTMLElement
segmentAparteToolCallSegment

void

ts
optional update?: (element, segment) => void;

Defined in: packages/core/src/types/tools.ts:232

Patch the element in place when the call changes — its result landing, an approval decided, a failed status. Without it core rebuilds your markup from render() on every change, which is right for a receipt and wrong for anything with state: a mounted preview, an opened disclosure, a focused control are all lost in a rebuild. Same contract as AparteSegmentRenderer.update: never re-render, never touch what the reader opened.

ParameterType
elementHTMLElement
segmentAparteToolCallSegment

void

ts
optional relabel?: (element, segment) => void;

Defined in: packages/core/src/types/tools.ts:239

Re-read the locale and the icon provider without touching the DOM shape — called on every config change (setLocale, setIconProvider, reset()), for as long as the element is on the page. Add or remove no child node; replace text and glyphs only.

ParameterType
elementHTMLElement
segmentAparteToolCallSegment

void

ts
optional getStyles?: () => string;

Defined in: packages/core/src/types/tools.ts:241

Optional CSS to inject once into document.head

string

ts
type AparteContentPart = 
  | AparteTextPart
  | AparteImagePart;

Defined in: packages/core/src/types/chat.ts:38

Discriminated union of all content part types.

There was a third, AparteFilePart, “reserved for future PDF/audio support” — nothing ever produced one: the client inlines images and text files and drops the rest, and both wire mappers answered it with an empty text part no message could reach. What it did do was promise a consumer that attaching a PDF sent it. That promise is now a warning at the one place the file is really dropped.


ts
type AparteStreamEvent = { [K in keyof AparteStreamEventMap]: { type: K } & AparteStreamEventMap[K] }[keyof AparteStreamEventMap];

Defined in: packages/core/src/types/chat.ts:214

Discriminated union derived from AparteStreamEventMap — stays in sync automatically.


ts
type AparteChatResponse = ReadableStream<AparteStreamEvent> | string;

Defined in: packages/core/src/types/chat.ts:218


ts
type AparteAttrValue<T> = T extends boolean ? "" | null | undefined : T extends number ? number | string : T;

Defined in: packages/core/src/types/element-attributes.ts:44

One attribute as a TEMPLATE must write it.

A presence attribute becomes '' | undefined, never boolean, and that is not pedantry — it is the same trap in all three template languages. React and Vue stringify what they set on a custom element, so disabled={false} renders disabled="false", and an element that tests hasAttribute reads that as ON. The wrapper’s own bubble rendering already used the right spelling (streaming={… ? '' : undefined}); this makes it the only one that type-checks.

Svelte 5 is the caveat that shaped the SETTERS (#62): it assigns the PROPERTY whenever the element has a setter for it (set_custom_element_data checks get_setters(node)), so an accessor-backed presence attribute receives the '' this union promises as a property value. Every presence setter therefore treats '' as ON (presenceOn in utils/presence.ts) — one spelling, both paths.

null is in the union alongside undefined because all three treat it as REMOVE, and Vue’s own wrapper template writes exactly that (:streaming="… ? '' : null"). Leaving it out made the wrapper’s own code fail to type-check — the second time the existing wrappers corrected a declaration here, after timestamp, which the React bubble rendering proved accepts a number.

A numeric attribute takes a number or the string it becomes, because both read naturally in a template.

Angular does NOT use this: its directives take boolean through booleanAttribute and write the attribute themselves, so a consumer there writes [disabled]="busy". That difference is the whole reason this is a mapping and not the declared type.

Type Parameter
T

ts
type AparteTemplateAttrs<T> = { [K in keyof T]?: AparteAttrValue<NonNullable<T[K]>> };

Defined in: packages/core/src/types/element-attributes.ts:49

An element’s attribute surface, in template spelling. Used by three of four wrappers.

Type Parameter
T

ts
type ModelStatus = "ready" | "cached" | "not-downloaded";

Defined in: packages/core/src/types/model-provider.ts:66

Availability status of a model, as reported by the provider.

Provider-agnostic: Transformers.js checks the Cache API, Ollama checks pulled models, LM Studio checks VRAM, etc.


ts
type AparteAIProvider = 
  | AparteAIProviderCore & AparteAIProviderFormat & AparteAIProviderAuth & Partial<AparteAIProviderChat>
  | AparteAIProviderCore & AparteAIProviderChat & Partial<AparteAIProviderFormat> & AparteAIProviderAuthMembers;

Defined in: packages/core/src/types/model-provider.ts:310

An AI provider (e.g. OpenRouter, OpenAI, Transformers.js).

A union, not one interface, because there are two mutually sufficient ways to be a provider and the compiler should say which one you implemented. It used to be a single interface with three required members and fifteen optional ones, discriminated at RUNTIME by isFormatAdapter() — so { id, getMetadata, getModels } typechecked, registered, and then failed on the first message with nothing at build time to warn you.

Every member of both surfaces stays reachable on the union (optional on the arm that does not require it), so runtime probes like typeof p.buildRequest === 'function' and isFormatAdapter() narrowing keep working unchanged. A provider implementing both surfaces is valid and satisfies the format arm.

A format adapter — a transport does the call

typescript
const provider: AparteAIProvider = {
  id: 'openrouter',
  getMetadata: () => ({ name: 'OpenRouter', id: 'openrouter', icon: '<svg/>', color: '#000' }),
  getModels: () => [],
  defaultEndpoint: 'https://openrouter.ai/api/v1',
  buildRequest: (req) => ({ path: '/chat/completions', body: req }),
  authHeaders: (key) => ({ Authorization: `Bearer ${key}` }),
  parseStream: (body) => body as unknown as ReadableStream<AparteStreamEvent>,
};

A provider owning its own I/O

typescript
const local: AparteAIProvider = {
  id: 'transformers',
  getMetadata: () => ({ name: 'Transformers.js', id: 'transformers', icon: '<svg/>', color: '#000' }),
  getModels: () => [],
  chat: async (request) => runLocally(request),
};

ts
type AparteStatus = "idle" | "pending" | "streaming" | "completed" | "error" | "success";

Defined in: packages/core/src/types/models.ts:17

Generic status type for flow management Used to drive loading indicators, error states, and transitions without coupling to any specific data source


ts
type AparteBubbleRole = "user" | "assistant";

Defined in: packages/core/src/types/models.ts:24

Role of a message sender in the conversation


ts
type AparteBubbleActionName = "copy" | "edit" | "retry" | "thumbUp" | "thumbDown" | "info";

Defined in: packages/core/src/types/models.ts:103

Names of the individual action-bar buttons, used for explicit per-role ordered configuration (see AparteBubbleActionsConfig.user / .assistant).


ts
type AparteSegmentDefaults = Readonly<Record<string, unknown>>;

Defined in: packages/core/src/types/segments.ts:134

Field defaults for one segment TYPE, merged when a segment enters a message.

A record and not a Partial<AparteSegment> on purpose: the type key is a string, a consumer’s own segment type is as valid as a built-in one, and its fields cannot be known here. What CANNOT be defaulted is identity — id, type, messageId, index, startedAt, endedAt are refused, because a default id would give every segment in a conversation the same one.


ts
type AparteSegment = 
  | AparteTextSegment
  | AparteThinkingSegment
  | AparteCodeSegment
  | AparteErrorSegment
  | AparteCustomSegment
  | AparteToolCallSegment;

Defined in: packages/core/src/types/segments.ts:314

All built-in segment types. An artifact is not one of them any more: it is a convention an app teaches its model, and @aparte/plugin-artifacts registers its segment — a type of its own, through registerSegmentRenderer and registerStreamBlock — the way any consumer registers a chart or a citation.


ts
type AparteSegmentType = AparteSegment["type"];

Defined in: packages/core/src/types/segments.ts:323

Segment type discriminator values


ts
type AparteThemeVariables = { [K in `--aparte-${string}`]?: string };

Defined in: packages/core/src/types/theming.ts:34

Any aparté CSS custom property, for typing a theme object or a style record:

ts
const brand: AparteThemeVariables = {
  '--aparte-primary': '#b45309',
  '--aparte-surface-1': '#ffffff',
};

This was a hand-written list of 33 named properties, and it could not work. Ten of the 33 named variables were neither declared nor read anywhere in aparté — so the type autocompleted ten knobs that did nothing — while the real surface is 254 tokens, meaning even the 23 live members were an arbitrary slice presented as the whole. A hand-maintained mirror of a stylesheet that size is wrong the day after it is written, and nothing could tell you.

What this trades away, stated rather than glossed: autocomplete, and catching a typo in the part after --aparte-. What it buys is that the type cannot lie. The discoverable list is the generated CSS variables reference, which is swept from the source on every build and marks which tokens aparté actually reads.


ts
type AparteApprovalPolicy = (call, tool) => AparteApprovalRuling | undefined;

Defined in: packages/core/src/types/tools.ts:97

Decides, per CALL, whether a tool runs, asks, or is refused.

A tool’s own needsApproval is a declaration about the tool; a policy is a decision about the call — the same run_command can be a read (ls) or an execution, and a mode (“plan”: read-only, “auto”: never ask) is a policy the host switches, not a flag on twenty definitions. Registered with setApprovalPolicy(); the client’s default approval channel consults it before asking. Return undefined to have no opinion, in which case the tool’s needsApproval decides as before.

Pure and synchronous: it is evaluated once to decide whether the call pauses at all (so an allowed call never flashes “awaiting approval”) and once to answer.

ParameterType
callAparteToolCall
toolAparteTool | undefined

AparteApprovalRuling | undefined


ts
type AparteToolHandler = (call, signal, context?) => Promise<AparteToolResult>;

Defined in: packages/core/src/types/tools.ts:121

Handler function for a registered tool. Receives the tool call and an AbortSignal (fires after timeout or on cancellation). Must resolve with a AparteToolResult.

ParameterType
callAparteToolCall
signalAbortSignal
context?AparteToolContext

Promise<AparteToolResult>


ts
type AparteToolSummary = (segment) => string;

Defined in: packages/core/src/types/tools.ts:180

What a call targets, in one line: the text the DEFAULT tool row shows after the tool’s name — read_file skills.json rather than read_file alone. Registered via aparteGlobalConfig.registerToolSummary(toolName, summary).

The name says which tool ran; this says to what, without opening the disclosure. The row, its states and its disclosure stay core’s, so a host that only wants that one line does not re-implement them in an AparteToolRenderer.

Return plain text: it is written as text, never parsed as HTML. Return '' to show nothing. segment.toolCall.input is whatever the model chose, so read it defensively — a summary that throws shows nothing rather than breaking the row.

ParameterType
segmentAparteToolCallSegment

string

Defined in: packages/core/src/types/errors.ts:2

ts
CONFIG_NO_PROVIDER: "CONFIG_NO_PROVIDER";

Defined in: packages/core/src/types/errors.ts:4

ts
CONFIG_MISSING_KEY: "CONFIG_MISSING_KEY";

Defined in: packages/core/src/types/errors.ts:5

ts
CONFIG_INVALID_KEY: "CONFIG_INVALID_KEY";

Defined in: packages/core/src/types/errors.ts:6

ts
CONFIG_INVALID_MODEL: "CONFIG_INVALID_MODEL";

Defined in: packages/core/src/types/errors.ts:7

ts
USAGE_RATE_LIMIT: "USAGE_RATE_LIMIT";

Defined in: packages/core/src/types/errors.ts:10

ts
USAGE_CONTEXT_EXCEEDED: "USAGE_CONTEXT_EXCEEDED";

Defined in: packages/core/src/types/errors.ts:11

ts
USAGE_BAD_REQUEST: "USAGE_BAD_REQUEST";

Defined in: packages/core/src/types/errors.ts:12

ts
NET_OFFLINE: "NET_OFFLINE";

Defined in: packages/core/src/types/errors.ts:15

ts
NET_TIMEOUT: "NET_TIMEOUT";

Defined in: packages/core/src/types/errors.ts:16

ts
NET_ERROR: "NET_ERROR";

Defined in: packages/core/src/types/errors.ts:17

ts
PROVIDER_ERROR: "PROVIDER_ERROR";

Defined in: packages/core/src/types/errors.ts:20

ts
PROVIDER_UNAVAILABLE: "PROVIDER_UNAVAILABLE";

Defined in: packages/core/src/types/errors.ts:21

ts
PROVIDER_POLICY: "PROVIDER_POLICY";

Defined in: packages/core/src/types/errors.ts:22

ts
UNKNOWN_ERROR: "UNKNOWN_ERROR";

Defined in: packages/core/src/types/errors.ts:25

Defined in: packages/core/src/types/events.ts:66

Detail payload for aparte-message-done. Dispatched when a turn finishes normally. Carries token usage when the provider reports it.

aparte-message-done

ts
optional targetId?: string;

Defined in: packages/core/src/types/events.ts:75

Id of the <aparte-chat> host that produced this turn, when it has one.

Stamped by the client’s lifecycle dispatcher on EVERY lifecycle event so several chats on one page stay isolated — a composer reacts only to its own host’s turn. It was undeclared here while being sent at runtime, and the dispatcher takes Record<string, unknown>, so the compiler could not notice.

ts
messageId: string;

Defined in: packages/core/src/types/events.ts:76

ts
role: string;

Defined in: packages/core/src/types/events.ts:77

ts
optional usage?: AparteUsage;

Defined in: packages/core/src/types/events.ts:79

Token usage for the completed response, if reported by the provider.


Defined in: packages/core/src/types/events.ts:97

Detail payload for aparte-model-change. Dispatched by @aparte/plugin-model-selector when the user picks a different provider or model — the clearest cross-package contract in the set.

Its @event tag used to sit above AparteMessageDoneEventDetail’s doc block instead, so this interface had no documentation of its own and the other one claimed to describe a different event.

aparte-model-change

ts
optional providerId?: string;

Defined in: packages/core/src/types/events.ts:99

Selected provider ID (e.g., ‘openrouter’, ‘gemini’)

ts
modelId: string;

Defined in: packages/core/src/types/events.ts:102

Selected model ID (e.g., ‘gpt-4-turbo’, ‘claude-3-opus’)

ts
optional previousModelId?: string;

Defined in: packages/core/src/types/events.ts:105

Previous model ID if changed

ts
optional previousProviderId?: string;

Defined in: packages/core/src/types/events.ts:108

Previous provider ID if changed


Defined in: packages/core/src/types/events.ts:138

Detail payload for aparte-link-click. Dispatched by the bubble, cancelable, when a link in a message body is about to be followed — a link the MODEL wrote. preventDefault() on the event keeps the browser from navigating, so a host routes the link itself (an external browser, a confirmation, an embedded view) without intercepting the DOM. With no listener the browser follows it; the sanitizer has made an external URL open in a new tab.

aparte-link-click

ts
href: string;

Defined in: packages/core/src/types/events.ts:140

The href as written on the anchor, after sanitization.

ts
anchor: HTMLAnchorElement;

Defined in: packages/core/src/types/events.ts:142

The anchor itself, for a host that wants its text or its rel.

ts
messageId: string | null;

Defined in: packages/core/src/types/events.ts:144

The message the link sits in, or null for a bubble mounted without one.


Defined in: packages/core/src/types/events.ts:154

Detail payload for aparte-branch-navigate. Dispatched by the bubble’s branch-picker buttons; the viewport listens (bubbling) and calls navigateBranch(messageId, direction) on its repo.

aparte-branch-navigate

ts
messageId: string;

Defined in: packages/core/src/types/events.ts:156

Message id whose siblings should be navigated

ts
direction: "prev" | "next";

Defined in: packages/core/src/types/events.ts:158

‘prev’ = move to previous sibling, ‘next’ = move to next sibling


Defined in: packages/core/src/types/events.ts:169

Detail payload for aparte-path-changed. Dispatched by the viewport in framework-managed mode after the active path of the conversation tree changes (sibling switch, retry, edit, etc.). Consumers (Angular wrapper) reconcile their signal/state from this payload.

aparte-path-changed

ts
messages: AparteMessage[];

Defined in: packages/core/src/types/events.ts:171

Messages on the new active path, root → head.

ts
siblings: AparteSiblingInfo[];

Defined in: packages/core/src/types/events.ts:173

Sibling metadata, one entry per message on the path.


Defined in: packages/core/src/types/events.ts:184

Detail payload for aparte-retry. Dispatched by the assistant bubble’s “Retry” action. Picked up by the AparteClient, which calls addSiblingOf on the target viewport and streams a fresh response into the new sibling.

aparte-retry

ts
messageId: string;

Defined in: packages/core/src/types/events.ts:186

Message id of the assistant response being retried (its sibling will be created).

ts
optional targetId?: string;

Defined in: packages/core/src/types/events.ts:188

Optional id of the host element (aparte-chat) — used by scopeToTargetId.


Defined in: packages/core/src/types/events.ts:199

Detail payload for aparte-edit. Dispatched by the user bubble after the user confirms an inline edit. Picked up by the AparteClient, which truncates the existing responses to this message and re-streams a new one.

aparte-edit

ts
messageId: string;

Defined in: packages/core/src/types/events.ts:201

Message id of the user message being edited.

ts
content: string;

Defined in: packages/core/src/types/events.ts:203

New content submitted by the user.

ts
optional targetId?: string;

Defined in: packages/core/src/types/events.ts:205

Optional id of the host element (aparte-chat) — used by scopeToTargetId.


Defined in: packages/core/src/types/events.ts:214

Detail payload for aparte-feedback. Dispatched by the assistant bubble’s thumbs-up / thumbs-down buttons.

aparte-feedback

ts
messageId: string;

Defined in: packages/core/src/types/events.ts:216

Message id receiving the feedback.

ts
value: "positive" | "negative";

Defined in: packages/core/src/types/events.ts:218

‘positive’ for thumbs-up, ‘negative’ for thumbs-down.


Defined in: packages/core/src/types/events.ts:230

Detail payload for aparte-action. Dispatched by a custom action button (registered via aparteGlobalConfig.registerAction) in a message-bubble toolbar. Apps listen (bubbling) and dispatch on actionId. Mirrors the built-in bubble events (retry/feedback) so custom actions are wired the same way in every framework and in vanilla.

aparte-action

ts
actionId: string;

Defined in: packages/core/src/types/events.ts:232

The registered action’s id (from AparteAction.id).

ts
zone: "bubble";

Defined in: packages/core/src/types/events.ts:234

Which zone the action was clicked in.

ts
optional messageId?: string;

Defined in: packages/core/src/types/events.ts:236

Message id of the bubble the action was clicked on.

ts
optional role?: "user" | "assistant";

Defined in: packages/core/src/types/events.ts:238

Role of that bubble.

ts
optional targetId?: string;

Defined in: packages/core/src/types/events.ts:240

Optional host element id (aparte-chat) — same use as retry/edit’s targetId.


Defined in: packages/core/src/types/events.ts:250

Detail payload for aparte-message-info. Dispatched by the assistant bubble’s info (“i”) action button. Apps listen (bubbling) and present a stats popover for the completed response.

aparte-message-info

ts
messageId: string;

Defined in: packages/core/src/types/events.ts:252

Message id whose stats should be shown.

ts
optional usage?: AparteUsage;

Defined in: packages/core/src/types/events.ts:254

Token usage + timing for the message, when available.


Defined in: packages/core/src/types/events.ts:277

Detail payload for aparte-message-start. Dispatched when a turn begins, before the first token. The composer and the conversation controller both use it to enter their streaming state.

aparte-message-start

ts
optional targetId?: string;

Defined in: packages/core/src/types/events.ts:279

Host element id, when the <aparte-chat> has one.

ts
messageId: string;

Defined in: packages/core/src/types/events.ts:281

Id of the assistant message about to stream.

ts
role: string;

Defined in: packages/core/src/types/events.ts:283

Role of the message being created — 'assistant' in every current path.


Defined in: packages/core/src/types/events.ts:293

Detail payload for aparte-message-error. Dispatched when a turn fails. Four core components listen for it, and it is the event guides/troubleshooting.md tells you to bind to surface a failure.

aparte-message-error

ts
optional targetId?: string;

Defined in: packages/core/src/types/events.ts:295

Host element id, when the <aparte-chat> has one.

ts
messageId: string;

Defined in: packages/core/src/types/events.ts:297

Id of the message that failed.

ts
error: AparteError;

Defined in: packages/core/src/types/events.ts:299

The failure, with its code — not a string, unlike aparte-compact-error.


Defined in: packages/core/src/types/events.ts:312

Detail payload for aparte-message-paused. Dispatched when a server loop pauses the turn on calls only the page can answer — an approval, a browser tool. The turn is not over (aparte-message-done follows the resume), but the reply is worth saving now: its paused calls carry the server’s ticket, and a conversation saved here can still be answered after a reload. The conversation manager persists on it.

aparte-message-paused

ts
optional targetId?: string;

Defined in: packages/core/src/types/events.ts:314

Host element id, when the <aparte-chat> has one.

ts
messageId: string;

Defined in: packages/core/src/types/events.ts:316

Id of the paused reply.


Defined in: packages/core/src/types/events.ts:325

Detail payload for aparte-message-aborted. Dispatched when the user stops a turn.

aparte-message-aborted

ts
optional targetId?: string;

Defined in: packages/core/src/types/events.ts:327

Host element id, when the <aparte-chat> has one.

ts
optional messageId?: string;

Defined in: packages/core/src/types/events.ts:333

Id of the aborted message — OPTIONAL, and that is not laziness: the composer dispatches this event on its own with only a targetId, because a user can press Stop during the pre-flight window before any message id exists.


Defined in: packages/core/src/types/events.ts:345

Detail payload for aparte-abort — a COMMAND, not a notification.

AparteClient listens for it on window; a bring-your-own-composer consumer dispatches it to stop a stream. Nothing in core dispatches it on the consumer’s behalf, which is exactly why its shape had to be documented somewhere.

aparte-abort

ts
optional targetId?: string;

Defined in: packages/core/src/types/events.ts:347

Which chat to stop. Omit on a single-chat page.


Defined in: packages/core/src/types/events.ts:360

Detail payload for aparte-compact — a COMMAND dispatched on window to ask for the conversation so far to be summarised.

<aparte-context auto-compact> dispatches it on reaching danger, and a host dispatches it from a button; @aparte/plugin-compaction is what answers it. Core itself neither listens for it nor compacts — the events below are the plugin’s.

aparte-compact

ts
optional targetId?: string;

Defined in: packages/core/src/types/events.ts:362

Which chat to compact. Omit on a single-chat page.


Defined in: packages/core/src/types/events.ts:372

Detail payload for aparte-compact-start — the summarisation began. The point for a host to show a spinner: summarising a long conversation is a model call and takes as long as one.

aparte-compact-start

ts
optional targetId?: string;

Defined in: packages/core/src/types/events.ts:374

The chat being compacted, when it has an id.


Defined in: packages/core/src/types/events.ts:386

Detail payload for aparte-compact-done.

A union flattened to optional fields, because the two outcomes carry different payloads and a consumer cannot guess which: nothing to compact sends { skipped: true, reason }, a real compaction sends { summary, kept, dropped }.

aparte-compact-done

ts
optional skipped?: boolean;

Defined in: packages/core/src/types/events.ts:388

true when there was nothing worth summarising; the other two are absent.

ts
optional reason?: "streaming" | "empty" | "nothing-to-drop" | "running";

Defined in: packages/core/src/types/events.ts:394

Why it was skipped: empty (no messages), nothing-to-drop (the selector kept everything), running (a compaction was already in flight), streaming (a turn is in flight in the transcript).

ts
optional summary?: string;

Defined in: packages/core/src/types/events.ts:396

The summary that replaced the compacted turns.

ts
optional kept?: number;

Defined in: packages/core/src/types/events.ts:398

How many messages were kept verbatim after the summary.

ts
optional dropped?: number;

Defined in: packages/core/src/types/events.ts:400

How many messages the summary replaced.

ts
optional targetId?: string;

Defined in: packages/core/src/types/events.ts:402

The chat that was compacted, when it has an id — so a gauge on a multi-chat page resets only its own.


Defined in: packages/core/src/types/events.ts:413

Detail payload for aparte-compact-error.

Note the asymmetry with aparte-message-error, which is deliberate rather than hidden: error here is a plain string, not an AparteError.

aparte-compact-error

ts
error: string;

Defined in: packages/core/src/types/events.ts:415

Human-readable failure reason.

ts
optional targetId?: string;

Defined in: packages/core/src/types/events.ts:417

The chat the compaction was for, when it has an id.


Defined in: packages/core/src/types/events.ts:432

Detail payload for aparte-attachment-preview. Dispatched when a user clicks an image tile, once setHostHandlers({ attachmentPreview: true }) has declared that you will open a lightbox. Without the handler the tile is not interactive at all.

aparte-attachment-preview

ts
url: string;

Defined in: packages/core/src/types/events.ts:434

Object URL or remote URL of the image.

ts
name: string;

Defined in: packages/core/src/types/events.ts:436

File name, or '' when the attachment carries none.


Defined in: packages/core/src/types/events.ts:451

Detail for aparte-approval-mode-change — dispatched by @aparte/plugin-approval’s <aparte-approval-mode> after the person switched the approval mode. Bubbles and crosses shadow roots, so a host can persist the choice from any ancestor.

Declared here, like aparte-model-change, because a listener in any framework reads e.detail through the typed event map. The values are the plugin’s four modes (plan, ask, auto-edit, auto); typed as strings so core names none of them.

aparte-approval-mode-change

ts
mode: string;

Defined in: packages/core/src/types/events.ts:453

The mode just switched to.

ts
previousMode: string;

Defined in: packages/core/src/types/events.ts:455

The mode it replaced.


Defined in: packages/core/src/types/tools.ts:58

Detail for aparte-tool-approval-request — emitted by the loop when a tool marked needsApproval is about to run.

Listen for it to present the decision yourself. Core’s own gate asks at the COMPOSER (<aparte-elicitation> renders the panel there), and the transcript row shows only a state — there is no Approve/Reject control in a bubble.

This block used to say “the built-in renderer already shows Approve/Reject inline”, which stopped being true when the decision moved to the composer. A consumer trusting it shipped with no presenter and no approvalResolver, and every needsApproval call aborted with nothing clickable on screen. The sibling docblock on needsApproval records the same correction; this one was missed in the same file.

aparte-tool-approval-request

ts
toolCallId: string;

Defined in: packages/core/src/types/tools.ts:59

ts
toolName: string;

Defined in: packages/core/src/types/tools.ts:60

ts
input: Record<string, unknown>;

Defined in: packages/core/src/types/tools.ts:61