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.
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/PKG-INFO +1 -1
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/pyproject.toml +1 -1
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/__init__.py +6 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_internal/message_parser.py +10 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_internal/query.py +6 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_internal/transport/subprocess_cli.py +3 -1
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/client.py +11 -3
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/__init__.py +8 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/base.py +1 -0
- clawd_code_sdk-1.0.3/src/clawd_code_sdk/models/content_blocks.py +147 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/control.py +9 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/hooks.py +10 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/mcp.py +24 -15
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/messages.py +113 -14
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/options.py +11 -18
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/output_types.py +19 -1
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/settings.py +1 -5
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/system_messages.py +10 -1
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/storage/helpers.py +0 -39
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/storage/models.py +47 -94
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/storage/replay.py +90 -201
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_include_partial_messages.py +4 -4
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_mcp_tools.py +7 -7
- clawd_code_sdk-1.0.3/tests/e2e/test_storage_parsing.py +26 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/mock_claude_server.py +1 -1
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_client.py +6 -6
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_integration.py +4 -4
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_sdk_mcp_integration.py +18 -19
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_session.py +2 -2
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_streaming_client.py +3 -3
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_transport.py +9 -6
- clawd_code_sdk-1.0.1/src/clawd_code_sdk/models/content_blocks.py +0 -97
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/.gitignore +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/LICENSE +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/README.md +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_bundled/.gitignore +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_errors.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_internal/__init__.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_internal/transport/__init__.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_version.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/anthropic_types.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/list_sessions.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/mcp_utils.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/agents.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/input_types.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/permissions.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/prompt_requests.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/prompts.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/server_info.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/models/thinking.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/py.typed +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/query.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/session.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/storage/ARCHITECTURE.md +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/storage/__init__.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/usage.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/__init__.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/conftest.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/__init__.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_agents_and_settings.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_dynamic_control.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_hook_events.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_hooks.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_sdk_mcp_tools.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_slash_commands.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_stderr_callback.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_structured_output.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_subagent_invocation.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/e2e/test_tool_permissions.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/mcp_server.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_changelog.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_errors.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_image.png +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_message_parser.py +0 -0
- {clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/tests/test_subprocess_buffering.py +0 -0
- {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.
|
|
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
|
|
@@ -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",
|
{clawd_code_sdk-1.0.1 → clawd_code_sdk-1.0.3}/src/clawd_code_sdk/_internal/message_parser.py
RENAMED
|
@@ -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
|
|
111
|
-
sdk_mcp_servers[name] = config
|
|
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 =
|
|
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",
|
|
@@ -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
|
-
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(kw_only=True)
|
|
75
|
+
class McpStdioServerConfig:
|
|
72
76
|
"""MCP stdio server configuration."""
|
|
73
77
|
|
|
74
|
-
type:
|
|
78
|
+
type: Literal["stdio"] = "stdio"
|
|
75
79
|
command: str
|
|
76
|
-
args:
|
|
77
|
-
env:
|
|
80
|
+
args: list[str] = field(default_factory=list)
|
|
81
|
+
env: dict[str, str] = field(default_factory=dict)
|
|
78
82
|
|
|
79
83
|
|
|
80
|
-
|
|
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:
|
|
90
|
+
headers: dict[str, str] = field(default_factory=dict)
|
|
86
91
|
|
|
87
92
|
|
|
88
|
-
|
|
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:
|
|
99
|
+
headers: dict[str, str] = field(default_factory=dict)
|
|
94
100
|
|
|
95
101
|
|
|
96
|
-
|
|
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
|
-
|
|
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
|
|
10
|
-
|
|
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
|
-
|
|
197
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
334
|
+
from anthropic.types.beta import BetaRawContentBlockStopEvent
|
|
323
335
|
|
|
324
|
-
stop_event =
|
|
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
|
|
342
|
+
from anthropic.types.beta import BetaRawMessageStopEvent
|
|
331
343
|
|
|
332
|
-
stop_event =
|
|
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."""
|