clawd-code-sdk 1.0.1__tar.gz → 1.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/PKG-INFO +1 -1
  2. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/pyproject.toml +1 -1
  3. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/__init__.py +6 -0
  4. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_internal/message_parser.py +10 -0
  5. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_internal/query.py +6 -0
  6. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_internal/transport/subprocess_cli.py +3 -1
  7. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/client.py +11 -3
  8. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/__init__.py +8 -0
  9. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/base.py +1 -0
  10. clawd_code_sdk-1.0.3/src/clawd_code_sdk/models/content_blocks.py +147 -0
  11. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/control.py +9 -0
  12. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/hooks.py +10 -0
  13. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/mcp.py +24 -15
  14. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/messages.py +113 -14
  15. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/options.py +11 -18
  16. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/output_types.py +19 -1
  17. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/settings.py +1 -5
  18. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/system_messages.py +10 -1
  19. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/storage/helpers.py +0 -39
  20. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/storage/models.py +47 -94
  21. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/storage/replay.py +90 -201
  22. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_include_partial_messages.py +4 -4
  23. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_mcp_tools.py +7 -7
  24. clawd_code_sdk-1.0.3/tests/e2e/test_storage_parsing.py +26 -0
  25. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/mock_claude_server.py +1 -1
  26. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_client.py +6 -6
  27. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_integration.py +4 -4
  28. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_sdk_mcp_integration.py +18 -19
  29. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_session.py +2 -2
  30. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_streaming_client.py +3 -3
  31. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_transport.py +9 -6
  32. clawd_code_sdk-1.0.1/src/clawd_code_sdk/models/content_blocks.py +0 -97
  33. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/.gitignore +0 -0
  34. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/LICENSE +0 -0
  35. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/README.md +0 -0
  36. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_bundled/.gitignore +0 -0
  37. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_errors.py +0 -0
  38. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_internal/__init__.py +0 -0
  39. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_internal/transport/__init__.py +0 -0
  40. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_version.py +0 -0
  41. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/anthropic_types.py +0 -0
  42. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/list_sessions.py +0 -0
  43. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/mcp_utils.py +0 -0
  44. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/agents.py +0 -0
  45. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/input_types.py +0 -0
  46. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/permissions.py +0 -0
  47. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/prompt_requests.py +0 -0
  48. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/prompts.py +0 -0
  49. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/server_info.py +0 -0
  50. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/thinking.py +0 -0
  51. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/py.typed +0 -0
  52. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/query.py +0 -0
  53. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/session.py +0 -0
  54. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/storage/ARCHITECTURE.md +0 -0
  55. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/storage/__init__.py +0 -0
  56. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/usage.py +0 -0
  57. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/__init__.py +0 -0
  58. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/conftest.py +0 -0
  59. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/__init__.py +0 -0
  60. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_agents_and_settings.py +0 -0
  61. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_dynamic_control.py +0 -0
  62. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_hook_events.py +0 -0
  63. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_hooks.py +0 -0
  64. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_sdk_mcp_tools.py +0 -0
  65. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_slash_commands.py +0 -0
  66. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_stderr_callback.py +0 -0
  67. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_structured_output.py +0 -0
  68. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_subagent_invocation.py +0 -0
  69. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_tool_permissions.py +0 -0
  70. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/mcp_server.py +0 -0
  71. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_changelog.py +0 -0
  72. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_errors.py +0 -0
  73. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_image.png +0 -0
  74. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_message_parser.py +0 -0
  75. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_subprocess_buffering.py +0 -0
  76. {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_tool_callbacks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clawd-code-sdk
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: Python SDK for Claude Code
5
5
  Project-URL: Documentation, https://github.com/phil65/claude-agent-sdk-python
6
6
  Project-URL: Homepage, https://github.com/phil65/claude-agent-sdk-python
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "clawd-code-sdk"
3
- version = "1.0.1"
3
+ version = "1.0.3"
4
4
  description = "Python SDK for Claude Code"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -37,6 +37,8 @@ from .models import (
37
37
  CommandHookHandler,
38
38
  OnUserQuestion,
39
39
  ContentBlock,
40
+ ImageBlock,
41
+ ImageSource,
40
42
  HookCallback,
41
43
  HookContext,
42
44
  HookHandler,
@@ -60,6 +62,7 @@ from .models import (
60
62
  PermissionResultAllow,
61
63
  PermissionResultDeny,
62
64
  PermissionUpdate,
65
+ PostCompactHookInput,
63
66
  PostToolUseFailureHookInput,
64
67
  PostToolUseFailureHookSpecificOutput,
65
68
  PostToolUseHookInput,
@@ -168,7 +171,9 @@ __all__ = [
168
171
  "HookMatcher",
169
172
  "HookMatcherConfig",
170
173
  "Hooks",
174
+ "ImageBlock",
171
175
  "ImageMediaType",
176
+ "ImageSource",
172
177
  "InitSystemMessage",
173
178
  "InvalidRequestError",
174
179
  "ListSessionsOptions",
@@ -192,6 +197,7 @@ __all__ = [
192
197
  "PermissionUpdate",
193
198
  "Permissions",
194
199
  "PlainTextMediaType",
200
+ "PostCompactHookInput",
195
201
  "PostToolUseFailureHookInput",
196
202
  "PostToolUseFailureHookSpecificOutput",
197
203
  "PostToolUseHookInput",
@@ -3,6 +3,8 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
+ import os
7
+ from pathlib import Path
6
8
  from typing import TYPE_CHECKING, Any
7
9
 
8
10
  from pydantic import ValidationError
@@ -16,6 +18,9 @@ if TYPE_CHECKING:
16
18
 
17
19
  logger = logging.getLogger(__name__)
18
20
 
21
+ _RECORD_PATH = os.environ.get("CLAWD_RECORD_MESSAGES")
22
+ _record_file = Path(_RECORD_PATH).open("a") if _RECORD_PATH else None # noqa: SIM115
23
+
19
24
 
20
25
  def parse_message(data: dict[str, Any]) -> Message:
21
26
  """Parse message from CLI output into typed Message objects.
@@ -29,6 +34,11 @@ def parse_message(data: dict[str, Any]) -> Message:
29
34
  Raises:
30
35
  MessageParseError: If parsing fails or message type is unrecognized
31
36
  """
37
+ if _record_file is not None:
38
+ import json
39
+
40
+ _record_file.write(json.dumps(data) + "\n")
41
+ _record_file.flush()
32
42
  try:
33
43
  return message_adapter.validate_python(data)
34
44
  except ValidationError as e:
@@ -508,6 +508,12 @@ class Query:
508
508
  """Change the AI model."""
509
509
  return await self._send_control_request({"subtype": "set_model", "model": model})
510
510
 
511
+ async def cancel_async_message(self, message_uuid: str) -> dict[str, Any]:
512
+ """Drop a pending async user message from the command queue by uuid."""
513
+ return await self._send_control_request(
514
+ {"subtype": "cancel_async_message", "message_uuid": message_uuid}
515
+ )
516
+
511
517
  async def stop_task(self, task_id: str) -> dict[str, Any]:
512
518
  """Stop a running task."""
513
519
  return await self._send_control_request({"subtype": "stop_task", "task_id": task_id})
@@ -515,8 +515,10 @@ def to_cli_args(options: ClaudeAgentOptions) -> list[str]:
515
515
 
516
516
  match options.mcp_servers:
517
517
  case dict() as servers if servers:
518
+ import dataclasses
519
+
518
520
  servers_for_cli = {
519
- name: {k: v for k, v in cfg.items() if k != "instance"}
521
+ name: {k: v for k, v in dataclasses.asdict(cfg).items() if k != "instance"}
520
522
  for name, cfg in servers.items()
521
523
  }
522
524
  dct = anyenv.dump_json({"mcpServers": servers_for_cli})
@@ -14,6 +14,7 @@ from clawd_code_sdk.models import (
14
14
  AssistantMessage,
15
15
  ClaudeAgentOptions,
16
16
  ClaudeCodeAgentInfo, # noqa: TC001
17
+ McpSdkServerConfigWithInstance,
17
18
  McpSetServersResult,
18
19
  McpStatusResponse,
19
20
  ResultErrorMessage,
@@ -107,8 +108,8 @@ class ClaudeSDKClient:
107
108
  sdk_mcp_servers = {}
108
109
  if isinstance(self.options.mcp_servers, dict):
109
110
  for name, config in self.options.mcp_servers.items():
110
- if config.get("type") == "sdk":
111
- sdk_mcp_servers[name] = config["instance"] # type: ignore[typeddict-item]
111
+ if isinstance(config, McpSdkServerConfigWithInstance):
112
+ sdk_mcp_servers[name] = config.instance
112
113
 
113
114
  # Calculate initialize timeout from CLAUDE_CODE_STREAM_CLOSE_TIMEOUT env var if set
114
115
  # CLAUDE_CODE_STREAM_CLOSE_TIMEOUT is in milliseconds, convert to seconds
@@ -236,6 +237,11 @@ class ClaudeSDKClient:
236
237
  query = self._ensure_connected()
237
238
  await query.set_model(model)
238
239
 
240
+ async def cancel_async_message(self, message_uuid: str) -> None:
241
+ """Drop a pending async user message from the command queue by uuid."""
242
+ query = self._ensure_connected()
243
+ await query.cancel_async_message(message_uuid)
244
+
239
245
  async def stop_task(self, task_id: str) -> None:
240
246
  """Stop a running task."""
241
247
  query = self._ensure_connected()
@@ -304,9 +310,11 @@ class ClaudeSDKClient:
304
310
  - 'errors': Dict mapping server names to error messages (if any)
305
311
  """
306
312
  query = self._ensure_connected()
313
+ import dataclasses
314
+
307
315
  wire_servers: dict[str, dict[str, Any]] = {}
308
316
  for name, config in servers.items():
309
- server_dict = dict(config)
317
+ server_dict = dataclasses.asdict(config)
310
318
  server_dict["name"] = name
311
319
  wire_servers[name] = server_dict
312
320
  result = await query.set_mcp_servers(wire_servers)
@@ -26,6 +26,7 @@ from .control import (
26
26
  ControlErrorResponse,
27
27
  ControlRequestUnion,
28
28
  ControlResponse,
29
+ SDKControlCancelAsyncMessageRequest,
29
30
  SDKControlElicitationRequest,
30
31
  SDKControlEndSessionRequest,
31
32
  SDKControlInitializeRequest,
@@ -71,6 +72,7 @@ from .hooks import (
71
72
  SessionStartHookInput,
72
73
  SetupHookInput,
73
74
  PermissionRequestHookSpecificOutput,
75
+ PostCompactHookInput,
74
76
  PostToolUseFailureHookInput,
75
77
  PostToolUseFailureHookSpecificOutput,
76
78
  PostToolUseHookInput,
@@ -206,6 +208,8 @@ from .output_types import (
206
208
  )
207
209
  from .content_blocks import (
208
210
  ContentBlock,
211
+ ImageBlock,
212
+ ImageSource,
209
213
  TextBlock,
210
214
  ThinkingBlock,
211
215
  ToolResultBlock,
@@ -440,7 +444,9 @@ __all__ = [
440
444
  "HookSpecificOutput",
441
445
  "HookStartedSystemMessage",
442
446
  "Hooks",
447
+ "ImageBlock",
443
448
  "ImageMediaType",
449
+ "ImageSource",
444
450
  "InitSystemMessage",
445
451
  "InstructionsLoadedHookInput",
446
452
  "JSONRPCError",
@@ -497,6 +503,7 @@ __all__ = [
497
503
  "PermissionUpdateDestination",
498
504
  "Permissions",
499
505
  "PlainTextMediaType",
506
+ "PostCompactHookInput",
500
507
  "PostToolUseFailureHookInput",
501
508
  "PostToolUseFailureHookSpecificOutput",
502
509
  "PostToolUseHookInput",
@@ -532,6 +539,7 @@ __all__ = [
532
539
  "ResultMessage",
533
540
  "ResultSuccessMessage",
534
541
  "ResumeSession",
542
+ "SDKControlCancelAsyncMessageRequest",
535
543
  "SDKControlElicitationRequest",
536
544
  "SDKControlEndSessionRequest",
537
545
  "SDKControlGetSettingsRequest",
@@ -79,4 +79,5 @@ class ClaudeCodeBaseModel(BaseModel):
79
79
  populate_by_name=True,
80
80
  alias_generator=to_camel,
81
81
  extra="forbid" if IS_DEV else "ignore",
82
+ defer_build=True,
82
83
  )
@@ -0,0 +1,147 @@
1
+ """Content block types shared by both wire-format messages and JSONL storage.
2
+
3
+ Claude Code uses the same content block schema on the wire (SDK ↔ CLI JSON
4
+ messages) and in persisted JSONL session transcripts. A single set of models
5
+ therefore serves both purposes, avoiding a redundant conversion layer.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Sequence
11
+ from typing import TYPE_CHECKING, Annotated, Any, Literal
12
+
13
+ from anthropic.types.beta.beta_tool_use_block import Caller
14
+ from pydantic import BaseModel, ConfigDict, Discriminator, TypeAdapter
15
+
16
+ from clawd_code_sdk.models import ToolInput
17
+ from clawd_code_sdk.models.base import ClaudeCodeBaseModel, StopReason, ToolName
18
+
19
+
20
+ if TYPE_CHECKING:
21
+ from clawd_code_sdk.anthropic_types import ToolResultContentBlock
22
+
23
+
24
+ # =============================================================================
25
+ # Content block types
26
+ # =============================================================================
27
+
28
+
29
+ class BaseContentBlock(BaseModel):
30
+ """Shared base for all content block types."""
31
+
32
+ # extra="allow": storage JSONL includes all union fields on every block
33
+ # with null for fields belonging to other block types.
34
+ model_config = ConfigDict(extra="allow", defer_build=True)
35
+
36
+
37
+ class TextBlock(BaseContentBlock):
38
+ """Text content block."""
39
+
40
+ type: Literal["text"] = "text"
41
+ text: str
42
+
43
+
44
+ class ThinkingBlock(BaseContentBlock):
45
+ """Thinking/reasoning content block."""
46
+
47
+ type: Literal["thinking"] = "thinking"
48
+ thinking: str
49
+ signature: str = ""
50
+
51
+
52
+ class ToolUseBlock(BaseContentBlock):
53
+ """Tool use content block."""
54
+
55
+ type: Literal["tool_use"] = "tool_use"
56
+ id: str = ""
57
+ name: ToolName | str = ""
58
+ input: ToolInput | dict[str, Any] = {}
59
+ caller: Caller | None = None
60
+
61
+
62
+ class ToolResultBlock(BaseContentBlock):
63
+ """Tool result content block."""
64
+
65
+ type: Literal["tool_result"] = "tool_result"
66
+ tool_use_id: str = ""
67
+ content: str | list[dict[str, Any]] | None = None # BetaContentBlock
68
+ is_error: bool | None = None
69
+
70
+ def get_parsed_content(self) -> list[ToolResultContentBlock] | str | None:
71
+ from clawd_code_sdk.anthropic_types import validate_tool_result_content
72
+
73
+ if self.content is None or isinstance(self.content, str):
74
+ return self.content
75
+ # Validate list content against Anthropic SDK types
76
+ return validate_tool_result_content(self.content)
77
+
78
+ def extract_text(self) -> str:
79
+ """Extract text content from this tool result."""
80
+ if self.content is None:
81
+ return ""
82
+ if isinstance(self.content, str):
83
+ return self.content
84
+ text_parts = [
85
+ tc.get("text", "")
86
+ for tc in self.content
87
+ if isinstance(tc, dict) and tc.get("type") == "text"
88
+ ]
89
+ return "\n".join(text_parts)
90
+
91
+
92
+ class ImageSource(BaseContentBlock):
93
+ """Base64-encoded image source data."""
94
+
95
+ type: Literal["base64"]
96
+ media_type: str
97
+ data: str
98
+
99
+
100
+ class ImageBlock(BaseContentBlock):
101
+ """Image content block (storage-only, not emitted on the wire)."""
102
+
103
+ type: Literal["image"] = "image"
104
+ source: ImageSource
105
+
106
+
107
+ # =============================================================================
108
+ # Unions and adapters
109
+ # =============================================================================
110
+
111
+ ContentBlock = Annotated[
112
+ TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock | ImageBlock,
113
+ Discriminator("type"),
114
+ ]
115
+
116
+ content_block_adapter = TypeAdapter[ContentBlock](ContentBlock)
117
+
118
+
119
+ # =============================================================================
120
+ # Message-level models
121
+ # =============================================================================
122
+
123
+
124
+ class MessageParam(ClaudeCodeBaseModel):
125
+ """Replacement for Anthropic MessageParam which serializes to our own content blocks."""
126
+
127
+ content: Sequence[ContentBlock] | str
128
+ role: Literal["user", "assistant"]
129
+ model_config = ConfigDict(extra="allow")
130
+
131
+
132
+ class AssistantMessageContent(ClaudeCodeBaseModel):
133
+ """Assistant message payload mirroring ``anthropic.types.beta.BetaMessage``.
134
+
135
+ Uses our own ``ContentBlock`` types instead of the Anthropic SDK's
136
+ ``BetaContentBlock`` variants. Extra fields from the wire format
137
+ (e.g. ``container``, ``context_management``) are preserved via ``extra="allow"``.
138
+ """
139
+
140
+ id: str
141
+ type: Literal["message"] = "message"
142
+ role: Literal["assistant"] = "assistant"
143
+ content: Sequence[ContentBlock]
144
+ model: str
145
+ stop_reason: StopReason | None = None
146
+ stop_sequence: str | None = None
147
+ model_config = ConfigDict(extra="allow")
@@ -87,6 +87,14 @@ class SDKControlRewindFilesRequest:
87
87
  dry_run: bool | None = None
88
88
 
89
89
 
90
+ @dataclass(frozen=True, slots=True, kw_only=True)
91
+ class SDKControlCancelAsyncMessageRequest:
92
+ """Drops a pending async user message from the command queue by uuid."""
93
+
94
+ subtype: Literal["cancel_async_message"] = "cancel_async_message"
95
+ message_uuid: str
96
+
97
+
90
98
  @dataclass(frozen=True, slots=True, kw_only=True)
91
99
  class SDKControlStopTaskRequest:
92
100
  """SDK control stop task request."""
@@ -239,6 +247,7 @@ ControlRequestUnion = Annotated[
239
247
  | SDKHookCallbackRequest
240
248
  | SDKControlMcpMessageRequest
241
249
  | SDKControlRewindFilesRequest
250
+ | SDKControlCancelAsyncMessageRequest
242
251
  | SDKControlMcpSetServersRequest
243
252
  | SDKControlMcpReconnectRequest
244
253
  | SDKControlMcpToggleRequest
@@ -25,6 +25,7 @@ HookEvent = Literal[
25
25
  "Stop",
26
26
  "SubagentStop",
27
27
  "PreCompact",
28
+ "PostCompact",
28
29
  "Notification",
29
30
  "SubagentStart",
30
31
  "PermissionRequest",
@@ -205,6 +206,14 @@ class PreCompactHookInput(BaseHookInput):
205
206
  custom_instructions: str | None
206
207
 
207
208
 
209
+ class PostCompactHookInput(BaseHookInput):
210
+ """Input data for PostCompact hook events."""
211
+
212
+ hook_event_name: Literal["PostCompact"]
213
+ trigger: CompactionTrigger
214
+ compact_summary: str
215
+
216
+
208
217
  class NotificationHookInput(BaseHookInput):
209
218
  """Input data for Notification hook events."""
210
219
 
@@ -343,6 +352,7 @@ HookInput = (
343
352
  | StopHookInput
344
353
  | SubagentStopHookInput
345
354
  | PreCompactHookInput
355
+ | PostCompactHookInput
346
356
  | NotificationHookInput
347
357
  | SubagentStartHookInput
348
358
  | PermissionRequestHookInput
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from dataclasses import dataclass, field
5
6
  from typing import TYPE_CHECKING, Any, Literal, NotRequired, TypedDict
6
7
 
7
8
  from pydantic import Field
@@ -68,42 +69,49 @@ JSONRPCMessage = JSONRPCRequest | JSONRPCNotification | JSONRPCResponse
68
69
 
69
70
 
70
71
  # MCP Server config
71
- class McpStdioServerConfig(TypedDict):
72
+
73
+
74
+ @dataclass(kw_only=True)
75
+ class McpStdioServerConfig:
72
76
  """MCP stdio server configuration."""
73
77
 
74
- type: NotRequired[Literal["stdio"]] # Optional for backwards compatibility
78
+ type: Literal["stdio"] = "stdio"
75
79
  command: str
76
- args: NotRequired[list[str]]
77
- env: NotRequired[dict[str, str]]
80
+ args: list[str] = field(default_factory=list)
81
+ env: dict[str, str] = field(default_factory=dict)
78
82
 
79
83
 
80
- class McpSSEServerConfig(TypedDict):
84
+ @dataclass(kw_only=True)
85
+ class McpSSEServerConfig:
81
86
  """MCP SSE server configuration."""
82
87
 
83
- type: Literal["sse"]
88
+ type: Literal["sse"] = "sse"
84
89
  url: str
85
- headers: NotRequired[dict[str, str]]
90
+ headers: dict[str, str] = field(default_factory=dict)
86
91
 
87
92
 
88
- class McpHttpServerConfig(TypedDict):
93
+ @dataclass(kw_only=True)
94
+ class McpHttpServerConfig:
89
95
  """MCP HTTP server configuration."""
90
96
 
91
- type: Literal["http"]
97
+ type: Literal["http"] = "http"
92
98
  url: str
93
- headers: NotRequired[dict[str, str]]
99
+ headers: dict[str, str] = field(default_factory=dict)
94
100
 
95
101
 
96
- class McpSdkServerConfig(TypedDict):
102
+ @dataclass(kw_only=True)
103
+ class McpSdkServerConfig:
97
104
  """SDK MCP server configuration (serializable, no instance)."""
98
105
 
99
- type: Literal["sdk"]
106
+ type: Literal["sdk"] = "sdk"
100
107
  name: str
101
108
 
102
109
 
110
+ @dataclass(kw_only=True)
103
111
  class McpSdkServerConfigWithInstance(McpSdkServerConfig):
104
112
  """SDK MCP server config with a live McpServer instance. Not serializable."""
105
113
 
106
- instance: McpServer
114
+ instance: McpServer = field(repr=False)
107
115
 
108
116
 
109
117
  ExternalMcpServerConfig = McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig
@@ -113,10 +121,11 @@ McpServerConfig = (
113
121
  )
114
122
 
115
123
 
116
- class McpClaudeAIProxyServerConfig(TypedDict):
124
+ @dataclass(kw_only=True)
125
+ class McpClaudeAIProxyServerConfig:
117
126
  """MCP Claude AI proxy server configuration."""
118
127
 
119
- type: Literal["claudeai-proxy"]
128
+ type: Literal["claudeai-proxy"] = "claudeai-proxy"
120
129
  url: str
121
130
  id: str
122
131
 
@@ -6,8 +6,22 @@ from collections.abc import Sequence
6
6
  import re
7
7
  from typing import TYPE_CHECKING, Any, Literal, NotRequired, TypedDict
8
8
 
9
- from anthropic.types import RawMessageStreamEvent
10
- from pydantic import BaseModel, ConfigDict
9
+ from anthropic.types.beta import (
10
+ BetaInputJSONDelta,
11
+ BetaMessageDeltaUsage,
12
+ BetaRawContentBlockDeltaEvent,
13
+ BetaRawContentBlockStartEvent,
14
+ BetaRawMessageDeltaEvent,
15
+ BetaRawMessageStreamEvent,
16
+ BetaStopReason, # noqa: TC002
17
+ BetaTextBlock as ATextBlock,
18
+ BetaTextDelta,
19
+ BetaThinkingBlock as AThinkingBlock,
20
+ BetaThinkingDelta,
21
+ BetaToolUseBlock as AToolUseBlock,
22
+ )
23
+ from anthropic.types.beta.beta_raw_message_delta_event import Delta as BetaRawMessageDelta
24
+ from pydantic import BaseModel, ConfigDict, Field
11
25
 
12
26
  from clawd_code_sdk._errors import (
13
27
  APIError,
@@ -28,9 +42,7 @@ from clawd_code_sdk.models.output_types import ToolUseResult
28
42
 
29
43
 
30
44
  if TYPE_CHECKING:
31
- from clawd_code_sdk.models.content_blocks import (
32
- ContentBlock,
33
- )
45
+ from clawd_code_sdk.models.content_blocks import ContentBlock
34
46
 
35
47
 
36
48
  # Message types
@@ -179,7 +191,7 @@ class Usage(BaseModel):
179
191
  class BaseMessage(BaseModel):
180
192
  """Base class for messages."""
181
193
 
182
- model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
194
+ model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True, populate_by_name=True)
183
195
 
184
196
  uuid: str
185
197
  session_id: str
@@ -193,8 +205,8 @@ class UserMessage(BaseMessage):
193
205
  tool_use_result: (
194
206
  Sequence[ToolUseResult | dict[str, Any]] | ToolUseResult | dict[str, Any] | str | None
195
207
  ) = None
196
- isReplay: bool | None = None # noqa: N815
197
- isSynthetic: bool | None = None # noqa: N815
208
+ is_replay: bool | None = Field(default=None, validation_alias="isReplay")
209
+ is_synthetic: bool | None = Field(default=None, validation_alias="isSynthetic")
198
210
  priority: Literal["now", "next", "later"] | None = None
199
211
  message: MessageParam
200
212
 
@@ -283,7 +295,7 @@ class BaseResultMessage(BaseMessage):
283
295
  usage: Usage
284
296
  """Token usage from the last API call only (per-turn)."""
285
297
  stop_reason: StopReason | None
286
- modelUsage: dict[str, ModelUsage] = {} # noqa: N815
298
+ model_usage: dict[str, ModelUsage] = Field(default_factory=dict, validation_alias="modelUsage")
287
299
  """Cumulative token usage per model across the entire session."""
288
300
  permission_denials: list[SDKPermissionDenial] = []
289
301
  """Permission denials from the last API call only (per-turn)."""
@@ -313,25 +325,112 @@ class StreamEvent(BaseMessage):
313
325
  """Stream event for partial message updates during streaming."""
314
326
 
315
327
  type: Literal["stream_event"] = "stream_event"
316
- event: RawMessageStreamEvent
328
+ event: BetaRawMessageStreamEvent
317
329
  parent_tool_use_id: str | None = None
318
330
 
319
331
  @classmethod
320
332
  def block_stop(cls, *, index: int, session_id: str, uuid: str) -> StreamEvent:
321
333
  """Create a synthetic content_block_stop StreamEvent."""
322
- from anthropic.types import RawContentBlockStopEvent
334
+ from anthropic.types.beta import BetaRawContentBlockStopEvent
323
335
 
324
- stop_event = RawContentBlockStopEvent(type="content_block_stop", index=index)
336
+ stop_event = BetaRawContentBlockStopEvent(type="content_block_stop", index=index)
325
337
  return StreamEvent(event=stop_event, session_id=session_id, uuid=uuid)
326
338
 
327
339
  @classmethod
328
340
  def message_stop(cls, *, session_id: str, uuid: str) -> StreamEvent:
329
341
  """Create a synthetic message_stop StreamEvent."""
330
- from anthropic.types import RawMessageStopEvent
342
+ from anthropic.types.beta import BetaRawMessageStopEvent
331
343
 
332
- stop_event = RawMessageStopEvent(type="message_stop")
344
+ stop_event = BetaRawMessageStopEvent(type="message_stop")
333
345
  return StreamEvent(event=stop_event, session_id=session_id, uuid=uuid)
334
346
 
347
+ @classmethod
348
+ def block_start_text(cls, *, index: int, session_id: str, uuid: str) -> StreamEvent:
349
+ """Create a synthetic content_block_start StreamEvent for a text block."""
350
+ content_block = ATextBlock(type="text", text="")
351
+ start_event = BetaRawContentBlockStartEvent(
352
+ type="content_block_start", index=index, content_block=content_block
353
+ )
354
+ return StreamEvent(event=start_event, session_id=session_id, uuid=uuid)
355
+
356
+ @classmethod
357
+ def block_start_thinking(cls, *, index: int, session_id: str, uuid: str) -> StreamEvent:
358
+ """Create a synthetic content_block_start StreamEvent for a thinking block."""
359
+ content_block = AThinkingBlock(type="thinking", thinking="", signature="")
360
+ start_event = BetaRawContentBlockStartEvent(
361
+ type="content_block_start", index=index, content_block=content_block
362
+ )
363
+ return StreamEvent(event=start_event, session_id=session_id, uuid=uuid)
364
+
365
+ @classmethod
366
+ def block_start_tool_use(
367
+ cls, *, tool_use_id: str, name: str, index: int, session_id: str, uuid: str
368
+ ) -> StreamEvent:
369
+ """Create a synthetic content_block_start StreamEvent for a tool_use block."""
370
+ content_block = AToolUseBlock(type="tool_use", id=tool_use_id, name=name, input={})
371
+ start_event = BetaRawContentBlockStartEvent(
372
+ type="content_block_start", index=index, content_block=content_block
373
+ )
374
+ return StreamEvent(event=start_event, session_id=session_id, uuid=uuid)
375
+
376
+ @classmethod
377
+ def block_text_delta(cls, *, text: str, index: int, session_id: str, uuid: str) -> StreamEvent:
378
+ """Create a synthetic content_block_delta StreamEvent with full block content."""
379
+ delta_event = BetaRawContentBlockDeltaEvent(
380
+ type="content_block_delta",
381
+ index=index,
382
+ delta=BetaTextDelta(type="text_delta", text=text),
383
+ )
384
+ return StreamEvent(event=delta_event, session_id=session_id, uuid=uuid)
385
+
386
+ @classmethod
387
+ def block_thinking_delta(
388
+ cls,
389
+ *,
390
+ thinking: str,
391
+ index: int,
392
+ session_id: str,
393
+ uuid: str,
394
+ ) -> StreamEvent:
395
+ """Create a synthetic content_block_delta StreamEvent with full block content."""
396
+ delta_event = BetaRawContentBlockDeltaEvent(
397
+ type="content_block_delta",
398
+ index=index,
399
+ delta=BetaThinkingDelta(type="thinking_delta", thinking=thinking),
400
+ )
401
+ return StreamEvent(event=delta_event, session_id=session_id, uuid=uuid)
402
+
403
+ @classmethod
404
+ def block_tool_json_delta(
405
+ cls,
406
+ *,
407
+ partial_json: str,
408
+ index: int,
409
+ session_id: str,
410
+ uuid: str,
411
+ ) -> StreamEvent:
412
+ """Create a synthetic content_block_delta StreamEvent with full block content."""
413
+ delta_event = BetaRawContentBlockDeltaEvent(
414
+ type="content_block_delta",
415
+ index=index,
416
+ delta=BetaInputJSONDelta(type="input_json_delta", partial_json=partial_json),
417
+ )
418
+ return StreamEvent(event=delta_event, session_id=session_id, uuid=uuid)
419
+
420
+ @classmethod
421
+ def message_delta(
422
+ cls,
423
+ *,
424
+ stop_reason: BetaStopReason | None,
425
+ session_id: str,
426
+ uuid: str,
427
+ ) -> StreamEvent:
428
+ """Create a synthetic message_delta StreamEvent."""
429
+ usage = BetaMessageDeltaUsage(output_tokens=0)
430
+ delta = BetaRawMessageDelta(stop_reason=stop_reason)
431
+ delta_event = BetaRawMessageDeltaEvent(type="message_delta", delta=delta, usage=usage)
432
+ return StreamEvent(event=delta_event, session_id=session_id, uuid=uuid)
433
+
335
434
 
336
435
  class ToolProgressMessage(BaseMessage):
337
436
  """Progress update for a running tool."""