feat(message): align message contracts with Python - #52
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Event timestamps are replaced during processing, malformed permission rules pass validation, and desktop usage updates mutate prior React state.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Aligns TypeScript message models, content blocks, parsing, usage accounting, and desktop consumers with Python SDK contracts.
Changes:
- Adds normalized content-block factories and Zod wire schemas.
- Aligns message defaults, filtering helpers, shared error types, and usage counters.
- Updates event handling, tests, and desktop state updates.
File summaries
| File | Description |
|---|---|
packages/agentscope/src/type/index.ts |
Adds shared reply and error contracts. |
packages/agentscope/src/message/schema.ts |
Adds runtime message and block schemas. |
packages/agentscope/src/message/message.ts |
Normalizes messages and event accumulation. |
packages/agentscope/src/message/message.test.ts |
Tests defaults, helpers, and parsing. |
packages/agentscope/src/message/index.ts |
Exports new message APIs. |
packages/agentscope/src/message/block.ts |
Adds normalized block factories. |
packages/agentscope/src/message/append-event.test.ts |
Updates event and usage tests. |
packages/agentscope/src/event/index.ts |
Shares error types and expands usage events. |
packages/agentscope/src/agent/agent.ts |
Adapts stored usage fields. |
packages/agentscope/src/agent/agent.test.ts |
Updates normalized-message expectations. |
packages/agentscope/src/_utils/common.ts |
Adds ID and timestamp generators. |
apps/desktop/src/renderer/src/hooks/agent-event-handler.ts |
Delegates event handling to shared logic. |
Review details
Suppressed comments (8)
packages/agentscope/src/message/message.ts:423
- The factory now uses wall-clock time instead of the event's timestamp. Replaying or processing this event asynchronously changes the serialized block chronology; preserve
event.created_at.
msg.content.push(ThinkingBlock({ id: event.block_id, thinking: '' }));
packages/agentscope/src/message/message.ts:453
- This one-shot block should be timestamped from the event, but both timestamps now come from the receiver's clock. That makes replayed events nondeterministic and can misorder them; pass the event timestamp for both fields.
const hintBlock = HintBlock({
id: event.block_id,
hint: event.hint,
source: event.source ?? null,
});
packages/agentscope/src/message/message.ts:464
- The data block's creation time is now generated when the event is handled rather than copied from the start event. Preserve
event.created_atso delayed and replayed streams retain their original timeline.
msg.content.push(
DataBlock({
id: event.block_id,
source: Base64Source({ data: '', media_type: event.media_type }),
})
packages/agentscope/src/message/message.ts:504
- Omitting
created_atreplaces the tool-call event timestamp with the local handling time. This changes persisted event history whenever delivery is delayed or replayed; pass throughevent.created_at.
msg.content.push(
ToolCallBlock({
id: event.tool_call_id,
name: event.tool_call_name,
input: '',
})
packages/agentscope/src/message/message.ts:534
- The result block is stamped with processing time instead of
event.created_at, so event replay no longer reconstructs the original message. Pass the event timestamp to the factory.
msg.content.push(
ToolResultBlock({
id: event.tool_call_id,
name: event.tool_call_name,
output: [],
})
packages/agentscope/src/message/message.ts:549
- Both text blocks created while applying this delta use the receiver's current time. The previous code used
event.created_at; retaining that timestamp is necessary for deterministic replay and correct block ordering.
trb.output = [TextBlock({ text: trb.output })];
}
const last = trb.output[trb.output.length - 1];
if (!last || last.type !== 'text') {
trb.output.push(TextBlock({ text: event.delta }));
packages/agentscope/src/message/message.ts:564
- Converting an existing string output now assigns the conversion time rather than the data event timestamp used previously. Pass
event.created_atso reconstructed nested output is deterministic.
trb.output = [TextBlock({ text: trb.output })];
packages/agentscope/src/message/message.ts:570
- This nested data block loses the event's timestamp and receives the local processing time. Delayed/replayed events therefore serialize differently; pass
event.created_attoDataBlock.
trb.output.push(DataBlock({ id: event.block_id ?? _generateId(), source }));
- Files reviewed: 12/12 changed files
- Comments generated: 3
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Linked Issues
Description
AgentScope Python models messages and content blocks as validated runtime entities. The TypeScript package previously exposed mostly structural interfaces, so default fields and serialized payload handling could differ between the two SDKs.
This PR aligns the public message behavior while retaining TypeScript-native APIs:
Msg,Usage,UserMsg,AssistantMsg, andSystemMsgdefaults with Python.hasContentBlocksand list-based filtering to match Python message helpers.parseMsg/parseContentBlockfor validated wire payloads.No generated parity inventory, migration tooling, or internal development documentation is included.
Verification
pnpm --filter @agentscope-ai/agentscope build pnpm --filter @agentscope-ai/agentscope exec jest --runInBand pnpm build:desktopResults:
git diff --checkpassed for all changed files.Checklist
pnpm formatpnpm test)