@aparte/engine — API reference
Interfaces
Section titled “Interfaces”StreamUsage
Section titled “StreamUsage”Defined in: packages/engine/src/agent/stream-events.ts:34
Token usage. Structurally a superset-compatible mirror of AparteUsage: the
five common fields plus an index signature that carries the provider-specific
rest (ttft/decode/phases/…) opaquely — the loop transports usage, never reads
past these five.
Properties
Section titled “Properties”inputTokens
Section titled “inputTokens”inputTokens: number;Defined in: packages/engine/src/agent/stream-events.ts:35
outputTokens
Section titled “outputTokens”outputTokens: number;Defined in: packages/engine/src/agent/stream-events.ts:36
totalTokens?
Section titled “totalTokens?”optional totalTokens?: number;Defined in: packages/engine/src/agent/stream-events.ts:37
cacheReadTokens?
Section titled “cacheReadTokens?”optional cacheReadTokens?: number;Defined in: packages/engine/src/agent/stream-events.ts:38
durationMs?
Section titled “durationMs?”optional durationMs?: number;Defined in: packages/engine/src/agent/stream-events.ts:39
StreamToolCall
Section titled “StreamToolCall”Defined in: packages/engine/src/agent/stream-events.ts:57
One tool call as surfaced by the provider stream (mirrors AparteToolCall).
Properties
Section titled “Properties”id: string;Defined in: packages/engine/src/agent/stream-events.ts:58
name: string;Defined in: packages/engine/src/agent/stream-events.ts:59
input: Record<string, unknown>;Defined in: packages/engine/src/agent/stream-events.ts:68
Record<string, unknown>, mirroring core exactly, not unknown.
unknown looked safer — the loop does not read tool input, it forwards it —
but the seam needs assignability in BOTH directions (transportCall is
contravariant), and unknown is not assignable to Record<string, unknown>.
Core’s composition compiles against it.
StreamTool
Section titled “StreamTool”Defined in: packages/engine/src/agent/stream-events.ts:90
A tool declaration, structurally mirroring core’s AparteTool.
The loop forwards the inventory to the transport and reads maxTurns and
needsApproval from its own lookup — it never interprets the schema. Mirrored
exactly all the same, because the seam needs assignability in both directions;
core’s composition compiles against it.
Properties
Section titled “Properties”name: string;Defined in: packages/engine/src/agent/stream-events.ts:91
description
Section titled “description”description: string;Defined in: packages/engine/src/agent/stream-events.ts:92
inputSchema
Section titled “inputSchema”inputSchema: Record<string, unknown>;Defined in: packages/engine/src/agent/stream-events.ts:93
systemPrompt?
Section titled “systemPrompt?”optional systemPrompt?: string;Defined in: packages/engine/src/agent/stream-events.ts:94
maxTurns?
Section titled “maxTurns?”optional maxTurns?: number;Defined in: packages/engine/src/agent/stream-events.ts:95
needsApproval?
Section titled “needsApproval?”optional needsApproval?: boolean;Defined in: packages/engine/src/agent/stream-events.ts:96
StreamTextPart
Section titled “StreamTextPart”Defined in: packages/engine/src/agent/stream-events.ts:113
Multimodal content parts — a structural mirror of core’s AparteContentPart.
Mirrored rather than imported for the same reason as everything else in this
file: engine stays zero-import at runtime. Mirrored EXACTLY rather than
loosened to unknown[], which was the first attempt: transportCall is
contravariant, so the message type has to be assignable to core’s AND core’s to
it, and unknown[] fails the second direction. Core compiles
streamRunner: runStreamAgent, so a part added on one side and not the other is
a typecheck error there rather than a silent divergence.
The loop never looks inside content — it carries messages to the transport and appends results. This exists to make the seam type-check, not to be read.
Properties
Section titled “Properties”type: "text";Defined in: packages/engine/src/agent/stream-events.ts:113
text: string;Defined in: packages/engine/src/agent/stream-events.ts:113
StreamImagePart
Section titled “StreamImagePart”Defined in: packages/engine/src/agent/stream-events.ts:114
Properties
Section titled “Properties”type: "image";Defined in: packages/engine/src/agent/stream-events.ts:114
image: string;Defined in: packages/engine/src/agent/stream-events.ts:114
mimeType?
Section titled “mimeType?”optional mimeType?: string;Defined in: packages/engine/src/agent/stream-events.ts:114
StreamFilePart
Section titled “StreamFilePart”Defined in: packages/engine/src/agent/stream-events.ts:115
Properties
Section titled “Properties”type: "file";Defined in: packages/engine/src/agent/stream-events.ts:115
data: string;Defined in: packages/engine/src/agent/stream-events.ts:115
mimeType
Section titled “mimeType”mimeType: string;Defined in: packages/engine/src/agent/stream-events.ts:115
optional name?: string;Defined in: packages/engine/src/agent/stream-events.ts:115
StreamAgentMessage
Section titled “StreamAgentMessage”Defined in: packages/engine/src/agent/stream-events.ts:132
A conversation message (mirrors AparteChatMessage).
role used to be a bare string, justified by a comment saying the loop needed
to push the 'tool_call' / 'tool_result' envelope roles “without importing
core’s union”. But core’s union already contains both — so the reason had not
been true for some time, and the looseness cost something real: transportCall
is contravariant, so this message type must ALSO be assignable to core’s, and a
string role is not assignable to a closed union. That was the last link in the
chain that stopped streamRunner: runStreamAgent from compiling.
Mirrored structurally rather than imported, to keep this package zero-import at runtime; core, which depends on this package, compiles the two against each other.
Properties
Section titled “Properties”role: "user" | "assistant" | "system" | "tool_call" | "tool_result";Defined in: packages/engine/src/agent/stream-events.ts:133
content
Section titled “content”content: string | StreamContentPart[];Defined in: packages/engine/src/agent/stream-events.ts:143
Message content — carried, never inspected by this loop.
Was plain string, because core’s AparteChatMessage.content became
string | AparteContentPart[] for multimodal messages and this mirror never
followed. The consequence was not subtle: new AparteClient({ streamRunner: runStreamAgent }) — the one composition the two packages exist to make, and
the headline of five docs pages — did not typecheck.
toolCalls?
Section titled “toolCalls?”optional toolCalls?: StreamToolCall[];Defined in: packages/engine/src/agent/stream-events.ts:145
Present on a 'tool_call' envelope — the whole turn’s calls, grouped.
toolCallId?
Section titled “toolCallId?”optional toolCallId?: string;Defined in: packages/engine/src/agent/stream-events.ts:147
Present on a 'tool_result' message — which call it answers.
precedingText?
Section titled “precedingText?”optional precedingText?: string;Defined in: packages/engine/src/agent/stream-events.ts:149
Assistant text that preceded the tool call(s) this turn.
StreamChatRequest
Section titled “StreamChatRequest”Defined in: packages/engine/src/agent/stream-events.ts:178
The request handed to the transport each turn (mirrors AparteChatRequest).
The two fields the loop actually reads are DECLARED rather than reached through
an index signature. They used to be dynamic ([key: string]: unknown), which
read like “extra fields pass through” but is a requirement on the target of an
assignment, not a permission — it is what made core’s AparteChatRequest
un-assignable here and broke streamRunner: runStreamAgent. Declaring them is
both the fix and the more honest description: these are the fields the loop
branches on. Anything else a host attaches still passes through untouched,
because TypeScript only excess-property-checks object literals.
Types kept structural (not imported from core) so this package stays zero-import at runtime; core’s composition compiles the two against each other.
Properties
Section titled “Properties”messages
Section titled “messages”messages: StreamAgentMessage[];Defined in: packages/engine/src/agent/stream-events.ts:179
modelId
Section titled “modelId”modelId: string;Defined in: packages/engine/src/agent/stream-events.ts:186
Required, because transportCall is contravariant: the runner hands this
request to a transport typed against core’s AparteChatRequest, where
modelId is required. A mirror that omitted it made the whole runner
un-assignable — from the opposite direction to the messages mismatch.
_meta?
Section titled “_meta?”optional _meta?: Record<string, unknown>;Defined in: packages/engine/src/agent/stream-events.ts:188
Per-turn hints carried through for the host (prefixSegments, and whatever the consumer puts there); the loop reads none.
toolChoice?
Section titled “toolChoice?”optional toolChoice?: | { name: string; input?: Record<string, unknown>;} | "auto" | "none";Defined in: packages/engine/src/agent/stream-events.ts:190
'none' makes the loop drop the tool inventory for that turn.
tools?
Section titled “tools?”optional tools?: StreamTool[];Defined in: packages/engine/src/agent/stream-events.ts:192
The inventory the loop sends, and clears when toolChoice is 'none'.
StreamToolConfig
Section titled “StreamToolConfig”Defined in: packages/engine/src/agent/stream-events.ts:207
Per-tool loop configuration (mirrors the AparteTool subset the loop reads).
Properties
Section titled “Properties”maxTurns?
Section titled “maxTurns?”optional maxTurns?: number;Defined in: packages/engine/src/agent/stream-events.ts:208
needsApproval?
Section titled “needsApproval?”optional needsApproval?: boolean | ((call) => boolean | "ask" | "deny");Defined in: packages/engine/src/agent/stream-events.ts:222
Whether a call of this tool pauses for a decision. A boolean is the tool’s own
declaration; a predicate decides PER CALL, from the arguments — the shape an
approval policy needs (“a write to this path asks, a read does not”), and one a
boolean could only approximate by gating every call and auto-approving most,
which paints awaiting-approval on rows nobody was ever going to be asked about.
The predicate has three answers, because a policy has three: false runs the
call, 'ask' (or true) puts it to someone — the loop announces the pause with
tool-awaiting-approval — and 'deny' means the decision is already made: the
resolver is still consulted (it is the one channel that carries the refusal
sentence back), but nothing is announced, since nobody is being asked.
StreamRunOptions
Section titled “StreamRunOptions”Defined in: packages/engine/src/agent/stream-run.ts:40
Properties
Section titled “Properties”messageId
Section titled “messageId”messageId: string;Defined in: packages/engine/src/agent/stream-run.ts:42
Id of the assistant message being streamed (opaque; carried in events).
baseRequest
Section titled “baseRequest”baseRequest: StreamChatRequest;Defined in: packages/engine/src/agent/stream-run.ts:44
Turn-1 request; the loop clones its messages and enriches them per turn.
transportCall
Section titled “transportCall”transportCall: (request) => Promise< | string| AsyncIterable<StreamChatEvent, any, any>>;Defined in: packages/engine/src/agent/stream-run.ts:51
Calls the transport with the (possibly enriched) request. Returns the
structured stream, or a plain string for a non-streaming provider. Mirrors
getTransport().chat(provider, request, auth, ctx) with provider/auth/ctx
closed over by the adapter.
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
request | StreamChatRequest |
Returns
Section titled “Returns”Promise<
| string
| AsyncIterable<StreamChatEvent, any, any>>
toolLookup
Section titled “toolLookup”toolLookup: (name) => StreamToolHandler | undefined;Defined in: packages/engine/src/agent/stream-run.ts:53
Resolves a tool’s handler by name (mirrors aparteGlobalConfig.getToolHandler).
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
name | string |
Returns
Section titled “Returns”StreamToolHandler | undefined
toolConfigLookup?
Section titled “toolConfigLookup?”optional toolConfigLookup?: (name) => StreamToolConfig | undefined;Defined in: packages/engine/src/agent/stream-run.ts:55
Resolves a tool’s loop config by name (maxTurns / needsApproval).
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
name | string |
Returns
Section titled “Returns”StreamToolConfig | undefined
approvalResolver?
Section titled “approvalResolver?”optional approvalResolver?: StreamApprovalResolver;Defined in: packages/engine/src/agent/stream-run.ts:57
HITL approval resolver for needsApproval tools (default: never called).
emitter
Section titled “emitter”emitter: StreamRunEmitter;Defined in: packages/engine/src/agent/stream-run.ts:59
Synchronous, ordered event sink consumed by the adapter.
signal
Section titled “signal”signal: AbortSignal;Defined in: packages/engine/src/agent/stream-run.ts:61
Single abort signal composing _isAborted + the stream controller.
maxTurns?
Section titled “maxTurns?”optional maxTurns?: number;Defined in: packages/engine/src/agent/stream-run.ts:63
Global turn cap.
Default
Section titled “Default”10toolTimeoutMs?
Section titled “toolTimeoutMs?”optional toolTimeoutMs?: number;Defined in: packages/engine/src/agent/stream-run.ts:65
Per-tool-call handler timeout in ms.
Default
Section titled “Default”300000onHistoryAppend?
Section titled “onHistoryAppend?”optional onHistoryAppend?: (message) => void;Defined in: packages/engine/src/agent/stream-run.ts:86
Called for every turn the loop appends to the history — the grouped
tool_call envelope, each tool_result (resolved or rejected), and a
pipeline phase’s reply — in order, and always before the transport call that
would carry it. Never called for the messages you passed in baseRequest.
For hosts that own their own transcript. The loop re-sends its messages
array every turn, which suits stateless message APIs but not a prefix
cache (llama.cpp slots, vLLM), where turn N+1 must EXTEND turn N byte for
byte. Such a host builds its own request in transportCall — ignoring
request.messages — and mirrors these notifications into its append-only
log, instead of reimplementing the loop’s tool bookkeeping. Synchronous and
ordered, like emitter.
One rule about the envelope: a turn’s tool_call message is reported ONCE, when
its first call completes, and the calls that complete later in the same turn are
already in that same object’s toolCalls — the array is shared by reference, not
copied. Hold the reference, not a snapshot, and every result you receive after it
is declared by it.
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
message | StreamAgentMessage |
Returns
Section titled “Returns”void
idGen?
Section titled “idGen?”optional idGen?: (prefix) => string;Defined in: packages/engine/src/agent/stream-run.ts:93
Generates the ids the loop mints itself — today only the synthetic call of a
forced toolChoice (prefix is 'synthetic-tool'). The default is a
deterministic per-run counter; core’s client injects a crypto-based one. Tool
ids otherwise flow from the stream.
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
prefix | string |
Returns
Section titled “Returns”string
Type Aliases
Section titled “Type Aliases”StreamChatEvent
Section titled “StreamChatEvent”type StreamChatEvent = | { type: "text"; delta: string;} | { type: "thinking"; delta: string;} | { type: "tool_use";} & StreamToolCall | { type: "error"; message: string;} | { type: "done"; usage?: StreamUsage;};Defined in: packages/engine/src/agent/stream-events.ts:75
A structured stream event from the transport (mirrors AparteStreamEvent). The
tool_use variant spreads StreamToolCall exactly like core’s does.
StreamContentPart
Section titled “StreamContentPart”type StreamContentPart = | StreamTextPart | StreamImagePart | StreamFilePart;Defined in: packages/engine/src/agent/stream-events.ts:116
StreamToolHandler
Section titled “StreamToolHandler”type StreamToolHandler = (call, signal) => Promise<{ content: string; structuredContent?: unknown;}>;Defined in: packages/engine/src/agent/stream-events.ts:196
A tool handler (mirrors the resolved AparteToolHandler).
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
call | StreamToolCall |
signal | AbortSignal |
Returns
Section titled “Returns”Promise<{
content: string;
structuredContent?: unknown;
}>
StreamApprovalResolver
Section titled “StreamApprovalResolver”type StreamApprovalResolver = (call, signal) => Promise<{ approved: boolean; payload?: unknown; instruction?: string; reason?: string;}>;Defined in: packages/engine/src/agent/stream-events.ts:229
Resolves a human-in-the-loop approval (mirrors core’s
AparteToolApprovalResolver). Injected so the loop stays headless.
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
call | { id: string; name: string; input: Record<string, unknown>; } |
call.id | string |
call.name | string |
call.input | Record<string, unknown> |
signal | AbortSignal |
Returns
Section titled “Returns”Promise<{
approved: boolean;
payload?: unknown;
instruction?: string;
reason?: string;
}>
StreamRunEvent
Section titled “StreamRunEvent”type StreamRunEvent = | { type: "run-start";} | { type: "turn-start";} | { type: "text-delta"; delta: string;} | { type: "text-flush";} | { type: "thinking-delta"; delta: string;} | { type: "tool-start"; toolCallId: string; name: string; input: unknown;} | { type: "tool-awaiting-approval"; toolCallId: string; name: string; input: unknown;} | { type: "tool-approved"; toolCallId: string;} | { type: "tool-rejected"; toolCallId: string; reason: string;} | { type: "tool-failed"; toolCallId: string; error: string;} | { type: "tool-resolved"; toolCallId: string; result: string; structuredResult?: unknown;} | { type: "tool-aborted"; toolCallId: string;} | { type: "turn-limit-exceeded"; scope: "global" | "tool"; limit: number; toolCallId?: string;} | { type: "run-aborted";} | { type: "run-done"; usage?: StreamUsage;};Defined in: packages/engine/src/agent/stream-events.ts:272
High-level, DOM-free events isomorphic to the inline loop’s targetElement.*
call sequence. Emitted synchronously and in order (see StreamRunEmitter)
so the adapter reproduces the exact streaming update order.
Mapping to the inline loop it replaced, for the adapter:
run-start→ updateMessage(status:‘streaming’) once at loop entry (the leading write before turn 1)turn-start→ reset the per-turn parser / thinking / streaming-segment state (no DOM); one per turntext-delta→ parser-driven addSegment/updateSegment, else typeName/updateLastMessagetext-flush→ textParser.finalize() then addSegment/updateSegment the finalized segments; one per turn, after the inner SSE loop ends (surfaced by the spike — a turn-boundary flush)thinking-delta→ addSegment(‘thinking’) then updateSegment(content); firsttext-deltaafter thinking collapses it (updateSegment collapsed:true)tool-start→ renderer lookup + per-tool-name CSS inject into document.head + addSegmenttool-awaiting-approval→ updateSegment(‘awaiting-approval’) + dispatchaparte-tool-approval-requesttool-approved→ updateSegment(‘pending’)tool-rejected→ updateSegment(‘rejected’, result)tool-failed→ updateSegment(‘failed’, error) — the handler threw; the run then ends on that errortool-resolved→ updateSegment(‘resolved’, result)tool-aborted→ updateSegment(‘aborted’) (no-handler path, timeout/abort path, or per-tool maxTurns path)turn-limit-exceededscope:‘global’ → addSegment(error ‘MAX_TURNS_EXCEEDED’); scope:‘tool’ → updateSegment(‘aborted’)run-aborted→ dispatchaparte-message-aborted(from the inner-loop abort check or the outer turn-boundary abort check)run-done→ updateMessage(status:‘completed’) always + setUsage if usage
Union Members
Section titled “Union Members”Type Literal
Section titled “Type Literal”{ type: "run-start";}Type Literal
Section titled “Type Literal”{ type: "turn-start";}Type Literal
Section titled “Type Literal”{ type: "text-delta"; delta: string;}Type Literal
Section titled “Type Literal”{ type: "text-flush";}Type Literal
Section titled “Type Literal”{ type: "thinking-delta"; delta: string;}Type Literal
Section titled “Type Literal”{ type: "tool-start"; toolCallId: string; name: string; input: unknown;}Type Literal
Section titled “Type Literal”{ type: "tool-awaiting-approval"; toolCallId: string; name: string; input: unknown;}Type Literal
Section titled “Type Literal”{ type: "tool-approved"; toolCallId: string;}Type Literal
Section titled “Type Literal”{ type: "tool-rejected"; toolCallId: string; reason: string;}Type Literal
Section titled “Type Literal”{ type: "tool-failed"; toolCallId: string; error: string;}The handler threw (not an abort): the row hears of the crash before the run ends on it.
Type Literal
Section titled “Type Literal”{ type: "tool-resolved"; toolCallId: string; result: string; structuredResult?: unknown;}Type Literal
Section titled “Type Literal”{ type: "tool-aborted"; toolCallId: string;}Type Literal
Section titled “Type Literal”{ type: "turn-limit-exceeded"; scope: "global" | "tool"; limit: number; toolCallId?: string;}Type Literal
Section titled “Type Literal”{ type: "run-aborted";}Type Literal
Section titled “Type Literal”{ type: "run-done"; usage?: StreamUsage;}StreamRunEmitter
Section titled “StreamRunEmitter”type StreamRunEmitter = (event) => void;Defined in: packages/engine/src/agent/stream-events.ts:295
Synchronous event sink — mirrors AGUIEmitter. Synchronous by contract: the
loop must never yield between emitting an event and its ordered successor, or
the adapter’s streaming updates would interleave out of order.
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
event | StreamRunEvent |
Returns
Section titled “Returns”void
Functions
Section titled “Functions”runStreamAgent()
Section titled “runStreamAgent()”function runStreamAgent(opts): Promise<StreamUsage | undefined>;Defined in: packages/engine/src/agent/stream-run.ts:124
Run the structured-stream agent loop. Resolves the last turn’s usage (the
done{usage} last-write-wins, mirroring _streamLoop’s return), or
undefined. Throws on a stream error event or a non-abort tool failure —
the caller (adapter) routes that to its lifecycle-error handler, exactly as
_handleSend/_handleRetry/_handleEdit catch _streamLoop.
This block has to stay ADJACENT to the function. It used to sit above
warnUnknownStreamEvent, six lines and a docblock away from what it describes, and a
docblock separated from its declaration is attached to nothing: TypeDoc took the
helper’s comment as the neighbouring one and @aparte/engine’s reference page carried
the package’s headline export with no description at all — the very export its own
frontmatter names. Same trap the custom-elements analyser has with a class docblock
pushed above an import.
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
opts | StreamRunOptions |
Returns
Section titled “Returns”Promise<StreamUsage | undefined>