Skip to content

feat(core,chat): stream partial assistant-text deltas on the SDK runtime - #383

Merged
edspencer merged 1 commit into
mainfrom
feat/stream-partial-assistant-text
Jul 19, 2026
Merged

feat(core,chat): stream partial assistant-text deltas on the SDK runtime#383
edspencer merged 1 commit into
mainfrom
feat/stream-partial-assistant-text

Conversation

@edspencer

@edspencer edspencer commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #382 — the SDK runtime and the @herdctl/chat translator surface assistant text only as whole assistant messages, so consumers (e.g. Paddock's chat UI) render each reply in one drop rather than streaming token-by-token. This adds true streaming of partial assistant text through the stack, behind an opt-in flag.

Two independent gaps, both fixed:

1. SDK runtime never requested partials (@herdctl/core)

  • New opt-in includePartialMessages on ChatSessionOptionsRuntimeExecuteOptionsSDKQueryOptions, threaded from openChatSession into the SDK query() in SDKRuntime.buildSdkOptions().
  • Default off. Batch/one-shot (execute) and existing session callers that don't opt in are byte-for-byte unchanged — the SDK still yields whole assistant messages only.

2. Translator dropped stream events (@herdctl/chat)

  • SDKMessageTranslator.handle() now routes type: "stream_event" messages to a new handleStreamEvent(), which emits incremental onText(delta) for content_block_delta / text_delta events (other events — message start/stop, tool input-json deltas, thinking deltas — are ignored).
  • The terminal whole-assistant message suppresses its text re-emit when that text already streamed as deltas (tracked per-message), so the onText contract stays "deltas, in order" and consumers never receive the text twice. Tool tracking (onToolStart/onToolCall) still runs on the terminal message.
  • Boundary parity: the first text delta of a new assistant message applies the same "new turn after prior text → boundary" rule as the whole-message path; a tool result still resets the run so post-tool text starts a fresh bubble with no boundary.
  • Partials-off path is unchanged (no stream_event messages arrive, so state never engages).

Verified against the installed @anthropic-ai/claude-agent-sdk@0.3.205 types: SDKPartialAssistantMessage = { type: "stream_event", event: BetaRawMessageStreamEvent, parent_tool_use_id, … }, where a BetaRawContentBlockDeltaEvent (type: "content_block_delta") carries a BetaTextDelta ({ type: "text_delta", text }).

Tests

  • core (sdk-runtime-partials.test.ts): query() receives includePartialMessages: true when opted in via execute()/openSession(), and it's unset by default.
  • chat (sdk-message-translator.test.ts): text_delta events produce ordered incremental onText; the terminal assistant message does not re-emit already-streamed text; tool tracking still fires on the terminal message; non-text stream events are ignored; boundary parity across streamed turns and after tool results; attribution threading.

All core (3352 pass; the lone directory.test.ts "unwritable dir" failure is the known pre-existing root-only one) and chat (289 pass) suites green; typecheck + build clean.

Consumer

Paddock consumes this to stream keeper replies token-by-token (companion ticket edspencer/paddock#315).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added opt-in partial assistant message streaming for token-by-token text updates.
    • Streaming responses now preserve text order, tool attribution, and message boundaries.
    • Existing behavior remains unchanged unless partial message streaming is enabled.
  • Bug Fixes

    • Prevented streamed assistant text from being emitted again when the final message arrives.
    • Ignored unrelated streaming events that do not contain assistant text.

Adds true token-by-token streaming of assistant text through the stack, gated
by an opt-in flag so batch/one-shot and existing session callers are unchanged.

- core: `includePartialMessages` on ChatSessionOptions / RuntimeExecuteOptions /
  SDKQueryOptions, threaded from openChatSession into the SDK query(). Default off.
- chat: SDKMessageTranslator handles `stream_event` `text_delta` events as
  ordered incremental onText(delta) calls, and suppresses the terminal whole
  assistant text re-emit when it already streamed as deltas. Boundary + tool
  semantics preserved; partials-off path byte-for-byte identical.

Fixes #382

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 46ee89b9-afb1-4179-a121-93fe7debc1e3

📥 Commits

Reviewing files that changed from the base of the PR and between 0cf4673 and 1098aac.

📒 Files selected for processing (10)
  • .changeset/stream-partial-assistant-text.md
  • packages/chat/src/__tests__/sdk-message-translator.test.ts
  • packages/chat/src/message-extraction.ts
  • packages/chat/src/sdk-message-translator.ts
  • packages/core/src/fleet-manager/job-control.ts
  • packages/core/src/fleet-manager/types.ts
  • packages/core/src/runner/runtime/__tests__/sdk-runtime-partials.test.ts
  • packages/core/src/runner/runtime/interface.ts
  • packages/core/src/runner/runtime/sdk-runtime.ts
  • packages/core/src/runner/types.ts

📝 Walkthrough

Walkthrough

Adds opt-in partial assistant text streaming from chat sessions through the SDK runtime, translates stream_event text deltas into ordered onText callbacks, preserves attribution and boundaries, and prevents duplicate terminal assistant text emission.

Changes

Partial assistant text streaming

Layer / File(s) Summary
Runtime option propagation and SDK configuration
packages/core/src/fleet-manager/types.ts, packages/core/src/fleet-manager/job-control.ts, packages/core/src/runner/runtime/interface.ts, packages/core/src/runner/types.ts, packages/core/src/runner/runtime/sdk-runtime.ts, packages/core/src/runner/runtime/__tests__/*, .changeset/stream-partial-assistant-text.md
Adds the optional includePartialMessages setting, forwards it through chat sessions and runtime execution to SDK queries, and tests enabled and default behavior.
Stream event extraction and translation
packages/chat/src/message-extraction.ts, packages/chat/src/sdk-message-translator.ts, packages/chat/src/__tests__/sdk-message-translator.test.ts
Extracts text deltas from stream events, emits ordered incremental text with attribution and boundary handling, ignores non-text deltas, and suppresses duplicate terminal assistant text while retaining tool tracking.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

  • edspencer/herdctl issue 382: Directly covers the partial-message plumbing and translator streaming behavior implemented here.

Possibly related PRs

  • edspencer/herdctl#286: Modifies the same chat-session and runtime session pipeline extended with includePartialMessages.
  • edspencer/herdctl#298: Modifies the same translator and extraction code involved in streamed text attribution.
  • edspencer/herdctl#372: Changes assistant event translation in the same translator and test areas.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: opt-in streaming of partial assistant-text deltas through core, chat, and the SDK runtime.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/stream-partial-assistant-text

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying herdctl with  Cloudflare Pages  Cloudflare Pages

Latest commit: 1098aac
Status: ✅  Deploy successful!
Preview URL: https://44d2a206.herdctl.pages.dev
Branch Preview URL: https://feat-stream-partial-assistan.herdctl.pages.dev

View logs

@edspencer
edspencer merged commit ba61918 into main Jul 19, 2026
8 checks passed
@edspencer
edspencer deleted the feat/stream-partial-assistant-text branch July 19, 2026 16:09
@github-actions github-actions Bot mentioned this pull request Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stream partial assistant-text deltas on the SDK runtime (includePartialMessages + translator)

1 participant