Playground Streaming and Content Block Support#
Agenta has two separate playground surfaces with different streaming architectures:
- OSS Playground (
web/oss/src/components/Playground/) β the prompt-engineering playground used with LLM variants. Runs are dispatched via a Web Worker and use a fire-and-forgetfetchthat awaitsresponse.json()in a single shot β no streaming. - Agent Playground (
web/oss/src/components/AgentChatSlice/) β the chat interface for agent variants. Uses the Vercel AI SDK (ai@6) withuseChatand a full SSE streaming pipeline, including a streamβbatch negotiation fallback.
The gap between these two surfaces is the source of known rendering deficiencies for structured content block responses (e.g., Anthropic's thinking blocks).
OSS Playground: Batch-Only Execution#
The OSS playground runs every variant input through playground.worker.ts, which issues a standard POST /test request and calls await response.json() . There is no SSE/streaming path. When the response arrives, the result is handed back to the main thread via postMessage and merged into state by playgroundVariantMiddleware .
Content block type system: The shared Message interface types content as a plain string β there is no union type for content block arrays ({type: "text"|"thinking"|"tool_use", ...}[]). This means that when an LLM returns a content array (e.g., Anthropic's extended thinking output), the OSS playground has no first-class representation for it.
Response handling on result: When handleWebWorkerChatMessage receives a response, it calls createMessageFromSchema to build the message from the raw payload. This function initializes content as an empty string by default and only has special handling for toolCalls arrays . Content block arrays from the response data field pass through createMessageFromSchema but end up as-is in the value field β there is no normalization to extract text content from blocks.
Rendering in PromptMessageConfig: The component renders message content through a Lexical-based SharedEditor. It detects tool calls via isTool and renders JSON (codeOnly mode), but has no branch for content block arrays. If content arrives as an array of objects, it is passed as-is to the editor, which will render it as a raw JSON blob or fail to display it meaningfully. The checkIsJSON helper would detect the array as JSON and force codeOnly mode β so a thinking block response would show as raw JSON rather than as structured, human-readable thinking output.
Agent Playground: Streaming + Content Block Support#
The agent playground uses AgentChatTransport (subclassing Vercel AI SDK's DefaultChatTransport) with two response channels:
- Stream mode (default): Sends
Accept: text/event-stream, receives SSE chunks conforming toai@6'suiMessageChunkSchema. Content parts includetext,reasoning(thinking), and tool parts. - Batch mode (fallback): On HTTP 406 from the backend,
createNegotiatingFetchre-issues the request withAccept: application/json, receives a singleWorkflowBatchResponse, and replays it as a one-shotUIMessageChunkstream .
The AgentChatTransport batch path calls normalizeToParts to extract content from data.outputs, handling {role, content} where content may be a string or a list of content blocks . This means tool-use and reasoning blocks in batch responses are replayed best-effort as tool-input-available/tool-output-available chunks.
On the Python backend, the Vercel adapter (sdks/python/agenta/sdk/agents/adapters/vercel/stream.py) normalizes every part at the yield boundary via _conform() before emitting it to the SSE wire . This prevents null optional fields from violating ai@6's required-string schema slots, and emits an explicit error frame when a run produces zero content parts.
Key Gaps in the OSS Playground#
| Capability | OSS Playground | Agent Playground |
|---|---|---|
| Streaming (token-by-token) | β Not implemented | β
SSE via useChat + AgentChatTransport |
Content block array in content | β Rendered as raw JSON via codeOnly | β
normalizeToParts extracts text/reasoning/tool blocks |
| Anthropic thinking blocks | β Not recognized; shown as JSON blob | β
Mapped to reasoning part type |
Message type supports block arrays | β content: string only | β Handled at runtime via block normalization |
| Streamβbatch fallback | N/A (always batch) | β
createNegotiatingFetch on HTTP 406 |
Relevant Source Files#
| File | Role |
|---|---|
playground.worker.ts | OSS run dispatcher β issues POST /test, no streaming |
message.d.ts | Shared Message type β content: string, no block union |
messageHelpers.ts | createMessageFromSchema β populates message fields from response, no block normalization |
playgroundVariantMiddleware.ts | Merges Web Worker result into state; calls createMessageFromSchema |
PromptMessageConfig/index.tsx | Renders message content; falls back to codeOnly JSON mode for non-string content |
AgentChatTransport.ts | Agent playground transport β stream/batch, normalizeToParts for content blocks |
stream.py | Python Vercel adapter β _conform() gate, SSE part emission, error frames |
Adding Streaming / Block Support to the OSS Playground#
Bringing the OSS playground to parity requires changes at multiple layers:
- Type layer: Extend
Message.contentto acceptstring | ContentBlock[]whereContentBlockcovers{type: "text"|"thinking"|"tool_use", ...}. - Worker layer: Replace the batch
fetch+response.json()inplayground.worker.tswith an SSE consumer that emits incremental chunks back to the main thread. Alternatively, add a post-processing step that normalizes a content block array response into structured parts beforepostMessage. - State layer: Update
createMessageFromSchemainmessageHelpers.tsto handle content block arrays β extractingtextblocks intocontent, and preservingthinkingblocks in a separate field. - Rendering layer: Add a content block renderer to
PromptMessageConfig(orGenerationCompletionRow/GenerationChatRowOutput) that displays thinking blocks in a collapsible "Thinking" section, mirroring how the agent playground rendersreasoningparts.