Skip to content

Commit 4b27a9e

Browse files
authored
Merge branch 'anthropics:main' into main
2 parents 22e387e + 302ceb6 commit 4b27a9e

16 files changed

Lines changed: 1143 additions & 25 deletions

.github/workflows/build-and-publish.yml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
runs-on: ${{ matrix.os }}
1919
strategy:
2020
matrix:
21-
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest]
21+
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, macos-15-intel, windows-latest]
2222
steps:
2323
- uses: actions/checkout@v4
2424

@@ -67,6 +67,25 @@ jobs:
6767
pattern: wheel-*
6868
merge-multiple: true
6969

70+
- name: Verify all expected wheels are present
71+
run: |
72+
set -euo pipefail
73+
ls -la dist/
74+
expected=(
75+
"macosx_11_0_arm64"
76+
"macosx_11_0_x86_64"
77+
"manylinux_2_17_x86_64"
78+
"manylinux_2_17_aarch64"
79+
"win_amd64"
80+
)
81+
for tag in "${expected[@]}"; do
82+
if ! ls dist/*"${tag}".whl 2>/dev/null; then
83+
echo "::error::Missing wheel for platform tag: ${tag}"
84+
exit 1
85+
fi
86+
done
87+
echo "All ${#expected[@]} expected wheels present ✓"
88+
7089
- name: Build sdist and publish to PyPI
7190
run: |
7291
pip install build twine
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
name: Build Wheel Check
2+
3+
# Dry-run the wheel build on PRs so we catch packaging regressions
4+
# before a release, instead of discovering them at publish time.
5+
on:
6+
pull_request:
7+
paths:
8+
- 'scripts/build_wheel.py'
9+
- 'scripts/download_cli.py'
10+
- '.github/workflows/build-and-publish.yml'
11+
- '.github/workflows/build-wheel-check.yml'
12+
- 'pyproject.toml'
13+
- 'MANIFEST.in'
14+
15+
jobs:
16+
build:
17+
runs-on: ${{ matrix.os }}
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
include:
22+
- os: ubuntu-latest
23+
expected_tag: manylinux_2_17_x86_64
24+
- os: ubuntu-24.04-arm
25+
expected_tag: manylinux_2_17_aarch64
26+
- os: macos-latest
27+
expected_tag: macosx_11_0_arm64
28+
- os: macos-15-intel
29+
expected_tag: macosx_11_0_x86_64
30+
- os: windows-latest
31+
expected_tag: win_amd64
32+
steps:
33+
- uses: actions/checkout@v4
34+
35+
- uses: actions/setup-python@v5
36+
with:
37+
python-version: '3.12'
38+
39+
- name: Install build dependencies
40+
run: pip install build twine wheel
41+
shell: bash
42+
43+
- name: Build wheel with bundled CLI
44+
run: python scripts/build_wheel.py --skip-sdist --clean
45+
shell: bash
46+
47+
- name: Verify wheel filename contains expected platform tag
48+
shell: bash
49+
run: |
50+
set -euo pipefail
51+
ls -la dist/
52+
wheel=$(ls dist/*.whl)
53+
echo "Built wheel: $wheel"
54+
if [[ "$wheel" != *"${{ matrix.expected_tag }}"* ]]; then
55+
echo "::error::Expected platform tag '${{ matrix.expected_tag }}' in wheel filename, got: $wheel"
56+
exit 1
57+
fi
58+
59+
- name: Verify wheel contains bundled CLI binary
60+
shell: bash
61+
run: |
62+
set -euo pipefail
63+
wheel=$(ls dist/*.whl)
64+
python -m zipfile -l "$wheel" > wheel_contents.txt
65+
cat wheel_contents.txt
66+
if [[ "${{ runner.os }}" == "Windows" ]]; then
67+
grep -q "_bundled/claude.exe" wheel_contents.txt || {
68+
echo "::error::Bundled CLI (claude.exe) not found in wheel"
69+
exit 1
70+
}
71+
else
72+
grep -q "_bundled/claude" wheel_contents.txt || {
73+
echo "::error::Bundled CLI (claude) not found in wheel"
74+
exit 1
75+
}
76+
fi
77+
echo "Bundled CLI found in wheel ✓"
78+
79+
- name: Smoke-test install and import
80+
shell: bash
81+
run: |
82+
pip install dist/*.whl
83+
python -c "import claude_agent_sdk; print('import ok')"
84+
python -c "import claude_agent_sdk, pathlib, platform; cli_name = 'claude.exe' if platform.system() == 'Windows' else 'claude'; p = pathlib.Path(claude_agent_sdk.__file__).parent / '_bundled' / cli_name; assert p.exists() and p.is_file(), f'bundled CLI missing after install: {p}'; print('bundled CLI at', p)"
85+
86+
- uses: actions/upload-artifact@v4
87+
with:
88+
name: wheel-check-${{ matrix.expected_tag }}
89+
path: dist/*.whl
90+
retention-days: 7
91+
92+
verify-matrix-sync:
93+
# Guard against the build matrix here drifting from the publish matrix.
94+
runs-on: ubuntu-latest
95+
steps:
96+
- uses: actions/checkout@v4
97+
- name: Check build matrices are in sync
98+
run: |
99+
set -euo pipefail
100+
publish=$(grep -oP 'os:\s*\[\K[^\]]+' .github/workflows/build-and-publish.yml | tr -d ' ' | tr ',' '\n' | sort)
101+
check=$(python3 -c "import yaml; d = yaml.safe_load(open('.github/workflows/build-wheel-check.yml')); print('\n'.join(sorted(i['os'] for i in d['jobs']['build']['strategy']['matrix']['include'])))")
102+
echo "publish matrix:"; echo "$publish"
103+
echo "check matrix:"; echo "$check"
104+
diff <(echo "$publish") <(echo "$check") || {
105+
echo "::error::build-wheel-check.yml matrix is out of sync with build-and-publish.yml"
106+
exit 1
107+
}

CHANGELOG.md

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

3+
## 0.1.48
4+
5+
### Bug Fixes
6+
7+
- **Fine-grained tool streaming**: Fixed `include_partial_messages=True` not delivering `input_json_delta` events by enabling the `CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING` environment variable in the subprocess. This regression affected versions 0.1.36 through 0.1.47 for users without the server-side feature flag (#644)
8+
9+
### Internal/Other Changes
10+
11+
- Updated bundled Claude CLI to version 2.1.71
12+
13+
## 0.1.47
14+
15+
### Internal/Other Changes
16+
17+
- Updated bundled Claude CLI to version 2.1.70
18+
319
## 0.1.46
420

521
### New Features

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,11 @@ async for message in query(prompt="Tell me a joke", options=options):
5656

5757
### Using Tools
5858

59+
By default, Claude has access to the full [Claude Code toolset](https://code.claude.com/docs/en/settings#tools-available-to-claude) (Read, Write, Edit, Bash, and others). `allowed_tools` is a permission allowlist: listed tools are auto-approved, and unlisted tools fall through to `permission_mode` and `can_use_tool` for a decision. It does not remove tools from Claude's toolset. To block specific tools, use `disallowed_tools`. See the [permissions guide](https://platform.claude.com/docs/en/agent-sdk/permissions) for the full evaluation order.
60+
5961
```python
6062
options = ClaudeAgentOptions(
61-
allowed_tools=["Read", "Write", "Bash"],
63+
allowed_tools=["Read", "Write", "Bash"], # auto-approve these tools
6264
permission_mode='acceptEdits' # auto-accept file edits
6365
)
6466

@@ -116,7 +118,8 @@ server = create_sdk_mcp_server(
116118
tools=[greet_user]
117119
)
118120

119-
# Use it with Claude
121+
# Use it with Claude. allowed_tools pre-approves the tool so it runs
122+
# without a permission prompt; it does not control tool availability.
120123
options = ClaudeAgentOptions(
121124
mcp_servers={"tools": server},
122125
allowed_tools=["mcp__tools__greet"]
@@ -183,7 +186,7 @@ options = ClaudeAgentOptions(
183186

184187
### Hooks
185188

186-
A **hook** is a Python function that the Claude Code _application_ (_not_ Claude) invokes at specific points of the Claude agent loop. Hooks can provide deterministic processing and automated feedback for Claude. Read more in [Claude Code Hooks Reference](https://docs.anthropic.com/en/docs/claude-code/hooks).
189+
A **hook** is a Python function that the Claude Code _application_ (_not_ Claude) invokes at specific points of the Claude agent loop. Hooks can provide deterministic processing and automated feedback for Claude. Read more in [Intercept and control agent behavior with hooks](https://platform.claude.com/docs/en/agent-sdk/hooks).
187190

188191
For more examples, see examples/hooks.py.
189192

@@ -267,7 +270,7 @@ See [src/claude_agent_sdk/\_errors.py](src/claude_agent_sdk/_errors.py) for all
267270

268271
## Available Tools
269272

270-
See the [Claude Code documentation](https://docs.anthropic.com/en/docs/claude-code/settings#tools-available-to-claude) for a complete list of available tools.
273+
See the [Claude Code documentation](https://code.claude.com/docs/en/settings#tools-available-to-claude) for a complete list of available tools.
271274

272275
## Examples
273276

RELEASING.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,19 @@
22

33
There are two ways to release the SDK: **automatic** (triggered by a CLI version bump) and **manual** (triggered via GitHub Actions UI).
44

5-
Both flows call the same reusable `build-and-publish.yml` workflow, which builds platform-specific wheels on 4 OS targets (Ubuntu x86, Ubuntu ARM, macOS, Windows), publishes to PyPI, updates version files, generates a changelog entry using Claude, pushes to `main`, and creates a git tag + GitHub Release.
5+
Both flows call the same reusable `build-and-publish.yml` workflow, which builds platform-specific wheels on 5 OS targets, publishes to PyPI, updates version files, generates a changelog entry using Claude, pushes to `main`, and creates a git tag + GitHub Release.
6+
7+
**Wheel targets:**
8+
9+
| Runner | Platform tag |
10+
|---|---|
11+
| `ubuntu-latest` | `manylinux_2_17_x86_64` |
12+
| `ubuntu-24.04-arm` | `manylinux_2_17_aarch64` |
13+
| `macos-latest` | `macosx_11_0_arm64` |
14+
| `macos-15-intel` | `macosx_11_0_x86_64` |
15+
| `windows-latest` | `win_amd64` |
16+
17+
PRs that touch the build scripts, `pyproject.toml`, or the publish workflow trigger `build-wheel-check.yml`, which dry-runs the full build matrix and verifies each wheel contains the bundled CLI before merge.
618

719
## Versioning
820

pyproject.toml

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

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

src/claude_agent_sdk/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
CLINotFoundError,
1414
ProcessError,
1515
)
16+
from ._internal.session_mutations import rename_session, tag_session
1617
from ._internal.sessions import get_session_messages, list_sessions
1718
from ._internal.transport import Transport
1819
from ._version import __version__
@@ -54,6 +55,10 @@
5455
PostToolUseHookInput,
5556
PreCompactHookInput,
5657
PreToolUseHookInput,
58+
RateLimitEvent,
59+
RateLimitInfo,
60+
RateLimitStatus,
61+
RateLimitType,
5762
ResultMessage,
5863
SandboxIgnoreViolations,
5964
SandboxNetworkConfig,
@@ -64,6 +69,7 @@
6469
SessionMessage,
6570
SettingSource,
6671
StopHookInput,
72+
StreamEvent,
6773
SubagentStartHookInput,
6874
SubagentStartHookSpecificOutput,
6975
SubagentStopHookInput,
@@ -361,6 +367,11 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> Any:
361367
"TaskNotificationStatus",
362368
"TaskUsage",
363369
"ResultMessage",
370+
"RateLimitEvent",
371+
"RateLimitInfo",
372+
"RateLimitStatus",
373+
"RateLimitType",
374+
"StreamEvent",
364375
"Message",
365376
"ClaudeAgentOptions",
366377
"TextBlock",
@@ -410,6 +421,9 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> Any:
410421
"get_session_messages",
411422
"SDKSessionInfo",
412423
"SessionMessage",
424+
# Session mutations
425+
"rename_session",
426+
"tag_session",
413427
# Beta support
414428
"SdkBeta",
415429
# Sandbox support
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Bundled Claude Code CLI version."""
22

3-
__cli_version__ = "2.1.69"
3+
__cli_version__ = "2.1.76"

src/claude_agent_sdk/_internal/message_parser.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
AssistantMessage,
99
ContentBlock,
1010
Message,
11+
RateLimitEvent,
12+
RateLimitInfo,
1113
ResultMessage,
1214
StreamEvent,
1315
SystemMessage,
@@ -220,6 +222,28 @@ def parse_message(data: dict[str, Any]) -> Message | None:
220222
f"Missing required field in stream_event message: {e}", data
221223
) from e
222224

225+
case "rate_limit_event":
226+
try:
227+
info = data["rate_limit_info"]
228+
return RateLimitEvent(
229+
rate_limit_info=RateLimitInfo(
230+
status=info["status"],
231+
resets_at=info.get("resetsAt"),
232+
rate_limit_type=info.get("rateLimitType"),
233+
utilization=info.get("utilization"),
234+
overage_status=info.get("overageStatus"),
235+
overage_resets_at=info.get("overageResetsAt"),
236+
overage_disabled_reason=info.get("overageDisabledReason"),
237+
raw=info,
238+
),
239+
uuid=data["uuid"],
240+
session_id=data["session_id"],
241+
)
242+
except KeyError as e:
243+
raise MessageParseError(
244+
f"Missing required field in rate_limit_event message: {e}", data
245+
) from e
246+
223247
case _:
224248
# Forward-compatible: skip unrecognized message types so newer
225249
# CLI versions don't crash older SDK versions.

0 commit comments

Comments
 (0)