Skip to content

Commit 22e387e

Browse files
authored
Merge branch 'anthropics:main' into main
2 parents 532c5aa + 6f227de commit 22e387e

16 files changed

Lines changed: 3774 additions & 39 deletions

CHANGELOG.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,63 @@
11
# Changelog
22

3+
## 0.1.46
4+
5+
### New Features
6+
7+
- **Session history functions**: Added `list_sessions()` and `get_session_messages()` top-level functions for retrieving past session data (#622)
8+
- **MCP control methods**: Added `add_mcp_server()`, `remove_mcp_server()`, and typed `McpServerStatus` for runtime MCP server management (#620)
9+
- **Typed task messages**: Added `TaskStarted`, `TaskProgress`, and `TaskNotification` message subclasses for better type safety when handling task-related events (#621)
10+
- **ResultMessage stop_reason**: Added `stop_reason` field to `ResultMessage` for inspecting why a conversation turn ended (#619)
11+
- **Hook input enhancements**: Added `agent_id` and `agent_type` fields to tool-lifecycle hook inputs (`PreToolUseHookInput`, `PostToolUseHookInput`, `PostToolUseFailureHookInput`) (#628)
12+
13+
### Bug Fixes
14+
15+
- **String prompt MCP initialization**: Fixed an issue where passing a string prompt would close stdin before MCP server initialization completed, causing MCP servers to fail to register (#630)
16+
17+
### Internal/Other Changes
18+
19+
- Updated bundled Claude CLI to version 2.1.69
20+
21+
## 0.1.45
22+
23+
### Internal/Other Changes
24+
25+
- Updated bundled Claude CLI to version 2.1.63
26+
27+
## 0.1.44
28+
29+
### Internal/Other Changes
30+
31+
- Updated bundled Claude CLI to version 2.1.59
32+
33+
## 0.1.43
34+
35+
### Internal/Other Changes
36+
37+
- Updated bundled Claude CLI to version 2.1.56
38+
39+
## 0.1.42
40+
41+
### Internal/Other Changes
42+
43+
- Updated bundled Claude CLI to version 2.1.55
44+
45+
## 0.1.41
46+
47+
### Internal/Other Changes
48+
49+
- Updated bundled Claude CLI to version 2.1.52
50+
51+
## 0.1.40
52+
53+
### Bug Fixes
54+
55+
- **Unknown message type handling**: Fixed an issue where unrecognized CLI message types (e.g., `rate_limit_event`) would crash the session by raising `MessageParseError`. Unknown message types are now silently skipped, making the SDK forward-compatible with future CLI message types (#598)
56+
57+
### Internal/Other Changes
58+
59+
- Updated bundled Claude CLI to version 2.1.51
60+
361
## 0.1.39
462

563
### Internal/Other Changes

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "claude-agent-sdk"
7-
version = "0.1.39"
7+
version = "0.1.46"
88
description = "Python SDK for Claude Code"
99
readme = "README.md"
1010
requires-python = ">=3.10"

src/claude_agent_sdk/__init__.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
CLINotFoundError,
1414
ProcessError,
1515
)
16+
from ._internal.sessions import get_session_messages, list_sessions
1617
from ._internal.transport import Transport
1718
from ._version import __version__
1819
from .client import ClaudeSDKClient
@@ -31,6 +32,13 @@
3132
HookMatcher,
3233
McpSdkServerConfig,
3334
McpServerConfig,
35+
McpServerConnectionStatus,
36+
McpServerInfo,
37+
McpServerStatus,
38+
McpServerStatusConfig,
39+
McpStatusResponse,
40+
McpToolAnnotations,
41+
McpToolInfo,
3442
Message,
3543
NotificationHookInput,
3644
NotificationHookSpecificOutput,
@@ -52,12 +60,19 @@
5260
SandboxSettings,
5361
SdkBeta,
5462
SdkPluginConfig,
63+
SDKSessionInfo,
64+
SessionMessage,
5565
SettingSource,
5666
StopHookInput,
5767
SubagentStartHookInput,
5868
SubagentStartHookSpecificOutput,
5969
SubagentStopHookInput,
6070
SystemMessage,
71+
TaskNotificationMessage,
72+
TaskNotificationStatus,
73+
TaskProgressMessage,
74+
TaskStartedMessage,
75+
TaskUsage,
6176
TextBlock,
6277
ThinkingBlock,
6378
ThinkingConfig,
@@ -330,9 +345,21 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> Any:
330345
"PermissionMode",
331346
"McpServerConfig",
332347
"McpSdkServerConfig",
348+
"McpServerStatus",
349+
"McpServerStatusConfig",
350+
"McpServerConnectionStatus",
351+
"McpServerInfo",
352+
"McpStatusResponse",
353+
"McpToolAnnotations",
354+
"McpToolInfo",
333355
"UserMessage",
334356
"AssistantMessage",
335357
"SystemMessage",
358+
"TaskStartedMessage",
359+
"TaskProgressMessage",
360+
"TaskNotificationMessage",
361+
"TaskNotificationStatus",
362+
"TaskUsage",
336363
"ResultMessage",
337364
"Message",
338365
"ClaudeAgentOptions",
@@ -378,6 +405,11 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> Any:
378405
"SettingSource",
379406
# Plugin support
380407
"SdkPluginConfig",
408+
# Session listing
409+
"list_sessions",
410+
"get_session_messages",
411+
"SDKSessionInfo",
412+
"SessionMessage",
381413
# Beta support
382414
"SdkBeta",
383415
# Sandbox support
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Bundled Claude Code CLI version."""
22

3-
__cli_version__ = "2.1.49"
3+
__cli_version__ = "2.1.69"

src/claude_agent_sdk/_internal/client.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Internal client implementation."""
22

3+
import json
34
from collections.abc import AsyncIterable, AsyncIterator
45
from dataclasses import asdict, replace
56
from typing import Any
@@ -122,16 +123,14 @@ async def process_query(
122123
if isinstance(prompt, str):
123124
# For string prompts, write user message to stdin after initialize
124125
# (matching TypeScript SDK behavior)
125-
import json
126-
127126
user_message = {
128127
"type": "user",
129128
"session_id": "",
130129
"message": {"role": "user", "content": prompt},
131130
"parent_tool_use_id": None,
132131
}
133132
await chosen_transport.write(json.dumps(user_message) + "\n")
134-
await chosen_transport.end_input()
133+
await query.wait_for_result_and_end_input()
135134
elif isinstance(prompt, AsyncIterable) and query._tg:
136135
# Stream input in background for async iterables
137136
query._tg.start_soon(query.stream_input, prompt)

src/claude_agent_sdk/_internal/message_parser.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
ResultMessage,
1212
StreamEvent,
1313
SystemMessage,
14+
TaskNotificationMessage,
15+
TaskProgressMessage,
16+
TaskStartedMessage,
1417
TextBlock,
1518
ThinkingBlock,
1619
ToolResultBlock,
@@ -136,10 +139,49 @@ def parse_message(data: dict[str, Any]) -> Message | None:
136139

137140
case "system":
138141
try:
139-
return SystemMessage(
140-
subtype=data["subtype"],
141-
data=data,
142-
)
142+
subtype = data["subtype"]
143+
match subtype:
144+
case "task_started":
145+
return TaskStartedMessage(
146+
subtype=subtype,
147+
data=data,
148+
task_id=data["task_id"],
149+
description=data["description"],
150+
uuid=data["uuid"],
151+
session_id=data["session_id"],
152+
tool_use_id=data.get("tool_use_id"),
153+
task_type=data.get("task_type"),
154+
)
155+
case "task_progress":
156+
return TaskProgressMessage(
157+
subtype=subtype,
158+
data=data,
159+
task_id=data["task_id"],
160+
description=data["description"],
161+
usage=data["usage"],
162+
uuid=data["uuid"],
163+
session_id=data["session_id"],
164+
tool_use_id=data.get("tool_use_id"),
165+
last_tool_name=data.get("last_tool_name"),
166+
)
167+
case "task_notification":
168+
return TaskNotificationMessage(
169+
subtype=subtype,
170+
data=data,
171+
task_id=data["task_id"],
172+
status=data["status"],
173+
output_file=data["output_file"],
174+
summary=data["summary"],
175+
uuid=data["uuid"],
176+
session_id=data["session_id"],
177+
tool_use_id=data.get("tool_use_id"),
178+
usage=data.get("usage"),
179+
)
180+
case _:
181+
return SystemMessage(
182+
subtype=subtype,
183+
data=data,
184+
)
143185
except KeyError as e:
144186
raise MessageParseError(
145187
f"Missing required field in system message: {e}", data
@@ -154,6 +196,7 @@ def parse_message(data: dict[str, Any]) -> Message | None:
154196
is_error=data["is_error"],
155197
num_turns=data["num_turns"],
156198
session_id=data["session_id"],
199+
stop_reason=data.get("stop_reason"),
157200
total_cost_usd=data.get("total_cost_usd"),
158201
usage=data.get("usage"),
159202
result=data.get("result"),

src/claude_agent_sdk/_internal/query.py

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ async def _read_messages(self) -> None:
227227
# Put error in stream so iterators can handle it
228228
await self._message_send.send({"type": "error", "error": str(e)})
229229
finally:
230+
# Unblock any waiters (e.g. string-prompt path waiting for first
231+
# result) so they don't stall for the full timeout on early exit.
232+
self._first_result_event.set()
230233
# Always signal end of stream
231234
await self._message_send.send({"type": "end"})
232235

@@ -567,6 +570,65 @@ async def rewind_files(self, user_message_id: str) -> None:
567570
}
568571
)
569572

573+
async def reconnect_mcp_server(self, server_name: str) -> None:
574+
"""Reconnect a disconnected or failed MCP server.
575+
576+
Args:
577+
server_name: The name of the MCP server to reconnect
578+
"""
579+
await self._send_control_request(
580+
{
581+
"subtype": "mcp_reconnect",
582+
"serverName": server_name,
583+
}
584+
)
585+
586+
async def toggle_mcp_server(self, server_name: str, enabled: bool) -> None:
587+
"""Enable or disable an MCP server.
588+
589+
Args:
590+
server_name: The name of the MCP server to toggle
591+
enabled: Whether the server should be enabled
592+
"""
593+
await self._send_control_request(
594+
{
595+
"subtype": "mcp_toggle",
596+
"serverName": server_name,
597+
"enabled": enabled,
598+
}
599+
)
600+
601+
async def stop_task(self, task_id: str) -> None:
602+
"""Stop a running task.
603+
604+
Args:
605+
task_id: The task ID from task_notification events
606+
"""
607+
await self._send_control_request(
608+
{
609+
"subtype": "stop_task",
610+
"task_id": task_id,
611+
}
612+
)
613+
614+
async def wait_for_result_and_end_input(self) -> None:
615+
"""Wait for the first result (if needed) then close stdin.
616+
617+
If SDK MCP servers or hooks require bidirectional communication,
618+
keeps stdin open until the first result arrives (or timeout).
619+
Otherwise closes stdin immediately.
620+
"""
621+
if self.sdk_mcp_servers or self.hooks:
622+
logger.debug(
623+
"Waiting for first result before closing stdin "
624+
f"(sdk_mcp_servers={len(self.sdk_mcp_servers)}, "
625+
f"has_hooks={bool(self.hooks)})"
626+
)
627+
with anyio.move_on_after(self._stream_close_timeout):
628+
await self._first_result_event.wait()
629+
630+
await self.transport.end_input()
631+
570632
async def stream_input(self, stream: AsyncIterable[dict[str, Any]]) -> None:
571633
"""Stream input messages to transport.
572634
@@ -579,25 +641,7 @@ async def stream_input(self, stream: AsyncIterable[dict[str, Any]]) -> None:
579641
break
580642
await self.transport.write(json.dumps(message) + "\n")
581643

582-
# If we have SDK MCP servers or hooks that need bidirectional communication,
583-
# wait for first result before closing the channel
584-
has_hooks = bool(self.hooks)
585-
if self.sdk_mcp_servers or has_hooks:
586-
logger.debug(
587-
f"Waiting for first result before closing stdin "
588-
f"(sdk_mcp_servers={len(self.sdk_mcp_servers)}, has_hooks={has_hooks})"
589-
)
590-
try:
591-
with anyio.move_on_after(self._stream_close_timeout):
592-
await self._first_result_event.wait()
593-
logger.debug("Received first result, closing input stream")
594-
except Exception:
595-
logger.debug(
596-
"Timed out waiting for first result, closing input stream"
597-
)
598-
599-
# After all messages sent (and result received if needed), end input
600-
await self.transport.end_input()
644+
await self.wait_for_result_and_end_input()
601645
except Exception as e:
602646
logger.debug(f"Error streaming input: {e}")
603647

0 commit comments

Comments
 (0)