Skip to content

fix(agent-runtime): handle all content_block_start and delta subtypes in normalizeContentBlocks#430

Merged
jerryliang64 merged 3 commits intomasterfrom
fix-normalize-content-blocks
Apr 1, 2026
Merged

fix(agent-runtime): handle all content_block_start and delta subtypes in normalizeContentBlocks#430
jerryliang64 merged 3 commits intomasterfrom
fix-normalize-content-blocks

Conversation

@jerryliang64
Copy link
Copy Markdown
Contributor

@jerryliang64 jerryliang64 commented Apr 1, 2026

Summary

  • content_block_start: only extract tool_use, discard others (thinking, text, etc.)
  • content_block_delta: only extract text_delta and input_json_delta, discard others (thinking_delta, signature_delta, etc.)

Fixes missing cases discovered during integration testing with Claude Agent SDK.

Test plan

  • New test: discard content_block_start[thinking] and content_block_start[text]
  • New test: discard signature_delta
  • All 140 tests pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved streaming message processing to a two-phase flow that unwraps streamed events and strictly filters content so only text, tool usage, tool results, and explicit "thinking" blocks are retained; unknown or control-signal deltas are discarded.
  • Tests

    • Expanded tests to cover additional streamed-event scenarios, including preservation of "thinking" blocks and discarding of unknown delta types.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

Warning

Rate limit exceeded

@jerryliang64 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 26 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 4 minutes and 26 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d64f6097-aaff-4af0-b4a0-dd2b29d77eb7

📥 Commits

Reviewing files that changed from the base of the PR and between ee2429f and b0ca5b7.

📒 Files selected for processing (2)
  • core/agent-runtime/src/MessageConverter.ts
  • core/agent-runtime/test/MessageConverter.test.ts
📝 Walkthrough

Walkthrough

Refactors MessageConverter.normalizeContentBlocks to a two-phase normalization: first unwraps Anthropic streaming protocol events into concrete content blocks (extracting tool_use and converting specific content_block_delta subtypes), then applies a whitelist keeping only Text, ToolUse, ToolResult, and literal thinking blocks; tests expanded accordingly.

Changes

Cohort / File(s) Summary
Message conversion logic
core/agent-runtime/src/MessageConverter.ts
Reworked normalizeContentBlocks into a two-phase pass: (1) unwrap streaming events (extract tool_use from content_block_start, convert text_delta, input_json_delta, thinking_delta into content blocks, discard other delta/control types); (2) apply a whitelist to retain only ContentBlockType.Text, ContentBlockType.ToolUse, ContentBlockType.ToolResult, and 'thinking'.
Tests
core/agent-runtime/test/MessageConverter.test.ts
Expanded unit tests to cover the new two-phase behavior: ensure non-tool_use content_block_start events are dropped, thinking_delta becomes a thinking block, unknown delta.type (e.g., signature_delta) are discarded, and merged output ordering/length reflects the kept thinking block.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • akitaSummer

Poem

🐰 I nibbled bytes and hopped through streams,
Unwrapped the blocks and chased their dreams.
I kept the tools and thinking bright,
Tossed the noise into the night —
Hooray, the message carrots gleam! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: handling all content_block_start and delta subtypes in the normalizeContentBlocks function, which is the core focus of both modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-normalize-content-blocks

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 and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request modifies the MessageConverter to explicitly discard content_block_start events that are not tool-use related and catch-all content_block_delta events like thinking_delta and signature_delta. It also includes unit tests to verify that these specific event types are correctly filtered out. I have no feedback to provide.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/agent-runtime/src/MessageConverter.ts (1)

130-156: ⚠️ Potential issue | 🟠 Major

Normalize before emitting live stream deltas.

This only fixes the final mergeContentBlocks() path. In core/agent-runtime/src/AgentRuntime.ts:343-396, consumeStreamMessages() still sends MessageConverter.toContentBlocks(msg.message) straight to thread.message.delta, so content_block_start[thinking|text] and signature_delta can still leak to streaming clients even though the final merged message drops them.

💡 One low-risk fix is to normalize array payloads inside toContentBlocks()
 static toContentBlocks(msg: AgentStreamMessagePayload): MessageContentBlock[] {
   if (!msg) return [];
   const content = msg.content;
   if (typeof content === 'string') {
     return [{ type: ContentBlockType.Text, text: { value: content, annotations: [] } }];
   }
   if (Array.isArray(content)) {
-    return content.map(part => {
+    const blocks = content.map(part => {
       if (part.type === ContentBlockType.Text) {
         return {
           type: ContentBlockType.Text,
           text: { value: (part as TextInputContentPart).text, annotations: [] },
         } as MessageContentBlock;
       }
       return part as MessageContentBlock;
     });
+    return MessageConverter.normalizeContentBlocks(blocks);
   }
   return [];
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/agent-runtime/src/MessageConverter.ts` around lines 130 - 156, The
streaming path leaks unwanted content blocks because
MessageConverter.toContentBlocks is not normalizing/filtering the array payloads
before they are emitted (so AgentRuntime.consumeStreamMessages can pass through
content_block_start of non-ToolUse and non-text deltas); update
MessageConverter.toContentBlocks to apply the same normalization rules used by
mergeContentBlocks: drop content_block_start entries unless content_block.type
=== ContentBlockType.ToolUse, convert only input_json_delta and text_delta into
TextContentBlock (discard other delta types like
thinking_delta/signature_delta), and ensure the output array contains only
ToolUseContentBlock and TextContentBlock entries so streaming consumers (e.g.,
the path in AgentRuntime.consumeStreamMessages) never receive disallowed block
types.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@core/agent-runtime/src/MessageConverter.ts`:
- Around line 130-156: The streaming path leaks unwanted content blocks because
MessageConverter.toContentBlocks is not normalizing/filtering the array payloads
before they are emitted (so AgentRuntime.consumeStreamMessages can pass through
content_block_start of non-ToolUse and non-text deltas); update
MessageConverter.toContentBlocks to apply the same normalization rules used by
mergeContentBlocks: drop content_block_start entries unless content_block.type
=== ContentBlockType.ToolUse, convert only input_json_delta and text_delta into
TextContentBlock (discard other delta types like
thinking_delta/signature_delta), and ensure the output array contains only
ToolUseContentBlock and TextContentBlock entries so streaming consumers (e.g.,
the path in AgentRuntime.consumeStreamMessages) never receive disallowed block
types.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2738c51f-085c-438c-8864-98b3c4461ed8

📥 Commits

Reviewing files that changed from the base of the PR and between b21b281 and 4824417.

📒 Files selected for processing (2)
  • core/agent-runtime/src/MessageConverter.ts
  • core/agent-runtime/test/MessageConverter.test.ts

…ing support

- Restructure normalizeContentBlocks with two-phase approach:
  1. Unwrap streaming protocol events to extract actual content
  2. Whitelist filter — only keep known content block types
- Whitelist: text, tool_use, tool_result, thinking
- content_block_start: only extract tool_use, discard others
- content_block_delta: extract text_delta, input_json_delta, thinking_delta; discard others (signature_delta, etc.)
- Unknown block types (ping, future events) discarded by whitelist

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jerryliang64 jerryliang64 force-pushed the fix-normalize-content-blocks branch from 4824417 to ee2429f Compare April 1, 2026 18:00
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
core/agent-runtime/src/MessageConverter.ts (1)

121-127: Consider defining 'thinking' as a named constant.

The 'thinking' magic string is used both here and at line 168. While the other types come from ContentBlockType enum, 'thinking' is a string literal. Consider defining it as a constant (e.g., const THINKING_BLOCK_TYPE = 'thinking') to avoid duplication and reduce maintenance risk.

♻️ Suggested refactor
+const THINKING_BLOCK_TYPE = 'thinking';
+
 /** Content block types allowed in the final assembled message. */
 private static readonly ALLOWED_BLOCK_TYPES = new Set([
   ContentBlockType.Text,     // text
   ContentBlockType.ToolUse,  // tool_use
   ContentBlockType.ToolResult, // tool_result
-  'thinking',                // extended thinking
+  THINKING_BLOCK_TYPE,       // extended thinking
 ]);

And at line 168:

-        unwrapped.push({ type: 'thinking', thinking } as unknown as MessageContentBlock);
+        unwrapped.push({ type: THINKING_BLOCK_TYPE, thinking } as unknown as MessageContentBlock);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/agent-runtime/src/MessageConverter.ts` around lines 121 - 127, The
'thinking' magic string is duplicated; define a single named constant (e.g.,
THINKING_BLOCK_TYPE) and use it wherever the literal appears to avoid drift—add
the constant (either top-level or as a static member) and replace the literal in
MessageConverter.ALLOWED_BLOCK_TYPES and the other usage referenced in the diff
so both refer to THINKING_BLOCK_TYPE instead of the string literal, keeping
other ContentBlockType enum uses unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@core/agent-runtime/src/MessageConverter.ts`:
- Around line 121-127: The 'thinking' magic string is duplicated; define a
single named constant (e.g., THINKING_BLOCK_TYPE) and use it wherever the
literal appears to avoid drift—add the constant (either top-level or as a static
member) and replace the literal in MessageConverter.ALLOWED_BLOCK_TYPES and the
other usage referenced in the diff so both refer to THINKING_BLOCK_TYPE instead
of the string literal, keeping other ContentBlockType enum uses unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9b410e84-3e8f-4c67-9119-633392577be3

📥 Commits

Reviewing files that changed from the base of the PR and between 4824417 and ee2429f.

📒 Files selected for processing (2)
  • core/agent-runtime/src/MessageConverter.ts
  • core/agent-runtime/test/MessageConverter.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/agent-runtime/test/MessageConverter.test.ts

jerryliang64 and others added 2 commits April 2, 2026 02:11
…Blocks

Previously each thinking_delta from Anthropic streaming became a separate
thinking block in the final message. Now consecutive thinking blocks are
merged into one, matching the same pattern used for text block merging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jerryliang64 jerryliang64 merged commit 119ba38 into master Apr 1, 2026
16 of 18 checks passed
@jerryliang64 jerryliang64 deleted the fix-normalize-content-blocks branch April 1, 2026 18:33
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.

1 participant