Skip to content

refactor(agent-runtime): align message format with Claude Agent SDK#433

Merged
jerryliang64 merged 3 commits intomasterfrom
worktree-align-sdk-message-format
Apr 9, 2026
Merged

refactor(agent-runtime): align message format with Claude Agent SDK#433
jerryliang64 merged 3 commits intomasterfrom
worktree-align-sdk-message-format

Conversation

@jerryliang64
Copy link
Copy Markdown
Contributor

@jerryliang64 jerryliang64 commented Apr 9, 2026

Summary

  • Replace custom OpenAI Assistants-style message types (MessageObject, ContentBlockType, MessageRole, MessageStatus, etc.) with a lightweight SDK-aligned AgentMessage union type (system | stream_event | user | assistant | result | generic)
  • Remove RunObject.output / RunRecord.output — messages now belong to Thread only (aligned with SDK's session model)
  • Simplify MessageConverter from ~300 lines to ~50 lines: only extractUsage() and toAgentMessages() remain
  • OSSAgentStore.getThread() now filters out system messages by default, with includeSystemMessages option
  • execRun yields AgentMessage directly (was AgentStreamMessage), SSE streaming passes SDK messages through as-is

Breaking Changes

  • AgentStreamMessage type removed → use AgentMessage
  • MessageObject, ContentBlockType, MessageRole, MessageStatus removed
  • RunObject.output removed — query thread messages via getThread() instead
  • MessageConverter.toContentBlocks, toMessageObject, createStreamMessage, extractFromStreamMessages, normalizeContentBlocks, mergeContentBlocks, toInputMessageObjects removed
  • isTextBlock, isToolUseBlock, isToolResultBlock, isThinkingBlock type guards removed
  • RunBuilder.complete() signature changed: complete(usage?) instead of complete(output, usage?)

Test plan

  • All core/agent-runtime tests pass (112/113, 1 pre-existing timeout flake in reconnect test)
  • All core/controller-decorator tests pass (74/74)
  • TypeScript compilation clean for core/agent-runtime

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Breaking Changes

    • Run objects no longer include an output field; completed runs record status, timestamps, usage and metadata only.
    • Public message shapes and APIs moved to SDK-aligned message objects (older content-block/message types removed).
  • Behavior Changes

    • Thread retrieval filters to user/assistant messages by default; an option returns all message types.
    • Streaming/SSE now forwards SDK message objects as event data and uses message type as the SSE event name.
    • Usage (token) accounting is extracted from typed result messages; streamed events are persisted directly.

Replace custom OpenAI Assistants-style message types (MessageObject,
ContentBlockType, MessageRole, etc.) with a lightweight SDK-aligned
AgentMessage union type. Key changes:

- Define AgentMessage as union of SDK message subtypes (system,
  stream_event, user, assistant, result, generic)
- Remove RunObject/RunRecord.output — messages belong to Thread only
- Simplify MessageConverter to only extractUsage and toAgentMessages
- OSSAgentStore.getThread filters system messages by default
- execRun now yields AgentMessage directly instead of AgentStreamMessage
- Update all tests to match new types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

Replaces legacy stream/content-block types with SDK-aligned AgentMessage across runtime, converter, store, types, and tests; run completion no longer stores output and usage is extracted from result-typed AgentMessages; getThread gains optional GetThreadOptions.

Changes

Cohort / File(s) Summary
Runtime core
core/agent-runtime/src/AgentRuntime.ts
Exec contract now yields AgentMessage; runtime buffers AgentMessages, extracts usage from result messages, persists streamed messages directly, and completes runs using usage (removed output-based completion).
Message conversion
core/agent-runtime/src/MessageConverter.ts
Removed content-block assembly/merge and stream->MessageObject conversion logic. Added extractUsage(messages: AgentMessage[]), toAgentMessages(InputMessage[]), and filterForStorage(messages).
Run builder
core/agent-runtime/src/RunBuilder.ts
Dropped output from builder state and APIs: complete() no longer accepts/returns output; snapshots/records no longer include output.
Storage / Store
core/agent-runtime/src/OSSAgentStore.ts, core/types/agent-runtime/AgentStore.ts
Store APIs now operate on AgentMessage[]; appendMessages serializes JSONL AgentMessage lines; getThread(threadId, options?) added and filters messages by default unless includeAllMessages is set.
Types & re-exports
core/types/agent-runtime/AgentMessage.ts, core/types/agent-runtime/AgentRuntime.ts, core/tegg/agent.ts
Replaced MessageObject/content-block types with SDK-style message interfaces and AgentMessage union; removed role/status/SSE enums; added GetThreadOptions; updated re-exports.
Controller interface / fixtures
core/controller-decorator/src/decorator/agent/AgentHandler.ts, core/controller-decorator/test/fixtures/AgentFooController.ts
Updated execRun signature to AsyncGenerator<AgentMessage> and adjusted imports/types.
Tests
core/agent-runtime/test/*, core/controller-decorator/test/*
Adapted tests to SDK AgentMessage envelopes: changed streamed generator typings, simplified content-block tests, assert message type and usage from result messages, and added getThread filtering tests.
Misc
multiple files
Removed comment-only blocks and updated imports/types to propagate AgentMessage changes.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client/API
  participant Handler as AgentHandler
  participant Runtime as AgentRuntime
  participant Converter as MessageConverter
  participant Store as OSSAgentStore
  participant SSE as SSE/EventEmitter

  rect rgba(200,220,255,0.5)
  Client->>Handler: create run / call execRun
  Handler->>Runtime: invoke execRun (AsyncGenerator<AgentMessage>)
  end

  rect rgba(200,255,200,0.5)
  Runtime->>Handler: pull yielded AgentMessage*
  Runtime->>Converter: buffer messages -> extractUsage()
  Runtime->>Store: appendMessages(threadId, AgentMessage[])
  Runtime->>SSE: pushEvent(AgentMessage)  -- pass-through event data
  end

  rect rgba(255,220,200,0.5)
  Converter-->>Runtime: usage (from result messages) or undefined
  Runtime->>Runtime: complete run with usage
  Runtime->>Store: updateRun(status, usage, timestamps)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • killagu
  • akitaSummer

Poem

🐰 I hopped through streams and JSON lines,
Swapped clumps of blocks for clearer signs.
Tokens counted from result light,
Threads now keep messages just right.
A tiny twitch — clean code, bright nights! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main refactoring: aligning the agent-runtime message format with the Claude Agent SDK, which is the central change across all modified files.

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

✨ 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 worktree-align-sdk-message-format

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

@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.

Actionable comments posted: 1

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/AgentRuntime.ts (1)

95-100: ⚠️ Potential issue | 🟠 Major

Resume detection now misses system-only sessions.

At Line 97, ensureThread() calls getThread(input.threadId) without options. Since OSSAgentStore.getThread() now filters system messages by default, Line 98 can set isResume = false even when a session already exists with only system messages.

💡 Proposed fix
  private async ensureThread(input: CreateRunInput): Promise<{ threadId: string; input: CreateRunInput }> {
    if (input.threadId) {
-      const thread = await this.store.getThread(input.threadId);
+      const thread = await this.store.getThread(input.threadId, { includeSystemMessages: true });
       const isResume = thread.messages.length > 0;
       return { threadId: input.threadId, input: { ...input, isResume } };
    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/agent-runtime/src/AgentRuntime.ts` around lines 95 - 100, ensureThread
currently calls this.store.getThread(input.threadId) which returns a thread with
system messages filtered out, causing isResume to be false for sessions that
only contain system messages; fix by calling getThread with the option to
include system messages (e.g., this.store.getThread(input.threadId, {
includeSystemMessages: true })) inside ensureThread so the subsequent isResume =
thread.messages.length > 0 reflects system-only sessions as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/agent-runtime/src/MessageConverter.ts`:
- Around line 43-49: The toAgentMessages conversion in MessageConverter
currently coerces all non-system InputMessage entries to type: 'user', which
loses assistant turns; update MessageConverter.toAgentMessages to map
InputMessage.role === 'assistant' to type: 'assistant' and InputMessage.role ===
'user' to type: 'user' (skip 'system' as before) and keep message.payload as {
role, content } so assistant turns round-trip correctly; also update the
corresponding assistant-role unit test to expect AgentMessage.type ===
'assistant' for inputs with role 'assistant' (adjust test assertions referencing
toAgentMessages or MessageConverter).

---

Outside diff comments:
In `@core/agent-runtime/src/AgentRuntime.ts`:
- Around line 95-100: ensureThread currently calls
this.store.getThread(input.threadId) which returns a thread with system messages
filtered out, causing isResume to be false for sessions that only contain system
messages; fix by calling getThread with the option to include system messages
(e.g., this.store.getThread(input.threadId, { includeSystemMessages: true }))
inside ensureThread so the subsequent isResume = thread.messages.length > 0
reflects system-only sessions as well.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7e95c7d0-2f37-4bea-99e2-ff9543c4a262

📥 Commits

Reviewing files that changed from the base of the PR and between 44a6800 and 7b4c500.

📒 Files selected for processing (14)
  • core/agent-runtime/src/AgentRuntime.ts
  • core/agent-runtime/src/MessageConverter.ts
  • core/agent-runtime/src/OSSAgentStore.ts
  • core/agent-runtime/src/RunBuilder.ts
  • core/agent-runtime/test/AgentRuntime.test.ts
  • core/agent-runtime/test/MessageConverter.test.ts
  • core/agent-runtime/test/OSSAgentStore.test.ts
  • core/agent-runtime/test/RunBuilder.test.ts
  • core/controller-decorator/src/decorator/agent/AgentHandler.ts
  • core/controller-decorator/test/fixtures/AgentFooController.ts
  • core/tegg/agent.ts
  • core/types/agent-runtime/AgentMessage.ts
  • core/types/agent-runtime/AgentRuntime.ts
  • core/types/agent-runtime/AgentStore.ts

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 refactors the agent runtime to align its message format with SDK standards, replacing internal message objects with a unified AgentMessage type. Key changes include simplifying the MessageConverter logic, removing the output field from run records, and updating OSSAgentStore to support system message filtering. Feedback points out that toAgentMessages incorrectly maps assistant roles to the user type and suggests restoring architectural documentation in the storage layer to preserve context on atomicity and storage layout.

Comment thread core/agent-runtime/src/MessageConverter.ts
Comment thread core/agent-runtime/src/OSSAgentStore.ts
jerryliang64 and others added 2 commits April 9, 2026 16:48
…nment

- Fix toAgentMessages to map type from role instead of hardcoding 'user'
- Change getThread default filter to only return user+assistant messages
  (aligned with SDK's getSessionMessages behavior)
- Rename includeSystemMessages to includeAllMessages for clarity
- Pass through GetThreadOptions in AgentRuntime.getThread
- Restore precise test assertions for message counts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…to thread

Stream events are incremental token-level deltas only useful during
real-time streaming. The final assistant message already contains the
complete response, so storing deltas wastes space with no read-time
value. Other message types (system, result) are preserved for audit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/agent-runtime/src/AgentRuntime.ts`:
- Around line 207-211: asyncRun() and executeStreamBackground() currently append
raw streamMessages (via this.store.appendMessages) which causes per-token
stream_event deltas to be persisted; update both methods to filter messages for
storage before appending by running the combined messages through
MessageConverter.filterForStorage (the same helper used in syncRun()), i.e.,
replace appends of [...MessageConverter.toAgentMessages(...), ...streamMessages]
with
MessageConverter.filterForStorage([...MessageConverter.toAgentMessages(input.input.messages),
...streamMessages]) and pass that result to this.store.appendMessages(threadId,
...); ensure you use the same MessageConverter.filterForStorage utility and keep
existing use of MessageConverter.toAgentMessages for the input messages.

In `@core/agent-runtime/src/MessageConverter.ts`:
- Around line 53-59: The toAgentMessages conversion in
MessageConverter.toAgentMessages drops InputMessage.metadata so thread history
loses caller-supplied metadata; update MessageConverter.toAgentMessages to copy
metadata through into the AgentMessage.message (preserving existing role and
content) so AgentMessage.message includes metadata from InputMessage.metadata
(e.g., retain m.metadata when building the returned object) and ensure any
downstream consumers of AgentMessage.message still receive the metadata field.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9c5700e2-ebf4-4ec4-a8a4-50f82502f19e

📥 Commits

Reviewing files that changed from the base of the PR and between 8084e8d and 2070dc0.

📒 Files selected for processing (3)
  • core/agent-runtime/src/AgentRuntime.ts
  • 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

Comment thread core/agent-runtime/src/AgentRuntime.ts
Comment thread core/agent-runtime/src/MessageConverter.ts
Copy link
Copy Markdown
Contributor

@akitaSummer akitaSummer left a comment

Choose a reason for hiding this comment

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

1

@jerryliang64 jerryliang64 merged commit d4c07d6 into master Apr 9, 2026
12 checks passed
@jerryliang64 jerryliang64 deleted the worktree-align-sdk-message-format branch April 9, 2026 09:59
jerryliang64 added a commit that referenced this pull request Apr 9, 2026
…434)

## Summary

- Fix missing `filterForStorage` in `asyncRun` and
`executeStreamBackground` methods
- Previously only `syncRun` filtered out `stream_event` deltas before
persisting to thread storage
- This caused `stream_event` messages (incremental token-level deltas)
to leak into thread history, wasting storage space

## Context

Follow-up fix for #433. During local testing, thread history still
contained `stream_event` messages because two out of three
`appendMessages` call sites were not applying the
`MessageConverter.filterForStorage()` filter.

## Test plan

- [x] All 116 existing tests pass
- [x] Verified locally that thread messages no longer contain
`stream_event` entries

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved message persistence to exclude unnecessary stream metadata,
resulting in cleaner and more efficient storage of conversation history.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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.

2 participants