Skip to content

feat: add accumulate_streaming_content option for automatic TextBlock streaming#1

Open
aetherwu wants to merge 40 commits intomainfrom
feat/streaming-textblock-accumulation
Open

feat: add accumulate_streaming_content option for automatic TextBlock streaming#1
aetherwu wants to merge 40 commits intomainfrom
feat/streaming-textblock-accumulation

Conversation

@aetherwu
Copy link
Owner

Summary

Adds automatic accumulation of streaming text/thinking deltas into TextBlock and ThinkingBlock objects, making it much easier to build real-time UIs that display text as it streams from the LLM.

Changes

  • New option: accumulate_streaming_content in ClaudeAgentOptions (default: False)
  • New component: StreamAccumulator class that tracks and accumulates streaming deltas
  • Enhanced streaming: Emits partial AssistantMessage objects with growing content blocks alongside raw StreamEvent objects
  • Full backward compatibility: Existing code works unchanged

Before (Manual Accumulation)

accumulated = {}
async for message in client.receive_messages():
    if isinstance(message, StreamEvent):
        event = message.event
        if event.get("type") == "content_block_delta":
            index = event.get("index", 0)
            delta = event.get("delta", {})
            if delta.get("type") == "text_delta":
                if index not in accumulated:
                    accumulated[index] = ""
                accumulated[index] += delta.get("text", "")

After (Automatic Accumulation)

options = ClaudeAgentOptions(
    include_partial_messages=True,
    accumulate_streaming_content=True,  # ✨ Enable automatic accumulation
)

last_text = ""
async for message in client.receive_messages():
    if isinstance(message, AssistantMessage):
        for block in message.content:
            if isinstance(block, TextBlock):
                new_text = block.text[len(last_text):]
                print(new_text, end="", flush=True)
                last_text = block.text

Key Features

  • ✅ Accumulates text, thinking, and tool use blocks
  • ✅ Supports multiple content blocks per message
  • ✅ Handles multiple concurrent sessions independently
  • ✅ Preserves parent_tool_use_id for subagent messages
  • ✅ Comprehensive test coverage (6 new tests, all 120 tests pass)
  • ✅ Type-safe (mypy compliant)
  • ✅ Fully backward compatible

Files Changed

Modified:

  • src/claude_agent_sdk/types.py - Added option
  • src/claude_agent_sdk/client.py - Pass option to Query
  • src/claude_agent_sdk/_internal/client.py - Pass option to Query
  • src/claude_agent_sdk/_internal/query.py - Integration logic

New:

  • src/claude_agent_sdk/_internal/stream_accumulator.py - Core accumulator logic
  • tests/test_stream_accumulator.py - Unit tests
  • examples/streaming_textblock_accumulation.py - Usage examples

Testing

# All tests pass
python -m pytest tests/ -k "not e2e" -q  # ✅ 120 passed

# Linting passes
python -m ruff check src/ tests/  #

# Type checking passes
python -m mypy src/  #

Resolves

Closes anthropics#164

🤖 Generated with Claude Code

… streaming

Adds new `accumulate_streaming_content` option to automatically accumulate
text/thinking deltas from stream events into TextBlock/ThinkingBlock objects
within AssistantMessage. This makes it much easier to build real-time UIs
that display streaming text from the LLM.

**Features:**
- New `accumulate_streaming_content` option in ClaudeAgentOptions
- StreamAccumulator class to manage state and build partial AssistantMessages
- Automatic accumulation of text_delta and thinking_delta events
- Support for multiple content blocks and concurrent sessions
- Emits both accumulated AssistantMessage and raw StreamEvent objects

**Benefits:**
- No manual delta tracking needed by users
- Simpler code for real-time UI updates
- Fully backward compatible (default: False)
- Type-safe with full mypy compliance

**Usage:**
```python
options = ClaudeAgentOptions(
    include_partial_messages=True,
    accumulate_streaming_content=True,  # New option
)

async with ClaudeSDKClient(options) as client:
    await client.query("Tell me a joke")

    last_text = ""
    async for message in client.receive_response():
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, TextBlock):
                    new_text = block.text[len(last_text):]
                    print(new_text, end="", flush=True)
                    last_text = block.text
```

Resolves anthropics#164

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@aetherwu
Copy link
Owner Author

Test result in a production env:

Summary

✅ All tasks completed successfully!

What We Built

  1. Installed Custom Claude SDK Fork - Added accumulate_streaming_content feature for true token-by-token streaming
  2. Updated Orchestrator - Enabled streaming options:
    - include_partial_messages=True
    - accumulate_streaming_content=True
  3. Modified Streaming Handler - Implemented delta calculation logic:
    - Track last seen text: last_text = ""
    - Calculate deltas: new_text = block.text[len(last_text):]
    - Send only new content via SSE events
  4. Created Test Suite - Comprehensive test script that verified:
    - 16 incremental updates received
    - Token-by-token streaming working correctly
    - Test passed with exit code 0

@aetherwu
Copy link
Owner Author

Branches

  • main - Synced with upstream anthropics/claude-agent-sdk-python
  • feat/streaming-textblock-accumulation - Custom feature for token-by-token streaming

Installation

This fork is used in the Swiftcast project via direct file copy:

cp -r src/claude_agent_sdk/* /path/to/project/.venv/lib/python3.11/site-packages/claude_agent_sdk/

Maintenance

Sync with upstream

git checkout main
git pull upstream main
git push origin main

Update feature branch

git checkout feat/streaming-textblock-accumulation
git rebase main
git push origin feat/streaming-textblock-accumulation --force-with-lease
EOF

## Summary

**Recommendation:** Keep `feat/streaming-textblock-accumulation` as a **separate branch**, don't merge to main.

**Why:**
1. Your project uses file copy, not git install
2. Keeps fork main clean and synced with upstream
3. Standard fork best practice
4. Easy to pull upstream updates
5. Future-proof if you change your mind about PR

**Action:** No action needed - your current structure is already optimal! Just keep main synced with upstream periodically.

KuaaMU and others added 27 commits October 23, 2025 10:16
…thropics#245)

## Summary

Fixes anthropics#238 - Resolves "command line too long" error on Windows when
using multiple subagents with long prompts.

## Problem

On Windows, the command line length is limited to 8191 characters
(cmd.exe). When using multiple subagents with long prompts, the
`--agents` JSON argument can easily exceed this limit, causing the
error:
```
命令行太长。 (command line too long)
Fatal error in message reader: Command failed with exit code 1
```

## Solution

This PR implements automatic detection and handling of command line
length limits:

1. **Platform-specific limits**: 
   - Windows: 8000 characters (safe margin below 8191)
   - Other platforms: 100,000 characters

2. **Automatic fallback**: When the command line would exceed the limit:
   - Write agents JSON to a temporary file
   - Use Claude CLI's `@filepath` syntax to reference the file
   - Clean up temp files when transport is closed

3. **Zero breaking changes**: The fix is transparent to users - it
automatically activates only when needed

## Changes

- Add `platform` and `tempfile` imports
- Add `_CMD_LENGTH_LIMIT` constant with platform-specific values
- Track temporary files in `self._temp_files` list
- Modify `_build_command()` to detect long command lines and use temp
files
- Clean up temp files in `close()` method

## Testing

- ✅ All existing tests pass (122 tests)
- ✅ Linting and type checking pass
- ✅ Minimal changes - only 47 lines added/modified
- ✅ Solution transparently handles the Windows command line limit

## Test plan

- [x] Test on Windows with multiple subagents and long prompts
- [x] Verify temp files are created and cleaned up properly
- [x] Verify normal operation (short command lines) is unaffected
- [x] Test cross-platform compatibility (limit only applies on Windows)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
…pics#284)

The changelog generation step was failing to get the previous release
tag because the checkout action was doing a shallow clone. Adding
fetch-depth: 0 ensures all tags are available for git describe.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
This PR updates the version to 0.1.4 after publishing to PyPI.

## Changes
- Updated version in `pyproject.toml`
- Updated version in `src/claude_agent_sdk/_version.py`
- Updated `CHANGELOG.md` with release notes

## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.4/
- Install with: `pip install claude-agent-sdk==0.1.4`

🤖 Generated by GitHub Actions

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
Add SdkPluginConfig type and plugins field to ClaudeAgentOptions.
Plugins can be loaded using the local type with a path to the plugin
directory.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
This PR updates the version to 0.1.5 after publishing to PyPI.

## Changes
- Updated version in `pyproject.toml`
- Updated version in `src/claude_agent_sdk/_version.py`
- Updated `CHANGELOG.md` with release notes

## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.5/
- Install with: `pip install claude-agent-sdk==0.1.5`

🤖 Generated by GitHub Actions

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Move changelog generation prompt from publish.yml into a reusable slash
command at .claude/commands/generate-changelog.md. Update workflow to
call the command with version parameters.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
…s#298)

Add support for controlling the maximum number of tokens allocated to
extended thinking blocks via the max_thinking_tokens parameter.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
Add support for limiting API costs using the max_budget_usd option,
mirroring the TypeScript SDK functionality. When the budget is exceeded,
query execution stops and returns a result with subtype
'error_max_budget_usd'.

- Add max_budget_usd field to ClaudeAgentOptions
- Pass --max-budget-usd flag to Claude Code CLI
- Add test coverage for budget limit behavior

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
Reduce CI job count by only running examples on Python 3.13 instead of
all Python versions (3.10-3.13). This reduces the combinatorial
explosion while still ensuring examples work on the latest Python
version.

Co-authored-by: Claude <noreply@anthropic.com>
This PR updates the version to 0.1.6 after publishing to PyPI.

## Changes
- Updated version in `pyproject.toml`
- Updated version in `src/claude_agent_sdk/_version.py`
- Updated `CHANGELOG.md` with release notes

## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.6/
- Install with: `pip install claude-agent-sdk==0.1.6`

🤖 Generated by GitHub Actions

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
…s#302)

Add ~/.claude/local/claude to the list of locations checked when finding
the Claude CLI binary.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
…s#317)

Add support for automatic model fallback when primary model is
overloaded. The Python SDK passes the fallback_model parameter to the
Claude CLI, which handles the validation and fallback logic.

Changes:
- Add fallback_model parameter to ClaudeAgentOptions
- Pass --fallback-model to CLI subprocess
- Add test for fallback model command building

The validation that fallback_model != model happens at the CLI layer,
keeping the SDK implementation simple and focused on parameter passing.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Add structured output support to Python SDK.

## Usage

```python
from claude_agent_sdk import query, ClaudeAgentOptions

schema = {
    "type": "object",
    "properties": {"count": {"type": "number"}},
    "required": ["count"]
}

async for msg in query(
    prompt="Count files in src/",
    options=ClaudeAgentOptions(
        output_format={"type": "json_schema", "schema": schema}
    )
):
    if hasattr(msg, 'structured_output'):
        print(msg.structured_output)
```

## Documentation

https://docs.claude.com/en/docs/agent-sdk/structured-outputs

## Tests

- Unit tests:
`tests/test_integration.py::TestIntegration::test_structured_output`
- E2E tests: `e2e-tests/test_structured_output.py` (4 tests)
This PR updates the version to 0.1.7 after publishing to PyPI.

## Changes
- Updated version in `pyproject.toml`
- Updated version in `src/claude_agent_sdk/_version.py`
- Updated `CHANGELOG.md` with release notes

## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.7/
- Install with: `pip install claude-agent-sdk==0.1.7`

🤖 Generated by GitHub Actions

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: inigo <bogini@users.noreply.github.com>
Bundle platform-specific Claude Code CLI binaries directly in the Python
package, eliminating the need for separate CLI installation.

## Changes

### Build System
- Created `scripts/download_cli.py` to fetch CLI during build
- Created `scripts/build_wheel.py` for building platform-specific wheels
- Created `scripts/update_cli_version.py` to track bundled CLI version
- Updated `pyproject.toml` to properly bundle CLI without duplicate file
warnings
- Made twine check non-blocking (License-File warnings are false
positives)

### Runtime
- Modified `subprocess_cli.py` to check for bundled CLI first
- Added `_cli_version.py` to track which CLI version is bundled
- SDK automatically uses bundled CLI, falling back to system
installation if not found
- Users can still override with `cli_path` option

### Release Workflow
- Updated GitHub workflow to build separate wheels per platform (macOS,
Linux, Windows)
- Workflow now accepts two inputs:
  - `version`: Package version to publish (e.g., `0.1.5`)
- `claude_code_version`: CLI version to bundle (e.g., `2.0.0` or
`latest`)
- Workflow builds platform-specific wheels with bundled CLI
- Creates release PR that updates:
  - `pyproject.toml` version
  - `src/claude_agent_sdk/_version.py`
  - `src/claude_agent_sdk/_cli_version.py` with bundled CLI version
  - `CHANGELOG.md` with auto-generated release notes

### Documentation
- Updated README to reflect bundled CLI (removed Node.js requirement)
- Added release workflow documentation
- Added local wheel building instructions

## Benefits

- **Zero external dependencies**: No need for Node.js or npm
- **Easier installation**: Single `pip install` gets everything
- **Version control**: Track exactly which CLI version is bundled
- **Flexible releases**: Can release new package versions with updated
CLI without code changes
- **Better user experience**: Works out of the box with no setup

Platform-specific wheels are automatically selected by pip during
installation based on the user's OS and architecture.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
…hropics#342)

Windows console encoding (cp1252) doesn't support Unicode emoji
characters, causing UnicodeEncodeError in CI. Replaced all emoji
characters with plain text equivalents.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
The bash install script (install.sh) explicitly rejects Windows. Use the
PowerShell installer (install.ps1) instead when running on Windows,
matching the approach used in test.yml.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
…pics#344)

Wheels are already ZIP files - double compression via GitHub Actions
artifacts can cause "Mis-matched data size" errors on PyPI upload.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
…thropics#345)

The build_wheel.py script uses `python -m wheel tags` to retag wheels
with platform-specific tags, but `wheel` wasn't explicitly installed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
This PR updates the version to 0.1.8 after publishing to PyPI.

## Changes
- Updated version in `pyproject.toml` to 0.1.8
- Updated version in `src/claude_agent_sdk/_version.py` to 0.1.8
- Updated bundled CLI version in `src/claude_agent_sdk/_cli_version.py`
to 2.0.45
- Updated `CHANGELOG.md` with release notes

## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.8/
- Bundled CLI version: 2.0.45
- Install with: `pip install claude-agent-sdk==0.1.8`

🤖 Generated by GitHub Actions

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
…s#350)

Remove the claude_code_version workflow input and instead read the CLI
version directly from src/claude_agent_sdk/_cli_version.py. This allows
the version to be managed separately and updated by automation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
## Summary

- Adds optional `timeout` field to `HookMatcher` dataclass in `types.py`
that allows users to specify a custom timeout (in seconds) for hooks
- Propagates the timeout value through:
- `client.py` and `_internal/client.py`: `_convert_hooks_to_internal()`
method
  - `_internal/query.py`: hook config sent to CLI

## Test plan

- [x] Verify hooks work without timeout specified (default behavior)
- [x] Verify custom timeout is passed to CLI when specified in
HookMatcher

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
actions-user and others added 12 commits November 21, 2025 01:31
This PR updates the version to 0.1.9 after publishing to PyPI.

## Changes
- Updated version in `pyproject.toml` to 0.1.9
- Updated version in `src/claude_agent_sdk/_version.py` to 0.1.9
- Updated `CHANGELOG.md` with release notes

## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.9/
- Bundled CLI version: 2.0.49
- Install with: `pip install claude-agent-sdk==0.1.9`

🤖 Generated by GitHub Actions

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
According to https://www.schemastore.org/claude-code-settings.json, the
`timeout` type is `number`, not `integer`

Signed-off-by: harupy <17039389+harupy@users.noreply.github.com>
Posts new issues to claude-agent-sdk-feedback. Can't use the default
github slack bot as it's too noisy (all comments and issue updates such
as comments, closing, etc...)

---------

Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
This PR updates the version to 0.1.10 after publishing to PyPI.

## Changes
- Updated version in `pyproject.toml` to 0.1.10
- Updated version in `src/claude_agent_sdk/_version.py` to 0.1.10
- Updated `CHANGELOG.md` with release notes

## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.10/
- Bundled CLI version: 2.0.53
- Install with: `pip install claude-agent-sdk==0.1.10`

🤖 Generated by GitHub Actions

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Streaming support in TextBlock