High-level wrapper around a protocol connection to a specific thread.
In the thread-centric protocol, threads are durable (backed by
checkpoints) and connections are ephemeral. A ThreadStream is the
client-side handle for interacting with a thread: starting runs,
subscribing to events, consuming assembled projections (messages,
values, toolCalls, etc.), and responding to interrupts.
Construct via client.threads.stream(threadId?, { assistantId? }).
Whether the run was interrupted (a lifecycle "interrupted" event
was received). Mirrors the in-process run.interrupted.
Interrupt payloads collected during the run, if any.
Mirrors the in-process run.interrupts.
Register a listener for every globally-unique event on the thread.
Fires exactly once per event_id across both the content pump
(user subscribe() calls) and the lifecycle watcher. Events
without an event_id always fire through (dedup is best-effort).
Returns an unsubscribe function. Primary consumer is
StreamController, which uses the listener to feed discovery
runners and pick up deeply-nested interrupts that the narrow
content pump wouldn't deliver.
Respond to an interrupt without the v1 eager lazy-getter shims. See submitRun for why this exists alongside input.respond.
Public, idempotent entry point to start the wildcard lifecycle watcher.
The watcher is normally started lazily by submitRun /
respondInput because for fresh (self-created) threads the SSE
stream would 404 if opened before the server has the thread row.
Callers that already know the thread exists server-side
(StreamController.hydrate of an existing thread) can use this
to start the watcher up front. The watcher subscribes to wildcard
lifecycle events across every namespace, so it sees arbitrarily-
nested subagent lifecycle messages that the narrow root content
pump (running at depth: 1) wouldn't reach — that's what makes
subagent discovery work for historical thread loads.
Idempotent — repeat calls reuse the in-flight start promise.
Start a run without the v1 eager lazy-getter shims.
run.start (the v1 entry point) eagerly opens a wildcard values
projection so thread.output / thread.values resolve regardless
of access order, and calls #ensureLifecycleTracking which opens
another wildcard ["lifecycle", "input"] subscription. Both
subscriptions widen #computeUnionFilter to wildcard, defeating
the progressive-expansion rotation strategy.
submitRun skips those shims — callers that manage their own
content subscriptions (such as StreamController) get the narrow
union filter they asked for. Lifecycle / interrupt tracking is
instead served by the dedicated #startLifecycleWatcher, which
opens a wildcard ["lifecycle", "input"] stream alongside the
narrow content pump on both SSE and WebSocket transports.
Subscribe to raw wire channels and receive protocol events.
For assembled projections, use the lazy getters instead:
thread.messages, thread.values, thread.toolCalls,
thread.subgraphs, thread.subagents, thread.output.