feat: add toJSON() to all streaming events for wire-safe serialization#708
Conversation
|
@agent-of-mkmeral do we send all of these events through stream? we don't need to do it for hooks, only streamed events |
|
Good question @mkmeral! Yes — in the current architecture, all 16 events flow through Here's the relevant code from public async *stream(args, options) {
const streamGenerator = this._stream(args, options)
let result = await streamGenerator.next()
while (!result.done) {
const event = result.value
// Hooks fire on the event
if (event instanceof HookableEvent) {
await this._hooksRegistry.invokeCallbacks(event)
}
this._printer?.processEvent(event)
yield event // ← every event is also yielded to the stream consumer
result = await streamGenerator.next()
}
// ...
}So when a user does That said, you raise a valid design point. We could scope
But even lifecycle events carry Would you prefer I:
Happy to adjust either way! 👍 |
Address review feedback from @zastrowm to use Pick<EventClass, 'field'> instead of inline type literals. This keeps return types in sync with class definitions automatically. - Simple events: Pick<EventClass, 'type'> - Data events: Pick<EventClass, 'type' | 'field1' | 'field2'> - Error-transforming events: Pick<...> & { error?: string } (since Error is serialized as its .message string)
|
/strands review |
|
Assessment: Request Changes This PR implements wire-safe serialization for streaming events cleanly. The code quality is solid with proper use of Review Categories
Blocking Issue: This PR adds new public API surface ( Please add a "Documentation PR" section to the PR description with either:
Note: Consider adding the |
|
@agent-of-mkmeral create a docs PR, we probably want to mention the serialization behavior somewhere |
…ents
Document the toJSON() serialization behavior for all TypeScript streaming
events. This includes:
- New 'Wire-Safe Serialization' section in the streaming overview
- Table showing what each event serializes to
- Explanation of excluded fields (agent, tool, cancel, retry)
- Error handling format ({ error: { message?: string } })
- In-process vs wire access patterns
- Updated Express.js example with serialization comments
Related: strands-agents/sdk-typescript#708
|
Done ✅ — Created docs PR: strands-agents/docs#693 The PR adds a "Wire-Safe Serialization (TypeScript)" section to the Streaming Events overview with:
Documentation PR: strands-agents/docs#693 |
|
Hey @mkmeral — checking in on status. Here's where things stand: ✅ All review feedback addressedAll 4 items from @zastrowm's review were implemented across 3 commits:
✅ CI passingAll 9 checks green (tests on Node 20/22/24 × ubuntu/macos/windows, code quality, PR title validation). ✅ Docs PR createdstrands-agents/docs#693 — also open, awaiting review. 👋 @pgrayy's feedback@pgrayy agreed the typed Pick shapes make sense as a future follow-up — noted and aligned. 🔴 BlockerPR is @zastrowm — would you be able to re-review when you get a chance? All your feedback has been implemented. 🙏 |
…est instances Address @pgrayy's review nit: the retry field is optional and was never set in the completeness test factory, so Object.keys() wouldn't include it — meaning the test couldn't verify it's properly excluded from toJSON() output. Using Object.assign() to set retry=true ensures the completeness test catches this field.
|
Addressed @pgrayy's nit in Current status
@zastrowm — would you be able to re-review when you get a chance? All your feedback has been implemented. 🙏 |
📋 Status updateNew since last check: @pgrayy approved ✅ (March 25)
The only remaining blocker is @zastrowm's re-review. All 4 items from their original review have been implemented. @zastrowm — friendly ping, would you be able to take another look when you get a chance? 🙏 |
Two long lines in the toJSON serialization completeness test needed wrapping to comply with Prettier's line-length rules.
we got 2 approvals. don't want to block
01f9083
|
🎉 Merged! Thanks @mkmeral and @pgrayy for the approvals, and @zastrowm for the thorough review that shaped the final design (Pick types, extensible error format, completeness test). What shipped
|
Motivation
Every event from
agent.stream()carriesagent: LocalAgent— the full Agent instance with conversation history, app state, model config, and internal registries. When users send events over the wire viaJSON.stringify(event), the output is massively bloated:This makes SSE streaming, WebSocket forwarding, and any wire transport impractical without manual field picking.
Resolves: agent-of-mkmeral/strands-coder#41
Public API Changes
All 16 streaming event classes now have a
toJSON()method thatJSON.stringify()calls automatically. Zero breaking changes —event.agentstill works for direct access in hooks and in-process code.What each event serializes to:
toJSON()outputModelStreamUpdateEvent{ type, event }ContentBlockEvent{ type, contentBlock }ModelMessageEvent{ type, message, stopReason }ToolResultEvent{ type, result }ToolStreamUpdateEvent{ type, event }AgentResultEvent{ type, result }MessageAddedEvent{ type, message }BeforeToolCallEvent{ type, toolUse }AfterToolCallEvent{ type, toolUse, result, error? }AfterModelCallEvent{ type, stopData?, error? }BeforeToolsEvent/AfterToolsEvent{ type, message }{ type }Excluded from all events:
agent: LocalAgent. Additionally excluded where applicable:tool: Tool(class with methods), mutable flags (cancel,retry).Errorobjects are converted to their.messagestring.cc @mkmeral