diff --git a/CHANGELOG.md b/CHANGELOG.md index d827354e..6b41a9da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.1.4 + +### Features + +- **Message timestamps**: Added optional `timestamp` field to all message types (`UserMessage`, `AssistantMessage`, `SystemMessage`, `ResultMessage`, `StreamEvent`) to support timeline displays and message ordering. The timestamp is automatically populated from CLI transcript data when available + ## 0.1.3 ### Features diff --git a/e2e-tests/conftest.py b/e2e-tests/conftest.py index 392c213a..9abe3041 100644 --- a/e2e-tests/conftest.py +++ b/e2e-tests/conftest.py @@ -27,4 +27,4 @@ def event_loop_policy(): def pytest_configure(config): """Add e2e marker.""" - config.addinivalue_line("markers", "e2e: marks tests as e2e tests requiring API key") \ No newline at end of file + config.addinivalue_line("markers", "e2e: marks tests as e2e tests requiring API key") diff --git a/e2e-tests/test_agents_and_settings.py b/e2e-tests/test_agents_and_settings.py index 6e040664..1db0bb89 100644 --- a/e2e-tests/test_agents_and_settings.py +++ b/e2e-tests/test_agents_and_settings.py @@ -166,4 +166,4 @@ async def test_setting_sources_project_included(): # On Windows, wait for file handles to be released before cleanup if sys.platform == "win32": - await asyncio.sleep(0.5) \ No newline at end of file + await asyncio.sleep(0.5) diff --git a/e2e-tests/test_include_partial_messages.py b/e2e-tests/test_include_partial_messages.py index 2ff4f2a8..5f32d8d4 100644 --- a/e2e-tests/test_include_partial_messages.py +++ b/e2e-tests/test_include_partial_messages.py @@ -4,20 +4,19 @@ including StreamEvent parsing and message interleaving. """ -import asyncio -from typing import List, Any +from typing import Any import pytest from claude_agent_sdk import ClaudeSDKClient from claude_agent_sdk.types import ( + AssistantMessage, ClaudeAgentOptions, + ResultMessage, StreamEvent, - AssistantMessage, SystemMessage, - ResultMessage, - ThinkingBlock, TextBlock, + ThinkingBlock, ) @@ -35,7 +34,7 @@ async def test_include_partial_messages_stream_events(): }, ) - collected_messages: List[Any] = [] + collected_messages: list[Any] = [] async with ClaudeSDKClient(options) as client: # Send a simple prompt that will generate streaming response with thinking @@ -136,7 +135,7 @@ async def test_partial_messages_disabled_by_default(): max_turns=2, ) - collected_messages: List[Any] = [] + collected_messages: list[Any] = [] async with ClaudeSDKClient(options) as client: await client.query("Say hello") @@ -151,4 +150,4 @@ async def test_partial_messages_disabled_by_default(): # Should still have the regular messages assert any(isinstance(msg, SystemMessage) for msg in collected_messages) assert any(isinstance(msg, AssistantMessage) for msg in collected_messages) - assert any(isinstance(msg, ResultMessage) for msg in collected_messages) \ No newline at end of file + assert any(isinstance(msg, ResultMessage) for msg in collected_messages) diff --git a/src/claude_agent_sdk/_internal/message_parser.py b/src/claude_agent_sdk/_internal/message_parser.py index 6532a204..bf254459 100644 --- a/src/claude_agent_sdk/_internal/message_parser.py +++ b/src/claude_agent_sdk/_internal/message_parser.py @@ -75,10 +75,12 @@ def parse_message(data: dict[str, Any]) -> Message: return UserMessage( content=user_content_blocks, parent_tool_use_id=parent_tool_use_id, + timestamp=data.get("timestamp"), ) return UserMessage( content=data["message"]["content"], parent_tool_use_id=parent_tool_use_id, + timestamp=data.get("timestamp"), ) except KeyError as e: raise MessageParseError( @@ -120,6 +122,7 @@ def parse_message(data: dict[str, Any]) -> Message: content=content_blocks, model=data["message"]["model"], parent_tool_use_id=data.get("parent_tool_use_id"), + timestamp=data.get("timestamp"), ) except KeyError as e: raise MessageParseError( @@ -131,6 +134,7 @@ def parse_message(data: dict[str, Any]) -> Message: return SystemMessage( subtype=data["subtype"], data=data, + timestamp=data.get("timestamp"), ) except KeyError as e: raise MessageParseError( @@ -149,6 +153,7 @@ def parse_message(data: dict[str, Any]) -> Message: total_cost_usd=data.get("total_cost_usd"), usage=data.get("usage"), result=data.get("result"), + timestamp=data.get("timestamp"), ) except KeyError as e: raise MessageParseError( @@ -162,6 +167,7 @@ def parse_message(data: dict[str, Any]) -> Message: session_id=data["session_id"], event=data["event"], parent_tool_use_id=data.get("parent_tool_use_id"), + timestamp=data.get("timestamp"), ) except KeyError as e: raise MessageParseError( diff --git a/src/claude_agent_sdk/types.py b/src/claude_agent_sdk/types.py index be1cb996..dc1a2b3f 100644 --- a/src/claude_agent_sdk/types.py +++ b/src/claude_agent_sdk/types.py @@ -450,6 +450,7 @@ class UserMessage: content: str | list[ContentBlock] parent_tool_use_id: str | None = None + timestamp: int | str | None = None @dataclass @@ -459,6 +460,7 @@ class AssistantMessage: content: list[ContentBlock] model: str parent_tool_use_id: str | None = None + timestamp: int | str | None = None @dataclass @@ -467,6 +469,7 @@ class SystemMessage: subtype: str data: dict[str, Any] + timestamp: int | str | None = None @dataclass @@ -482,6 +485,7 @@ class ResultMessage: total_cost_usd: float | None = None usage: dict[str, Any] | None = None result: str | None = None + timestamp: int | str | None = None @dataclass @@ -492,6 +496,7 @@ class StreamEvent: session_id: str event: dict[str, Any] # The raw Anthropic API stream event parent_tool_use_id: str | None = None + timestamp: int | str | None = None Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage | StreamEvent