clawd-code-sdk 1.0.7__tar.gz → 1.0.8__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 (79) hide show
  1. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/PKG-INFO +1 -1
  2. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/pyproject.toml +1 -1
  3. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/client.py +2 -16
  4. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/agents.py +6 -2
  5. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/base.py +8 -2
  6. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/control.py +3 -1
  7. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/hooks.py +2 -2
  8. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/input_types.py +2 -2
  9. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/messages.py +7 -10
  10. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/options.py +12 -2
  11. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/output_types.py +9 -9
  12. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/permissions.py +19 -0
  13. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/server_info.py +9 -0
  14. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/settings.py +1 -1
  15. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/system_messages.py +15 -1
  16. clawd_code_sdk-1.0.8/src/clawd_code_sdk/models/thinking.py +56 -0
  17. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/test_subprocess_buffering.py +14 -14
  18. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/test_tool_callbacks.py +27 -20
  19. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/test_transport.py +7 -5
  20. clawd_code_sdk-1.0.7/src/clawd_code_sdk/models/thinking.py +0 -30
  21. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/.gitignore +0 -0
  22. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/LICENSE +0 -0
  23. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/README.md +0 -0
  24. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/__init__.py +0 -0
  25. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/_bundled/.gitignore +0 -0
  26. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/_errors.py +0 -0
  27. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/_internal/__init__.py +0 -0
  28. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/_internal/message_parser.py +0 -0
  29. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/_internal/query.py +0 -0
  30. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/_internal/transport/__init__.py +0 -0
  31. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/_internal/transport/subprocess_cli.py +0 -0
  32. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/_version.py +0 -0
  33. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/anthropic_types.py +0 -0
  34. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/list_sessions.py +0 -0
  35. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/mcp_utils.py +0 -0
  36. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/__init__.py +1 -1
  37. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/content_blocks.py +0 -0
  38. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/mcp.py +0 -0
  39. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/prompt_requests.py +0 -0
  40. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/prompts.py +0 -0
  41. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/models/session.py +0 -0
  42. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/py.typed +0 -0
  43. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/query.py +0 -0
  44. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/session.py +0 -0
  45. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/storage/ARCHITECTURE.md +0 -0
  46. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/storage/__init__.py +0 -0
  47. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/storage/helpers.py +0 -0
  48. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/storage/models.py +0 -0
  49. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/storage/replay.py +0 -0
  50. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/src/clawd_code_sdk/usage.py +0 -0
  51. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/__init__.py +0 -0
  52. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/conftest.py +0 -0
  53. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/e2e/__init__.py +0 -0
  54. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/e2e/test_agents_and_settings.py +0 -0
  55. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/e2e/test_dynamic_control.py +0 -0
  56. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/e2e/test_hook_events.py +0 -0
  57. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/e2e/test_hooks.py +0 -0
  58. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/e2e/test_include_partial_messages.py +0 -0
  59. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/e2e/test_mcp_resources.py +0 -0
  60. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/e2e/test_mcp_tools.py +0 -0
  61. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/e2e/test_sdk_mcp_resources.py +0 -0
  62. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/e2e/test_sdk_mcp_tools.py +0 -0
  63. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/e2e/test_slash_commands.py +0 -0
  64. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/e2e/test_stderr_callback.py +0 -0
  65. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/e2e/test_storage_parsing.py +0 -0
  66. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/e2e/test_structured_output.py +0 -0
  67. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/e2e/test_subagent_invocation.py +0 -0
  68. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/e2e/test_tool_permissions.py +0 -0
  69. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/mcp_server.py +0 -0
  70. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/mock_claude_server.py +0 -0
  71. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/test_changelog.py +0 -0
  72. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/test_client.py +0 -0
  73. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/test_errors.py +0 -0
  74. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/test_image.png +0 -0
  75. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/test_integration.py +0 -0
  76. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/test_message_parser.py +0 -0
  77. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/test_sdk_mcp_integration.py +0 -0
  78. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/test_session.py +0 -0
  79. {clawd_code_sdk-1.0.7 → clawd_code_sdk-1.0.8}/tests/test_streaming_client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clawd-code-sdk
3
- Version: 1.0.7
3
+ Version: 1.0.8
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.7"
3
+ version = "1.0.8"
4
4
  description = "Python SDK for Claude Code"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -7,7 +7,6 @@ import os
7
7
  from typing import TYPE_CHECKING, Any, Literal, Self, cast
8
8
 
9
9
  import anyenv
10
- from pydantic import TypeAdapter
11
10
 
12
11
  from clawd_code_sdk._errors import CLIConnectionError
13
12
  from clawd_code_sdk.models import (
@@ -95,9 +94,6 @@ class ClaudeSDKClient:
95
94
  from clawd_code_sdk._internal.query import Query
96
95
  from clawd_code_sdk._internal.transport.subprocess_cli import SubprocessCLITransport
97
96
 
98
- # Validate and configure permission settings (matching TypeScript SDK logic)
99
- self.options.validate()
100
-
101
97
  # If on_permission is a callback, extract it for Query and replace with
102
98
  # "stdio" so the CLI routes permission requests through the control protocol.
103
99
  if callable(self.options.on_permission):
@@ -134,20 +130,10 @@ class ClaudeSDKClient:
134
130
  else:
135
131
  system_prompt = self.options.system_prompt
136
132
 
137
- # JSON schema for structured output
138
- json_schema: dict[str, Any] | None
139
- match self.options.output_schema:
140
- case type() as typ:
141
- json_schema = TypeAdapter(typ).json_schema()
142
- case dict() as schema:
143
- json_schema = schema
144
- case None:
145
- json_schema = None
146
-
147
133
  # Create Query to handle control protocol
148
134
  self._query = Query(
149
135
  transport=self._transport,
150
- can_use_tool=can_use_tool,
136
+ can_use_tool=can_use_tool, # ty:ignore[invalid-argument-type]
151
137
  on_user_question=self.options.on_user_question,
152
138
  on_elicitation=self.options.on_elicitation,
153
139
  hooks=self.options.hooks,
@@ -156,7 +142,7 @@ class ClaudeSDKClient:
156
142
  agents=self.options.agents,
157
143
  system_prompt=system_prompt,
158
144
  append_system_prompt=append_system_prompt,
159
- json_schema=json_schema,
145
+ json_schema=self.options.get_json_schema(),
160
146
  prompt_suggestions=self.options.prompt_suggestions,
161
147
  agent_progress_summaries=self.options.agent_progress_summaries,
162
148
  )
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from dataclasses import dataclass, fields
6
- from typing import Any, Literal, TypedDict
6
+ from typing import TYPE_CHECKING, Any, Literal, TypedDict
7
7
 
8
8
  from anthropic.types import Model # noqa: TC002
9
9
 
@@ -12,6 +12,10 @@ from clawd_code_sdk.models.hooks import AgentHooksConfig # noqa: TC001
12
12
  from clawd_code_sdk.models.mcp import ExternalMcpServerConfig # noqa: TC001
13
13
 
14
14
 
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Mapping
17
+
18
+
15
19
  # Agent MCP server spec: either a string name or a {name: config} dict.
16
20
  # Matches the TypeScript type: AgentMcpServerSpec =
17
21
  # string | Record<string, McpServerConfigForProcessTransport>
@@ -57,7 +61,7 @@ class AgentDefinition:
57
61
  tools: list[str] | None = None
58
62
  model: ModelName | Literal["inherit"] | str | None = None # noqa: PYI051
59
63
  memory: SettingSource | None = None
60
- mcp_servers: list[AgentMcpServerSpec] | dict[str, ExternalMcpServerConfig] | None = None
64
+ mcp_servers: list[AgentMcpServerSpec] | Mapping[str, ExternalMcpServerConfig] | None = None
61
65
  disallowed_tools: list[str] | None = None
62
66
  critical_system_reminder_experimental: str | None = None
63
67
  skills: list[str] | None = None
@@ -27,8 +27,14 @@ ElicitationMode = Literal["form", "url"]
27
27
  ElicitationAction = Literal["accept", "decline", "cancel"]
28
28
  FastModeState = Literal["off", "cooldown", "on"]
29
29
  EffortLevel = Literal["low", "medium", "high", "max"]
30
-
31
-
30
+ AssistantMessageError = Literal[
31
+ "authentication_failed",
32
+ "billing_error",
33
+ "rate_limit",
34
+ "invalid_request",
35
+ "server_error",
36
+ "unknown",
37
+ ]
32
38
  StopReason = Literal[
33
39
  "end_turn",
34
40
  "max_tokens",
@@ -35,10 +35,12 @@ class SDKControlPermissionRequest:
35
35
  subtype: Literal["can_use_tool"] = "can_use_tool"
36
36
  tool_name: str
37
37
  input: dict[str, Any]
38
- tool_use_id: str
39
38
  permission_suggestions: list[PermissionUpdate] | None = None
40
39
  blocked_path: str | None = None
41
40
  decision_reason: str | None = None
41
+ title: str | None = None
42
+ display_name: str | None = None
43
+ tool_use_id: str
42
44
  agent_id: str | None = None
43
45
  description: str | None = None
44
46
 
@@ -42,7 +42,7 @@ HookEvent = Literal[
42
42
  "WorktreeRemove",
43
43
  "InstructionsLoaded",
44
44
  ]
45
-
45
+ LoadReason = Literal["session_start", "nested_traversal", "path_glob_match", "include", "compact"]
46
46
 
47
47
  # ---------------------------------------------------------------------------
48
48
  # Declarative hook handler configs (for agent/skill frontmatter & settings)
@@ -338,7 +338,7 @@ class InstructionsLoadedHookInput(BaseHookInput):
338
338
  hook_event_name: Literal["InstructionsLoaded"]
339
339
  file_path: str
340
340
  memory_type: Literal["User", "Project", "Local", "Managed"]
341
- load_reason: Literal["session_start", "nested_traversal", "path_glob_match", "include"]
341
+ load_reason: LoadReason
342
342
  globs: NotRequired[list[str]]
343
343
  trigger_file_path: NotRequired[str]
344
344
  parent_file_path: NotRequired[str]
@@ -33,8 +33,8 @@ class AgentInput(TypedDict):
33
33
 
34
34
  Takes precedence over the agent definition's model frontmatter.
35
35
  If omitted, uses the agent definition's model, or inherits from the parent."""
36
- resume: NotRequired[str]
37
- """Optional agent ID to resume from in order to continue from the previous exec transcript."""
36
+ # resume: NotRequired[str]
37
+ # """Optional agent ID to resume from in order to continue from the previous exec transcript."""
38
38
  run_in_background: NotRequired[bool]
39
39
  """Whether to run the agent in the background."""
40
40
  name: NotRequired[str]
@@ -31,7 +31,12 @@ from clawd_code_sdk._errors import (
31
31
  RateLimitError,
32
32
  ServerError,
33
33
  )
34
- from clawd_code_sdk.models.base import FastModeState, StopReason, ToolName # noqa: TC001
34
+ from clawd_code_sdk.models.base import ( # noqa: TC001
35
+ AssistantMessageError,
36
+ FastModeState,
37
+ StopReason,
38
+ ToolName,
39
+ )
35
40
  from clawd_code_sdk.models.content_blocks import (
36
41
  AssistantMessageContent,
37
42
  MessageParam,
@@ -46,15 +51,6 @@ if TYPE_CHECKING:
46
51
 
47
52
 
48
53
  # Message types
49
- AssistantMessageError = Literal[
50
- "authentication_failed",
51
- "billing_error",
52
- "rate_limit",
53
- "invalid_request",
54
- "server_error",
55
- "unknown",
56
- ]
57
-
58
54
  ErrorSubType = Literal[
59
55
  "error_during_execution",
60
56
  "error_max_turns",
@@ -173,6 +169,7 @@ class UserMessage(BaseMessage):
173
169
  is_synthetic: bool | None = Field(default=None, validation_alias="isSynthetic")
174
170
  priority: Literal["now", "next", "later"] | None = None
175
171
  message: MessageParam
172
+ timestamp: str | None = None
176
173
 
177
174
  @property
178
175
  def content(self) -> str | Sequence[ContentBlock]:
@@ -334,5 +334,15 @@ class ClaudeAgentOptions:
334
334
 
335
335
  return anyenv.dump_json(settings_obj) if settings_obj else None
336
336
 
337
- def validate(self) -> None:
338
- """Validate option constraints."""
337
+ def get_json_schema(self) -> dict[str, Any] | None:
338
+ from pydantic import TypeAdapter
339
+
340
+ match self.output_schema:
341
+ case type() as typ:
342
+ return TypeAdapter(typ).json_schema()
343
+ case dict() as schema:
344
+ return schema
345
+ case None:
346
+ return None
347
+ case _ as unreachable:
348
+ assert_never(unreachable)
@@ -112,18 +112,18 @@ class AgentAsyncLaunchedOutput(TypedDict):
112
112
  """Whether the calling agent has Read/Bash tools to check progress."""
113
113
 
114
114
 
115
- class AgentQueuedToRunningOutput(TypedDict):
116
- """Output from the Task tool when an agent is queued to run."""
115
+ # class AgentQueuedToRunningOutput(TypedDict):
116
+ # """Output from the Task tool when an agent is queued to run."""
117
117
 
118
- status: Literal["queued_to_running"]
119
- """Indicates the agent is queued to run."""
120
- agentId: str
121
- """The ID of the async agent."""
122
- prompt: str
123
- """The prompt for the agent."""
118
+ # status: Literal["queued_to_running"]
119
+ # """Indicates the agent is queued to run."""
120
+ # agentId: str
121
+ # """The ID of the async agent."""
122
+ # prompt: str
123
+ # """The prompt for the agent."""
124
124
 
125
125
 
126
- AgentOutput = AgentCompletedOutput | AgentAsyncLaunchedOutput | AgentQueuedToRunningOutput
126
+ AgentOutput = AgentCompletedOutput | AgentAsyncLaunchedOutput # | AgentQueuedToRunningOutput
127
127
 
128
128
 
129
129
  # ---------------------------------------------------------------------------
@@ -90,6 +90,25 @@ class ToolPermissionContext:
90
90
  For example, when a Bash command tries to access a path outside allowed directories.
91
91
  """
92
92
 
93
+ title: str | None = None
94
+ """Full permission prompt sentence rendered by the bridge.
95
+
96
+ E.g. "Claude wants to read foo.txt". Use this as the primary prompt
97
+ text when present instead of reconstructing from toolName+input.
98
+ """
99
+
100
+ display_name: str | None = None
101
+ """Short noun phrase for the tool action (e.g. "Read file").
102
+
103
+ Suitable for button labels or compact UI.
104
+ """
105
+
106
+ description: str | None = None
107
+ """Human-readable subtitle from the bridge.
108
+
109
+ E.g. "Claude will have read and write access to files in ~/Downloads".
110
+ """
111
+
93
112
 
94
113
  # Match TypeScript's PermissionResult structure
95
114
  class PermissionResultAllow(ClaudeCodeBaseModel):
@@ -2,6 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from typing import Literal
6
+
5
7
  from anthropic.types import Model
6
8
  from pydantic import Field
7
9
 
@@ -67,6 +69,13 @@ class ClaudeCodeAccountInfo(ClaudeCodeBaseModel):
67
69
  organization: str | None = None
68
70
  """Organization name."""
69
71
 
72
+ api_provider: Literal["firstParty", "bedrock", "vertex", "foundry"] | None = None
73
+ """Active API backend.
74
+
75
+ Anthropic OAuth login only applies when "firstParty"; for 3P providers
76
+ the other fields are absent and auth is external (AWS creds, gcloud ADC, etc.).
77
+ """
78
+
70
79
 
71
80
  class ClaudeCodeAgentInfo(ClaudeCodeBaseModel):
72
81
  """Information about an available subagent from Claude Code server."""
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: claude-code-settings-titled.json
3
- # timestamp: 2026-03-14T02:28:03+00:00
3
+ # timestamp: 2026-03-17T00:56:37+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -10,6 +10,7 @@ from pydantic import BaseModel, ConfigDict, Discriminator, TypeAdapter
10
10
  from clawd_code_sdk.models import McpConnectionStatus
11
11
  from clawd_code_sdk.models.base import ( # noqa: TC001
12
12
  ApiKeySource,
13
+ AssistantMessageError,
13
14
  CompactionTrigger,
14
15
  FastModeState,
15
16
  PermissionMode,
@@ -216,6 +217,17 @@ class TaskProgressSystemMessage(BaseSystemMessage):
216
217
  """AI-generated progress summary when agentProgressSummaries is enabled."""
217
218
 
218
219
 
220
+ class APIRetrySystemMessage(BaseSystemMessage):
221
+ """System message emitted when an API call fails and is retried."""
222
+
223
+ subtype: Literal["api_retry"] = "api_retry"
224
+ attempt: int
225
+ max_retries: int
226
+ retry_delay_ms: int
227
+ error_status: int | None
228
+ error: AssistantMessageError
229
+
230
+
219
231
  SystemMessageUnion = (
220
232
  InitSystemMessage
221
233
  | HookStartedSystemMessage
@@ -229,6 +241,7 @@ SystemMessageUnion = (
229
241
  | FilesPersistedSystemMessage
230
242
  | ElicitationCompleteMessage
231
243
  | LocalCommandOutputMessage
244
+ | APIRetrySystemMessage
232
245
  )
233
246
 
234
247
  SystemMessages = Annotated[
@@ -243,7 +256,8 @@ SystemMessages = Annotated[
243
256
  | TaskProgressSystemMessage
244
257
  | FilesPersistedSystemMessage
245
258
  | ElicitationCompleteMessage
246
- | LocalCommandOutputMessage,
259
+ | LocalCommandOutputMessage
260
+ | APIRetrySystemMessage,
247
261
  Discriminator("subtype"),
248
262
  ]
249
263
 
@@ -0,0 +1,56 @@
1
+ """Thinking configuration models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Literal
6
+
7
+ from clawd_code_sdk.models.base import ClaudeCodeBaseModel
8
+
9
+
10
+ ThinkingDisplay = Literal["summarized", "omitted"]
11
+
12
+
13
+ # Thinking configuration types
14
+ class ThinkingConfigAdaptive(ClaudeCodeBaseModel):
15
+ """Adaptive thinking configuration - model decides thinking budget."""
16
+
17
+ type: Literal["adaptive"] = "adaptive"
18
+
19
+ display: ThinkingDisplay | None = None
20
+ """Controls how thinking content appears in the response.
21
+ When set to `summarized`, thinking is returned normally. When set to `omitted`,
22
+ thinking content is redacted but a signature is returned for multi-turn
23
+ continuity. Defaults to `summarized`.
24
+ """
25
+
26
+
27
+ class ThinkingConfigEnabled(ClaudeCodeBaseModel):
28
+ """Enabled thinking configuration with explicit token budget."""
29
+
30
+ type: Literal["enabled"] = "enabled"
31
+
32
+ budget_tokens: int
33
+ """Determines how many tokens Claude can use for its internal reasoning process.
34
+ Larger budgets can enable more thorough analysis for complex problems, improving
35
+ response quality.
36
+ Must be ≥1024 and less than `max_tokens`.
37
+ See
38
+ [extended thinking](https://docs.claude.com/en/docs/build-with-claude/extended-thinking)
39
+ for details.
40
+ """
41
+
42
+ display: ThinkingDisplay | None = None
43
+ """Controls how thinking content appears in the response.
44
+ When set to `summarized`, thinking is returned normally. When set to `omitted`,
45
+ thinking content is redacted but a signature is returned for multi-turn
46
+ continuity. Defaults to `summarized`.
47
+ """
48
+
49
+
50
+ class ThinkingConfigDisabled(ClaudeCodeBaseModel):
51
+ """Disabled thinking configuration."""
52
+
53
+ type: Literal["disabled"] = "disabled"
54
+
55
+
56
+ ThinkingConfig = ThinkingConfigAdaptive | ThinkingConfigEnabled | ThinkingConfigDisabled
@@ -92,8 +92,8 @@ class TestSubprocessBuffering:
92
92
  mock_process.returncode = None
93
93
  mock_process.wait = AsyncMock(return_value=None)
94
94
  transport._process = mock_process
95
- transport._stdout_stream = MockTextReceiveStream([buffered_line]) # pyright: ignore[reportAttributeAccessIssue]
96
- transport._stderr_stream = MockTextReceiveStream([]) # pyright: ignore[reportAttributeAccessIssue]
95
+ transport._stdout_stream = MockTextReceiveStream([buffered_line]) # pyright: ignore[reportAttributeAccessIssue] # ty:ignore[invalid-assignment]
96
+ transport._stderr_stream = MockTextReceiveStream([]) # pyright: ignore[reportAttributeAccessIssue] # ty:ignore[invalid-assignment]
97
97
  messages = [msg async for msg in transport.read_messages()]
98
98
  assert len(messages) == 2
99
99
  assert messages[0]["content"] == "Line 1\nLine 2\nLine 3"
@@ -113,8 +113,8 @@ class TestSubprocessBuffering:
113
113
  mock_process.returncode = None
114
114
  mock_process.wait = AsyncMock(return_value=None)
115
115
  transport._process = mock_process
116
- transport._stdout_stream = MockTextReceiveStream([buffered_line]) # pyright: ignore[reportAttributeAccessIssue]
117
- transport._stderr_stream = MockTextReceiveStream([]) # pyright: ignore[reportAttributeAccessIssue]
116
+ transport._stdout_stream = MockTextReceiveStream([buffered_line]) # pyright: ignore[reportAttributeAccessIssue] # ty:ignore[invalid-assignment]
117
+ transport._stderr_stream = MockTextReceiveStream([]) # pyright: ignore[reportAttributeAccessIssue] # ty:ignore[invalid-assignment]
118
118
  messages = [msg async for msg in transport.read_messages()]
119
119
  assert len(messages) == 2
120
120
  assert messages[0]["id"] == "msg1"
@@ -150,8 +150,8 @@ class TestSubprocessBuffering:
150
150
  mock_process.returncode = None
151
151
  mock_process.wait = AsyncMock(return_value=None)
152
152
  transport._process = mock_process
153
- transport._stdout_stream = MockTextReceiveStream([part1, part2, part3]) # pyright: ignore[reportAttributeAccessIssue]
154
- transport._stderr_stream = MockTextReceiveStream([]) # pyright: ignore[reportAttributeAccessIssue]
153
+ transport._stdout_stream = MockTextReceiveStream([part1, part2, part3]) # pyright: ignore[reportAttributeAccessIssue] # ty:ignore[invalid-assignment]
154
+ transport._stderr_stream = MockTextReceiveStream([]) # pyright: ignore[reportAttributeAccessIssue] # ty:ignore[invalid-assignment]
155
155
  messages = [msg async for msg in transport.read_messages()]
156
156
  assert len(messages) == 1
157
157
  assert messages[0]["type"] == "assistant"
@@ -187,8 +187,8 @@ class TestSubprocessBuffering:
187
187
  mock_process.returncode = None
188
188
  mock_process.wait = AsyncMock(return_value=None)
189
189
  transport._process = mock_process
190
- transport._stdout_stream = MockTextReceiveStream(chunks) # pyright: ignore[reportAttributeAccessIssue]
191
- transport._stderr_stream = MockTextReceiveStream([]) # pyright: ignore[reportAttributeAccessIssue]
190
+ transport._stdout_stream = MockTextReceiveStream(chunks) # pyright: ignore[reportAttributeAccessIssue] # ty:ignore[invalid-assignment]
191
+ transport._stderr_stream = MockTextReceiveStream([]) # pyright: ignore[reportAttributeAccessIssue] # ty:ignore[invalid-assignment]
192
192
  messages = [msg async for msg in transport.read_messages()]
193
193
  assert len(messages) == 1
194
194
  assert messages[0]["type"] == "user"
@@ -209,8 +209,8 @@ class TestSubprocessBuffering:
209
209
  mock_process.returncode = None
210
210
  mock_process.wait = AsyncMock(return_value=None)
211
211
  transport._process = mock_process
212
- transport._stdout_stream = MockTextReceiveStream([huge_incomplete]) # pyright: ignore[reportAttributeAccessIssue]
213
- transport._stderr_stream = MockTextReceiveStream([]) # pyright: ignore[reportAttributeAccessIssue]
212
+ transport._stdout_stream = MockTextReceiveStream([huge_incomplete]) # pyright: ignore[reportAttributeAccessIssue] # ty:ignore[invalid-assignment]
213
+ transport._stderr_stream = MockTextReceiveStream([]) # pyright: ignore[reportAttributeAccessIssue] # ty:ignore[invalid-assignment]
214
214
  with pytest.raises(Exception) as exc_info: # noqa: PT011
215
215
  _messages = [msg async for msg in transport.read_messages()]
216
216
 
@@ -231,8 +231,8 @@ class TestSubprocessBuffering:
231
231
  mock_process.returncode = None
232
232
  mock_process.wait = AsyncMock(return_value=None)
233
233
  transport._process = mock_process
234
- transport._stdout_stream = MockTextReceiveStream([huge_incomplete]) # pyright: ignore[reportAttributeAccessIssue]
235
- transport._stderr_stream = MockTextReceiveStream([]) # pyright: ignore[reportAttributeAccessIssue]
234
+ transport._stdout_stream = MockTextReceiveStream([huge_incomplete]) # pyright: ignore[reportAttributeAccessIssue] # ty:ignore[invalid-assignment]
235
+ transport._stderr_stream = MockTextReceiveStream([]) # pyright: ignore[reportAttributeAccessIssue] # ty:ignore[invalid-assignment]
236
236
 
237
237
  with pytest.raises(CLIJSONDecodeError) as exc_info:
238
238
  async for _ in transport.read_messages():
@@ -264,8 +264,8 @@ class TestSubprocessBuffering:
264
264
  mock_process.returncode = None
265
265
  mock_process.wait = AsyncMock(return_value=None)
266
266
  transport._process = mock_process
267
- transport._stdout_stream = MockTextReceiveStream(lines) # pyright: ignore[reportAttributeAccessIssue]
268
- transport._stderr_stream = MockTextReceiveStream([]) # pyright: ignore[reportAttributeAccessIssue]
267
+ transport._stdout_stream = MockTextReceiveStream(lines) # pyright: ignore[reportAttributeAccessIssue] # ty:ignore[invalid-assignment]
268
+ transport._stderr_stream = MockTextReceiveStream([]) # pyright: ignore[reportAttributeAccessIssue] # ty:ignore[invalid-assignment]
269
269
  messages = [msg async for msg in transport.read_messages()]
270
270
  assert len(messages) == 3
271
271
  assert messages[0]["type"] == "system"
@@ -26,7 +26,14 @@ async def _dispatch(query: Query, request: dict[str, Any]) -> None:
26
26
 
27
27
 
28
28
  if TYPE_CHECKING:
29
- from clawd_code_sdk import HookContext, HookInput, HookJSONOutput, ToolPermissionContext
29
+ from clawd_code_sdk import (
30
+ HookContext,
31
+ HookInput,
32
+ HookJSONOutput,
33
+ PermissionResult,
34
+ ToolPermissionContext,
35
+ )
36
+ from clawd_code_sdk.models import HookEvent, ToolInput
30
37
 
31
38
 
32
39
  class MockTransport(Transport):
@@ -65,7 +72,7 @@ class TestToolPermissionCallbacks:
65
72
  callback_invoked = False
66
73
 
67
74
  async def allow_callback(
68
- tool_name: str, input_data: dict, context: ToolPermissionContext
75
+ tool_name: str, input_data: ToolInput | dict[str, Any], context: ToolPermissionContext
69
76
  ) -> PermissionResultAllow:
70
77
  nonlocal callback_invoked
71
78
  callback_invoked = True
@@ -97,7 +104,7 @@ class TestToolPermissionCallbacks:
97
104
  """Test callback that denies tool execution."""
98
105
 
99
106
  async def deny_callback(
100
- tool_name: str, input_data: dict, context: ToolPermissionContext
107
+ tool_name: str, input_data: ToolInput | dict[str, Any], context: ToolPermissionContext
101
108
  ) -> PermissionResultDeny:
102
109
  return PermissionResultDeny(message="Security policy violation")
103
110
 
@@ -130,7 +137,7 @@ class TestToolPermissionCallbacks:
130
137
  """Test callback that modifies tool input."""
131
138
 
132
139
  async def modify_callback(
133
- tool_name: str, input_data: dict, context: ToolPermissionContext
140
+ tool_name: str, input_data: ToolInput | dict[str, Any], context: ToolPermissionContext
134
141
  ) -> PermissionResultAllow:
135
142
  # Modify the input to add safety flag
136
143
  modified_input = input_data.copy()
@@ -138,11 +145,7 @@ class TestToolPermissionCallbacks:
138
145
  return PermissionResultAllow(updated_input=modified_input)
139
146
 
140
147
  transport = MockTransport()
141
- query = Query(
142
- transport=transport,
143
- can_use_tool=modify_callback,
144
- hooks=None,
145
- )
148
+ query = Query(transport=transport, can_use_tool=modify_callback)
146
149
  request_data = SDKControlPermissionRequest(
147
150
  tool_name="WriteTool",
148
151
  input={"file_path": "/etc/passwd"},
@@ -161,16 +164,12 @@ class TestToolPermissionCallbacks:
161
164
  """Test that callback exceptions are properly handled."""
162
165
 
163
166
  async def error_callback(
164
- tool_name: str, input_data: dict, context: ToolPermissionContext
165
- ) -> PermissionResultAllow:
167
+ tool_name: str, input_data: ToolInput | dict[str, Any], context: ToolPermissionContext
168
+ ) -> PermissionResult:
166
169
  raise ValueError("Callback error")
167
170
 
168
171
  transport = MockTransport()
169
- query = Query(
170
- transport=transport,
171
- can_use_tool=error_callback,
172
- hooks=None,
173
- )
172
+ query = Query(transport=transport, can_use_tool=error_callback)
174
173
  request_data = SDKControlPermissionRequest(
175
174
  tool_name="TestTool",
176
175
  input={},
@@ -201,7 +200,9 @@ class TestHookCallbacks:
201
200
 
202
201
  transport = MockTransport()
203
202
  # Create hooks configuration
204
- hooks = {"tool_use_start": [HookMatcher(matcher="TestTool", hooks=[test_hook])]}
203
+ hooks: dict[HookEvent, list[HookMatcher]] = {
204
+ "tool_use_start": [HookMatcher(matcher="TestTool", hooks=[test_hook])]
205
+ }
205
206
  query = Query(transport=transport, can_use_tool=None, hooks=hooks)
206
207
  # Manually register the hook callback to avoid needing the full initialize flow
207
208
  callback_id = "test_hook_0"
@@ -261,7 +262,9 @@ class TestHookCallbacks:
261
262
  }
262
263
 
263
264
  transport = MockTransport()
264
- hooks = {"PreToolUse": [HookMatcher(matcher="TestTool", hooks=[comprehensive_hook])]}
265
+ hooks: dict[HookEvent, list[HookMatcher]] = {
266
+ "PreToolUse": [HookMatcher(matcher="TestTool", hooks=[comprehensive_hook])]
267
+ }
265
268
 
266
269
  query = Query(transport=transport, can_use_tool=None, hooks=hooks)
267
270
 
@@ -319,7 +322,9 @@ class TestHookCallbacks:
319
322
  return {"async_": True, "asyncTimeout": 5000}
320
323
 
321
324
  transport = MockTransport()
322
- hooks = {"PreToolUse": [HookMatcher(hooks=[async_hook])]}
325
+ hooks: dict[HookEvent, list[HookMatcher]] = {
326
+ "PreToolUse": [HookMatcher(hooks=[async_hook])]
327
+ }
323
328
  query = Query(transport=transport, can_use_tool=None, hooks=hooks)
324
329
  callback_id = "test_async_hook"
325
330
  query.hook_callbacks[callback_id] = async_hook
@@ -369,7 +374,9 @@ class TestHookCallbacks:
369
374
  }
370
375
 
371
376
  transport = MockTransport()
372
- hooks = {"PreToolUse": [HookMatcher(hooks=[conversion_test_hook])]}
377
+ hooks: dict[HookEvent, list[HookMatcher]] = {
378
+ "PreToolUse": [HookMatcher(hooks=[conversion_test_hook])]
379
+ }
373
380
  query = Query(transport=transport, can_use_tool=None, hooks=hooks)
374
381
  callback_id = "test_conversion"
375
382
  query.hook_callbacks[callback_id] = conversion_test_hook
@@ -21,6 +21,7 @@ from clawd_code_sdk.models import (
21
21
  AgentDefinition,
22
22
  ClaudeAgentOptions,
23
23
  ClaudeCodeSettings,
24
+ McpStdioServerConfig,
24
25
  Network,
25
26
  Permissions,
26
27
  Sandbox,
@@ -28,6 +29,7 @@ from clawd_code_sdk.models import (
28
29
  ThinkingConfigDisabled,
29
30
  ThinkingConfigEnabled,
30
31
  )
32
+ from clawd_code_sdk.models.mcp import McpSSEServerConfig
31
33
 
32
34
 
33
35
  DEFAULT_CLI_PATH = "/usr/bin/claude"
@@ -797,7 +799,7 @@ class TestSubprocessCLITransport:
797
799
  async def __aexit__(self, *args):
798
800
  pass
799
801
 
800
- transport._write_lock = NoOpLock() # pyright: ignore[reportAttributeAccessIssue]
802
+ transport._write_lock = NoOpLock() # pyright: ignore[reportAttributeAccessIssue] # ty:ignore[invalid-assignment]
801
803
 
802
804
  # Without the lock, writes may interleave. We verify the lock
803
805
  # exists and is used by checking the serialized test passes
@@ -983,7 +985,7 @@ class TestSubprocessCLITransport:
983
985
  description="Agent with MCP servers",
984
986
  prompt="You have MCP tools",
985
987
  mcp_servers={
986
- "git": {"command": "uvx", "args": ["mcp-server-git"]},
988
+ "git": McpStdioServerConfig(command="uvx", args=["mcp-server-git"]),
987
989
  },
988
990
  )
989
991
  serialized = agent_with_mcp.to_dict()
@@ -1042,7 +1044,7 @@ class TestSubprocessCLITransport:
1042
1044
  model="sonnet",
1043
1045
  memory="project",
1044
1046
  mcp_servers={
1045
- "git": {"command": "uvx", "args": ["mcp-server-git"]},
1047
+ "git": McpStdioServerConfig(command="uvx", args=["mcp-server-git"]),
1046
1048
  },
1047
1049
  )
1048
1050
  serialized = agent.to_dict()
@@ -1063,8 +1065,8 @@ class TestSubprocessCLITransport:
1063
1065
  description="Multi-MCP agent",
1064
1066
  prompt="You have many tools",
1065
1067
  mcp_servers={
1066
- "git": {"command": "uvx", "args": ["mcp-server-git"]},
1067
- "remote": {"type": "sse", "url": "http://localhost:8080/sse"},
1068
+ "git": McpStdioServerConfig(command="uvx", args=["mcp-server-git"]),
1069
+ "remote": McpSSEServerConfig(type="sse", url="http://localhost:8080/sse"),
1068
1070
  },
1069
1071
  )
1070
1072
  serialized = agent.to_dict()
@@ -1,30 +0,0 @@
1
- """Thinking configuration models."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import Literal
6
-
7
- from clawd_code_sdk.models.base import ClaudeCodeBaseModel
8
-
9
-
10
- # Thinking configuration types
11
- class ThinkingConfigAdaptive(ClaudeCodeBaseModel):
12
- """Adaptive thinking configuration - model decides thinking budget."""
13
-
14
- type: Literal["adaptive"] = "adaptive"
15
-
16
-
17
- class ThinkingConfigEnabled(ClaudeCodeBaseModel):
18
- """Enabled thinking configuration with explicit token budget."""
19
-
20
- type: Literal["enabled"] = "enabled"
21
- budget_tokens: int
22
-
23
-
24
- class ThinkingConfigDisabled(ClaudeCodeBaseModel):
25
- """Disabled thinking configuration."""
26
-
27
- type: Literal["disabled"] = "disabled"
28
-
29
-
30
- ThinkingConfig = ThinkingConfigAdaptive | ThinkingConfigEnabled | ThinkingConfigDisabled
File without changes
File without changes
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  from .agents import AgentDefinition, AgentInfo, ToolsPreset
6
6
  from .base import (
7
7
  ApiKeySource,
8
+ AssistantMessageError,
8
9
  ElicitationAction,
9
10
  ElicitationMode,
10
11
  PermissionMode,
@@ -223,7 +224,6 @@ from .content_blocks import (
223
224
  from .session import SDKSessionInfo, ListSessionsOptions
224
225
  from .messages import (
225
226
  AssistantMessage,
226
- AssistantMessageError,
227
227
  ModelUsage,
228
228
  BaseResultMessage,
229
229
  PromptSuggestionMessage,