Skip to content

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.

ts
inputTokens: number;

Defined in: packages/engine/src/agent/stream-events.ts:35

ts
outputTokens: number;

Defined in: packages/engine/src/agent/stream-events.ts:36

ts
optional totalTokens?: number;

Defined in: packages/engine/src/agent/stream-events.ts:37

ts
optional cacheReadTokens?: number;

Defined in: packages/engine/src/agent/stream-events.ts:38

ts
optional durationMs?: number;

Defined in: packages/engine/src/agent/stream-events.ts:39


Defined in: packages/engine/src/agent/stream-events.ts:57

One tool call as surfaced by the provider stream (mirrors AparteToolCall).

ts
id: string;

Defined in: packages/engine/src/agent/stream-events.ts:58

ts
name: string;

Defined in: packages/engine/src/agent/stream-events.ts:59

ts
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.


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.

ts
name: string;

Defined in: packages/engine/src/agent/stream-events.ts:91

ts
description: string;

Defined in: packages/engine/src/agent/stream-events.ts:92

ts
inputSchema: Record<string, unknown>;

Defined in: packages/engine/src/agent/stream-events.ts:93

ts
optional systemPrompt?: string;

Defined in: packages/engine/src/agent/stream-events.ts:94

ts
optional maxTurns?: number;

Defined in: packages/engine/src/agent/stream-events.ts:95

ts
optional needsApproval?: boolean;

Defined in: packages/engine/src/agent/stream-events.ts:96


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.

ts
type: "text";

Defined in: packages/engine/src/agent/stream-events.ts:113

ts
text: string;

Defined in: packages/engine/src/agent/stream-events.ts:113


Defined in: packages/engine/src/agent/stream-events.ts:114

ts
type: "image";

Defined in: packages/engine/src/agent/stream-events.ts:114

ts
image: string;

Defined in: packages/engine/src/agent/stream-events.ts:114

ts
optional mimeType?: string;

Defined in: packages/engine/src/agent/stream-events.ts:114


Defined in: packages/engine/src/agent/stream-events.ts:131

A conversation message (mirrors AparteChatMessage).

role used to be a bare string, justified by a comment saying the loop needed to push the envelope roles of a tool turn “without importing core’s union”. But core’s union already contained them — 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.

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

Defined in: packages/engine/src/agent/stream-events.ts:132

ts
content: string | StreamContentPart[];

Defined in: packages/engine/src/agent/stream-events.ts:142

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.

ts
optional toolCalls?: StreamToolCall[];

Defined in: packages/engine/src/agent/stream-events.ts:147

Present on an 'assistant' message — the whole turn’s calls, grouped. What it said before them is that message’s own content, '' when it said nothing.

ts
optional toolCallId?: string;

Defined in: packages/engine/src/agent/stream-events.ts:149

Present on a 'tool' message — which call it answers.

ts
optional toolName?: string;

Defined in: packages/engine/src/agent/stream-events.ts:151

Present on a 'tool' message — the name of the tool that ran.


Defined in: packages/engine/src/agent/stream-events.ts:180

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.

ts
messages: StreamAgentMessage[];

Defined in: packages/engine/src/agent/stream-events.ts:181

ts
modelId: string;

Defined in: packages/engine/src/agent/stream-events.ts:188

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.

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

Defined in: packages/engine/src/agent/stream-events.ts:190

Per-turn hints carried through for the host (prefixSegments, and whatever the consumer puts there); the loop reads none.

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

Defined in: packages/engine/src/agent/stream-events.ts:197

'none' makes the loop drop the tool inventory for that turn. An object forces a tool, and forcing is a turn-1 instruction: the loop sends it on the first transport call of the run and lifts it (to 'auto') for every turn after, so the model can answer once the tool has.

ts
optional tools?: StreamTool[];

Defined in: packages/engine/src/agent/stream-events.ts:199

The inventory the loop sends, and clears when toolChoice is 'none'.


Defined in: packages/engine/src/agent/stream-events.ts:214

Per-tool loop configuration (mirrors the AparteTool subset the loop reads).

ts
optional maxTurns?: number;

Defined in: packages/engine/src/agent/stream-events.ts:215

ts
optional needsApproval?: boolean | ((call) => boolean | "ask" | "deny");

Defined in: packages/engine/src/agent/stream-events.ts:229

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.

ts
optional external?: boolean;

Defined in: packages/engine/src/agent/stream-events.ts:236

Known to the model, executed elsewhere — a browser tool behind a server loop. The loop never looks up a handler for it: with deferral the call waits for its result in deferral.outcomes; without it, the call is aborted like a missing handler.


Defined in: packages/engine/src/agent/stream-events.ts:247

A call a suspended run left without an answer.

ts
toolCallId: string;

Defined in: packages/engine/src/agent/stream-events.ts:248

ts
name: string;

Defined in: packages/engine/src/agent/stream-events.ts:249

ts
input: unknown;

Defined in: packages/engine/src/agent/stream-events.ts:250

ts
reason: StreamDeferredReason;

Defined in: packages/engine/src/agent/stream-events.ts:251


Defined in: packages/engine/src/agent/stream-events.ts:277

Defer instead of block — see StreamRunOptions.deferral. Data only, so a run it answers can be days old and on another machine.

ts
optional outcomes?: Record<string, StreamDeferredOutcome>;

Defined in: packages/engine/src/agent/stream-events.ts:283

Answers for calls a previous run left pending, by tool call id. They answer those calls only: a call the model makes after the resume goes through its gate afresh, even when it carries the same id.

ts
optional token?: string;

Defined in: packages/engine/src/agent/stream-events.ts:285

Opaque to the loop, which never reads it: a remote runner carries its continuation ticket here.


Defined in: packages/engine/src/agent/stream-run.ts:45

ts
messageId: string;

Defined in: packages/engine/src/agent/stream-run.ts:47

Id of the assistant message being streamed (opaque; carried in events).

ts
baseRequest: StreamChatRequest;

Defined in: packages/engine/src/agent/stream-run.ts:49

Turn-1 request; the loop clones its messages and enriches them per turn.

ts
transportCall: (request) => Promise<
  | string
| AsyncIterable<StreamChatEvent, any, any>>;

Defined in: packages/engine/src/agent/stream-run.ts:56

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.

ParameterType
requestStreamChatRequest

Promise< | string | AsyncIterable<StreamChatEvent, any, any>>

ts
toolLookup: (name) => StreamToolHandler | undefined;

Defined in: packages/engine/src/agent/stream-run.ts:58

Resolves a tool’s handler by name (mirrors aparteGlobalConfig.getToolHandler).

ParameterType
namestring

StreamToolHandler | undefined

ts
optional toolConfigLookup?: (name) => StreamToolConfig | undefined;

Defined in: packages/engine/src/agent/stream-run.ts:60

Resolves a tool’s loop config by name (maxTurns / needsApproval).

ParameterType
namestring

StreamToolConfig | undefined

ts
optional approvalResolver?: StreamApprovalResolver;

Defined in: packages/engine/src/agent/stream-run.ts:62

HITL approval resolver for needsApproval tools (default: never called).

ts
emitter: StreamRunEmitter;

Defined in: packages/engine/src/agent/stream-run.ts:64

Synchronous, ordered event sink consumed by the adapter.

ts
signal: AbortSignal;

Defined in: packages/engine/src/agent/stream-run.ts:66

Single abort signal composing _isAborted + the stream controller.

ts
optional maxTurns?: number;

Defined in: packages/engine/src/agent/stream-run.ts:68

Global turn cap.

ts
10
ts
optional toolTimeoutMs?: number;

Defined in: packages/engine/src/agent/stream-run.ts:70

Per-tool-call handler timeout in ms.

ts
300000
ts
optional onHistoryAppend?: (message) => void;

Defined in: packages/engine/src/agent/stream-run.ts:104

Called for every turn the loop appends to the history — the assistant message carrying the turn’s calls, each tool message answering one (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 assistant 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.

Two of its fields are finalised after you are notified, then: toolCalls gains the turn’s later calls, and content is re-read at the turn’s end (a provider that emits [tool, text] streams the rest of the sentence after the envelope is opened — see syncEnvelopeText). A host that must write BYTES at receipt — an append-only log, which cannot hold a reference — should therefore treat the envelope as provisional and write it only once the turn’s last tool message has arrived, or re-read the object it holds before serialising.

content is the field that moved here, and the failure is louder than it was: a snapshot no longer loses a trailing clause, it loses the whole sentence the assistant said before it called anything.

ParameterType
messageStreamAgentMessage

void

ts
optional idGen?: (prefix) => string;

Defined in: packages/engine/src/agent/stream-run.ts:111

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.

ParameterType
prefixstring

string

ts
optional deferral?: StreamDeferral;

Defined in: packages/engine/src/agent/stream-run.ts:125

Defer instead of block. With this set, a call the gate sends to a person is not awaited on approvalResolver, and an external tool is not treated as missing: each takes its answer from outcomes, and without one the call is DEFERRED — declared on the turn’s envelope with no tool answer. The rest of the model’s reply is still read to its end (its later calls pending too, nothing run after the pause point), and the run ends on run-suspended instead of run-done.

A run whose baseRequest.messages ends on an assistant envelope with unanswered calls RESUMES: those calls go through the same processCall as a streamed one, in order, before the first transport call. A call the gate denies is never deferred — it is refused, through approvalResolver, as without this option.

ts
optional providerId?: string;

Defined in: packages/engine/src/agent/stream-run.ts:130

The caller’s provider id. Opaque to the loop, like messageId: a runner that forwards the run elsewhere (a server) needs it.

ts
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.


ts
type StreamContentPart = 
  | StreamTextPart
  | StreamImagePart;

Defined in: packages/engine/src/agent/stream-events.ts:115


ts
type StreamToolHandler = (call, signal) => Promise<{
  content: string;
  structuredContent?: unknown;
}>;

Defined in: packages/engine/src/agent/stream-events.ts:203

A tool handler (mirrors the resolved AparteToolHandler).

ParameterType
callStreamToolCall
signalAbortSignal

Promise<{ content: string; structuredContent?: unknown; }>


ts
type StreamDeferredReason = "approval" | "external" | "queued";

Defined in: packages/engine/src/agent/stream-events.ts:244

Why a deferred call waits. approval: a person must decide. external: the tool runs elsewhere and its result must come back. queued: nothing — it came after the pause point in the same reply, and runs when the run resumes.


ts
type StreamRefusalOrigin = "person" | "policy" | "earlier-refusal";

Defined in: packages/engine/src/agent/stream-events.ts:259

Who refused a call, for the row: the model reads one sentence whoever it was from, the person reading the transcript needs to know. 'earlier-refusal' is a call the loop did not run because another call of the same step had been refused.


ts
type StreamAbortCause = "stopped" | "timeout" | "no-handler" | "no-resolver";

Defined in: packages/engine/src/agent/stream-events.ts:265

Why a call was aborted: a Stop, its handler’s timeout, no handler for it here, or a gate with nobody to ask.


ts
type StreamDeferredOutcome = 
  | {
  kind: "decision";
  approved: boolean;
  reason?: string;
  instruction?: string;
  by?: StreamRefusalOrigin;
  payload?: unknown;
}
  | {
  kind: "result";
  content: string;
  structuredContent?: unknown;
}
  | {
  kind: "failed";
  error: string;
};

Defined in: packages/engine/src/agent/stream-events.ts:268

The answer to a deferred call, handed to the run that resumes it.


ts
type StreamApprovalResolver = (call, signal) => Promise<{
  approved: boolean;
  payload?: unknown;
  instruction?: string;
  reason?: string;
  by?: StreamRefusalOrigin;
}>;

Defined in: packages/engine/src/agent/stream-events.ts:292

Resolves a human-in-the-loop approval (mirrors core’s AparteToolApprovalResolver). Injected so the loop stays headless.

ParameterType
call{ id: string; name: string; input: Record<string, unknown>; }
call.idstring
call.namestring
call.inputRecord<string, unknown>
signalAbortSignal

Promise<{ approved: boolean; payload?: unknown; instruction?: string; reason?: string; by?: StreamRefusalOrigin; }>


ts
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;
  by?: StreamRefusalOrigin;
  instruction?: string;
}
  | {
  type: "tool-failed";
  toolCallId: string;
  error: string;
}
  | {
  type: "tool-resolved";
  toolCallId: string;
  result: string;
  structuredResult?: unknown;
}
  | {
  type: "tool-aborted";
  toolCallId: string;
  cause?: StreamAbortCause;
}
  | {
  type: "turn-limit-exceeded";
  scope: "global" | "tool";
  limit: number;
  toolCallId?: string;
}
  | {
  type: "run-aborted";
}
  | {
  type: "run-done";
  usage?: StreamUsage;
}
  | {
  type: "run-suspended";
  pending: StreamDeferredCall[];
  usage?: StreamUsage;
};

Defined in: packages/engine/src/agent/stream-events.ts:340

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 turn
  • text-delta → parser-driven addSegment/updateSegment, else typeName/updateLastMessage
  • text-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); first text-delta after thinking collapses it (updateSegment collapsed:true)
  • tool-start → renderer lookup + per-tool-name CSS inject into document.head + addSegment
  • tool-awaiting-approval → updateSegment(‘awaiting-approval’) + dispatch aparte-tool-approval-request
  • tool-approved → updateSegment(‘pending’)
  • tool-rejected → updateSegment(‘rejected’, result)
  • tool-failed → updateSegment(‘failed’, error) — the handler threw; the run then ends on that error
  • tool-resolved → updateSegment(‘resolved’, result)
  • tool-aborted → updateSegment(‘aborted’) (no-handler path, timeout/abort path, or per-tool maxTurns path)
  • turn-limit-exceeded scope:‘global’ → addSegment(error ‘MAX_TURNS_EXCEEDED’); scope:‘tool’ → updateSegment(‘aborted’)
  • run-aborted → dispatch aparte-message-aborted (from the inner-loop abort check or the outer turn-boundary abort check)
  • run-done → updateMessage(status:‘completed’) always + setUsage if usage
  • run-suspended → (with deferral) the run ends with calls left pending; the adapter marks them and completes the message — the TURN goes on through the run that resumes them
ts
{
  type: "run-start";
}

ts
{
  type: "turn-start";
}

ts
{
  type: "text-delta";
  delta: string;
}

ts
{
  type: "text-flush";
}

ts
{
  type: "thinking-delta";
  delta: string;
}

ts
{
  type: "tool-start";
  toolCallId: string;
  name: string;
  input: unknown;
}

ts
{
  type: "tool-awaiting-approval";
  toolCallId: string;
  name: string;
  input: unknown;
}

ts
{
  type: "tool-approved";
  toolCallId: string;
}

ts
{
  type: "tool-rejected";
  toolCallId: string;
  reason: string;
  by?: StreamRefusalOrigin;
  instruction?: string;
}

reason is the sentence the model reads; by and instruction are for the row, which speaks to a person in their own language (optional for a runner that emits its own).


ts
{
  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.


ts
{
  type: "tool-resolved";
  toolCallId: string;
  result: string;
  structuredResult?: unknown;
}

ts
{
  type: "tool-aborted";
  toolCallId: string;
  cause?: StreamAbortCause;
}

The call did not run to an answer. cause says why, for the row: 'no-handler' and 'no-resolver' are the page’s setup, not anything the person did. Absent on the calls a run ended before reaching.


ts
{
  type: "turn-limit-exceeded";
  scope: "global" | "tool";
  limit: number;
  toolCallId?: string;
}

ts
{
  type: "run-aborted";
}

ts
{
  type: "run-done";
  usage?: StreamUsage;
}

ts
{
  type: "run-suspended";
  pending: StreamDeferredCall[];
  usage?: StreamUsage;
}

Ends a run in place of run-done when calls are left for someone else to answer.


ts
type StreamRunEmitter = (event) => void;

Defined in: packages/engine/src/agent/stream-events.ts:374

Synchronous event sink. 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.

ParameterType
eventStreamRunEvent

void

ts
function runStreamAgent(opts): Promise<StreamUsage | undefined>;

Defined in: packages/engine/src/agent/stream-run.ts:161

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.

ParameterType
optsStreamRunOptions

Promise<StreamUsage | undefined>


ts
function unansweredTail(messages): StreamToolCall[];

Defined in: packages/engine/src/agent/stream-run.ts:589

The calls a history leaves unanswered: those of its LAST assistant envelope that no following tool message answers, in declaration order. A resumed run answers them first; a server checks them against the ticket it signed when the run paused.

ParameterType
messagesreadonly StreamAgentMessage[]

StreamToolCall[]


ts
function toolRefusal(decision): {
  reason: string;
  by: StreamRefusalOrigin;
  instruction?: string;
};

Defined in: packages/engine/src/agent/stream-run.ts:638

What a refusal says, and to whom. reason is the sentence the model reads: the refuser’s own when it gave one, else the person’s words quoted, else the fixed sentence. by and instruction are for the row, which tells a person who refused in their own language — a reason with no origin is a policy’s, as StreamApprovalResolver defines it.

Exported so a runner that answers an agent elsewhere writes the same sentence.

ParameterType
decision{ reason?: string; instruction?: string; by?: StreamRefusalOrigin; }
decision.reason?string
decision.instruction?string
decision.by?StreamRefusalOrigin
ts
{
  reason: string;
  by: StreamRefusalOrigin;
  instruction?: string;
}
ts
reason: string;
ts
by: StreamRefusalOrigin;
ts
optional instruction?: string;