Agents/Streaming/ — LLM Response Streaming Pipeline

Real-time LLM response streaming with provider-specific adapters

The streaming sub-module manages the real-time flow of LLM responses from 12+ providers through a unified pipeline. Each provider has dedicated stream wrappers that normalize vendor-specific SSE formats into a common chunk protocol, handle thinking/reasoning streams, manage cache retention headers, and provide graceful error recovery mid-stream.

12+
Provider Wrappers
6600+
Lines (run.ts)
~80k
Total Lines
5
Stream Formats

Real-Time Multi-Provider Backpressure-Aware Error-Recovery

📚 What is this & why does it exist?

The streaming pipeline handles the real-time delivery of AI responses. Instead of waiting for the entire response to generate, it delivers text token-by-token as the LLM produces it. Each provider (OpenAI, Anthropic, Google, etc.) sends data in a different format, so this module normalizes everything into a common protocol that the rest of OpenClaw can consume.

💡 Real-world analogy

Imagine a simultaneous translator at the United Nations. Delegates speak in different languages (providers), but listeners all hear through the same headset channel (unified stream). The translator (stream wrapper) converts each language in real-time, sentence-by-sentence, without waiting for the entire speech. If a speaker pauses or stumbles (backpressure/error), the translator smoothly manages the gap, perhaps repeating the last phrase or signaling a brief pause, rather than crashing the entire session.

Contents

Architecture

flowchart TD
  subgraph Entry["Stream Initiation"]
    RUN["run.ts\nrunEmbeddedPiAgent()"]
    ATTEMPT["attempt.ts\nTool Execution Loop"]
  end
  subgraph Wrappers["Provider Stream Wrappers"]
    OAI["openai-stream-wrappers.ts"]
    ANT["anthropic-cache-retention.ts"]
    GOO["google-stream-wrappers.ts"]
    BED["bedrock-stream-wrappers.ts"]
    MOON["moonshot-stream-wrappers.ts"]
    MINI["minimax-stream-wrappers.ts"]
    PROXY["proxy-stream-wrappers.ts"]
    ZAI["zai-stream-wrappers.ts"]
  end
  subgraph Processing["Stream Processing"]
    NORM["attempt.tool-call-normalization.ts"]
    REPAIR["attempt.tool-call-argument-repair.ts"]
    TRUNC["tool-result-truncation.ts"]
    GUARD["tool-result-context-guard.ts"]
  end
  subgraph Output["Delivery"]
    PAY["payloads.ts\nbuildEmbeddedRunPayloads()"]
    USAGE["usage-accumulator.ts"]
    REPORT["usage-reporting.ts"]
  end
  RUN --> Wrappers
  Wrappers --> ATTEMPT
  ATTEMPT --> Processing
  Processing --> PAY
  PAY --> USAGE
  USAGE --> REPORT

Data Flow

sequenceDiagram
    participant Runner as run.ts
    participant Wrapper as Stream Wrapper
    participant LLM as LLM Provider API
    participant Attempt as attempt.ts
    participant Payload as payloads.ts

    Runner->>Wrapper: initStream(model, messages)
    Wrapper->>LLM: HTTP SSE request
    loop Streaming chunks
        LLM-->>Wrapper: Provider-specific SSE event
        Wrapper-->>Runner: Normalized chunk
        Runner->>Attempt: Process chunk
        alt Tool call detected
            Attempt->>Attempt: Normalize tool call args
            Attempt->>Attempt: Repair malformed JSON
        end
        alt Thinking/reasoning content
            Attempt->>Attempt: Route to reasoning stream
        end
    end
    Attempt->>Payload: buildEmbeddedRunPayloads()
    Payload-->>Runner: Formatted payloads

Key Classes

Class / FunctionFilePurpose
runEmbeddedPiAgent()pi-embedded-runner/run.tsMain streaming orchestrator; manages the full agent execution lifecycle
buildEmbeddedRunPayloads()run/payloads.tsConstructs delivery payloads from assistant texts and tool metadata
attempt.ts looprun/attempt.tsCore tool execution loop; processes streaming chunks, handles tool calls
openai-stream-wrappers.tspi-embedded-runner/OpenAI-compatible SSE stream normalization
google-stream-wrappers.tspi-embedded-runner/Google Gemini stream normalization with schema sanitization
anthropic-cache-retention.tspi-embedded-runner/Anthropic prompt caching and cache-retention header management
bedrock-stream-wrappers.tspi-embedded-runner/AWS Bedrock stream adapter with IAM auth integration
moonshot-stream-wrappers.tspi-embedded-runner/Moonshot model stream adapter
moonshot-thinking-stream-wrappers.tspi-embedded-runner/Moonshot thinking/reasoning stream handling
minimax-stream-wrappers.tspi-embedded-runner/Minimax provider stream adapter
proxy-stream-wrappers.tspi-embedded-runner/Generic proxy pass-through for custom endpoints
zai-stream-wrappers.tspi-embedded-runner/Z AI provider stream adapter
UsageAccumulatorusage-accumulator.tsTracks input/output token counts across streaming chunks
usage-reporting.tspi-embedded-runner/Formats and reports token usage after stream completion

Data Structures

TypeNameDescription
typeEmbeddedPiRunResultComplete result from Pi agent run with payloads, usage, metadata, tool sends
typeEmbeddedPiRunMetaRun metadata: duration, model used, token counts, errors, stop reason
typeAgentStreamParamsStream parameters: temperature, maxTokens, fastMode
typeRunEmbeddedPiAgentParamsFull set of parameters for embedded run initiation
typeSubscribeEmbeddedPiSessionParamsCallbacks for tool results, reasoning, block replies during streaming
constBASE_CHUNK_RATIOBase ratio (0.4) for splitting message history during compaction
constMIN_CHUNK_RATIOMinimum ratio (0.15) for compaction chunk splitting
constSAFETY_MARGIN20% buffer (1.2) for token estimation inaccuracy

Interactions

DirectionPartnerInterfaceDescription
→LLM ProvidersHTTP SSESends streaming requests to provider APIs via wrappers
→Tool Systemattempt.ts loopExecutes tool calls discovered in streaming chunks
→Compactioncompact.tsTriggers history summarization on context overflow
←agent-command.tsrunEmbeddedPiAgent()Receives command parameters and returns results
→Auth Systemauth-profiles.tsFetches API credentials for provider authentication
→Session ManagerSessionManagerManages conversation history via pi-coding-agent library
→Context Enginecontext-engine-maintenance.tsMaintains context window boundaries
→Deliverycommand/delivery.tsRoutes completed payloads to target channels

Configuration

Knob / ParameterDefaultDescription
streamParams.temperatureundefinedLLM temperature override for response randomness
streamParams.maxTokensundefinedMaximum output token count per response
streamParams.fastModefalseEnables fast-mode processing with reduced quality
BASE_CHUNK_RATIO0.4Base ratio for splitting messages during compaction
MIN_CHUNK_RATIO0.15Minimum chunk ratio floor for compaction
SAFETY_MARGIN1.220% safety buffer for token count estimation
compaction.reserveTokens20000Token reserve floor for compaction trigger
compaction.keepRecentTokens—Recent tokens preserved during history compaction
thinkingoffThinking/reasoning level: off, minimal, low, medium, high, xhigh, adaptive
llmIdleTimeout—Timeout for idle LLM connections during streaming
STEER_ABORT_SETTLE_TIMEOUT_MS5000Abort signal settle timeout for stream termination

Directory Map

Source tree for streaming
pi-embedded-runner/
├── run.ts                                    ← Main orchestrator (6600+ lines)
├── types.ts                                  ← EmbeddedPiRunResult, meta types
├── abort.ts                                  ← Stream abort handling
├── openai-stream-wrappers.ts                 ← OpenAI SSE adapter
├── google-stream-wrappers.ts                 ← Google/Gemini adapter
├── anthropic-cache-retention.ts              ← Anthropic cache headers
├── anthropic-family-tool-payload-compat.ts   ← Anthropic tool compat
├── bedrock-stream-wrappers.ts                ← AWS Bedrock adapter
├── moonshot-stream-wrappers.ts               ← Moonshot adapter
├── moonshot-thinking-stream-wrappers.ts      ← Moonshot thinking
├── minimax-stream-wrappers.ts                ← Minimax adapter
├── proxy-stream-wrappers.ts                  ← Generic proxy
├── zai-stream-wrappers.ts                    ← Z AI adapter
├── usage-accumulator.ts                      ← Token usage tracking
├── usage-reporting.ts                        ← Usage report formatting
├── thinking.ts                               ← Thinking level negotiation
├── session-truncation.ts                     ← Session history truncation
├── tool-result-truncation.ts                 ← Tool result size limiting
├── tool-result-context-guard.ts              ← Context overflow guards
├── compact.ts                                ← Adaptive compaction
├── compact-reasons.ts                        ← Compaction trigger reasons
├── compaction-hooks.ts                       ← Compaction hook system
├── compaction-runtime-context.ts             ← Runtime compaction context
├── compaction-safety-timeout.ts              ← Compaction timeout guards
└── run/
    ├── attempt.ts                            ← Tool execution loop (2300+ lines)
    ├── attempt.tool-call-normalization.ts    ← Tool call normalization
    ├── attempt.tool-call-argument-repair.ts  ← JSON argument repair
    ├── attempt.stop-reason-recovery.ts       ← Stop reason recovery
    ├── payloads.ts                           ← Payload construction
    ├── setup.ts                              ← Model resolution
    └── helpers.ts                            ← Runtime utilities

Deep-Dive Highlights

Core streaming mechanisms have been extracted into dedicated focus pages for in-depth exploration. Each page covers one aspect of the streaming pipeline with diagrams, code walks, and configuration details.

Deep-Dive Pages

Chunk Assembly Focus

How streaming chunks from different providers are normalized and assembled into coherent response payloads.

Extracted topic page

Backpressure & Flow Control Focus

How the streaming pipeline manages flow control, context window limits, and adaptive compaction triggers.

Extracted topic page

Error Recovery Focus

How errors during streaming are categorized, handled, and recovered from via model fallback and retry strategies.

Extracted topic page

Provider Stream Formats

ProviderWrapper FileStream FormatSpecial Handling
OpenAIopenai-stream-wrappers.tsSSE with data: {choices}Standard delta chunking
Anthropicanthropic-cache-retention.tsSSE with content_block_deltaCache-retention headers, prompt caching
Google/Geminigoogle-stream-wrappers.tsGoogle-specific event streamSchema sanitization via cleanForGemini()
AWS Bedrockbedrock-stream-wrappers.tsAWS event stream binary framingIAM authentication, region routing
Moonshotmoonshot-stream-wrappers.tsOpenAI-compatible SSEDedicated thinking stream wrapper
Minimaxminimax-stream-wrappers.tsProvider-specific eventsCustom token counting
OpenRouterVia openai-stream-wrappers.tsOpenAI-compatible SSECapability flags from openrouter-model-capabilities.ts
Z AIzai-stream-wrappers.tsProvider-specific eventsCustom normalization
Proxyproxy-stream-wrappers.tsPass-throughGeneric wrapper for custom endpoints

Error Handling

The streaming pipeline handles errors at multiple levels:

Error TypeDetectionRecovery Action
Context OverflowestimateTokens() > context_window - reserveTrigger adaptive compaction
Rate LimitHTTP 429 or provider-specific signalCascade to fallback model
Auth FailureHTTP 401/403Mark auth profile in cooldown, try next
Stream Timeoutllm-idle-timeout.ts timer expiryAbort stream, retry with next provider
Malformed Tool ArgsJSON parse failureRepair via attempt.tool-call-argument-repair.ts
Unexpected StopStop reason mismatchRecovery via attempt.stop-reason-recovery.ts
Provider ErrorHTTP 5xx or connection resetCascade to fallback model
Abort SignalExternal cancellationGraceful shutdown via abort.ts (5s settle)

Thinking & Reasoning Levels

The streaming pipeline supports extended thinking/reasoning modes that route cognitive process output to a separate stream:

LevelDescriptionToken Impact
offNo thinking outputNone
minimalBrief reasoning hints~5% overhead
lowKey decision points only~10% overhead
mediumModerate reasoning detail~20% overhead
highFull reasoning chain~40% overhead
xhighMaximum thinking depth~60% overhead
adaptiveAuto-adjust based on complexityVariable

Code Examples

Example: Configuring Stream Parameters

const streamParams: AgentStreamParams = {
  temperature: 0.7,
  maxTokens: 4096,
  fastMode: false,
};

const opts: AgentCommandOpts = {
  message: "Explain the streaming architecture",
  provider: "anthropic",
  model: "claude-3-5-sonnet",
  streamParams,
  thinking: "medium",
  timeout: "120s",
};

Example: Compaction Token Strategy

// Adaptive compaction splits message history by token share
const BASE_CHUNK_RATIO = 0.4;
const MIN_CHUNK_RATIO = 0.15;
const SAFETY_MARGIN = 1.2; // 20% buffer for estimateTokens() inaccuracy

function splitMessagesByTokenShare(
  messages: AgentMessage[],
  ratio: number,
  contextWindow: number,
): AgentMessage[][] {
  // Splits message history by token ratio for adaptive compaction
  // First chunk gets BASE_CHUNK_RATIO of context window
  // Each subsequent chunk gets at least MIN_CHUNK_RATIO
  // SAFETY_MARGIN accounts for token estimation inaccuracy
}

Example: EmbeddedPiRunResult Structure

// The complete result from a streaming agent run
type EmbeddedPiRunResult = {
  payloads: Array<{
    text?: string;
    mediaUrl?: string;
    mediaUrls?: string[];
    replyToId?: string;
    isError?: boolean;
    isReasoning?: boolean;
  }>;
  usage: {
    inputTokens: number;
    outputTokens: number;
    cacheCreationTokens?: number;
    cacheReadTokens?: number;
  };
  meta: EmbeddedPiRunMeta;
  toolSends: ToolSendEntry[];
  compacted: boolean;
  pendingTools: PendingToolEntry[];
};