Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "1.3.0"
".": "1.4.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 201
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic/anthropic-e50cf35b74cc0471a2b5af7ea03765aa81c035f82588e9a1ba1b29aeaa17d064.yml
openapi_spec_hash: e4ae88bdd84c7e293dd46037a6769b8d
config_hash: e7cae7a85b1d1ebf25c2bfdff6b8fc4c
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic/anthropic-465bff21a179090915396565d1ae8f705cf8596e2ec920eb121072f25b8a7d68.yml
openapi_spec_hash: 85ce948f447920c14f37874560000153
config_hash: 4ce7f60a1597d4ae2a6fa00cd8bd15bc
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Changelog

## 1.4.0 (2026-09-03)

Full Changelog: [v1.3.0...v1.4.0](https://github.com/anthropics/anthropic-sdk-python/compare/v1.3.0...v1.4.0)

### Features

* **api:** add support for sending a workspace ID on more endpoints ([d1d2c01](https://github.com/anthropics/anthropic-sdk-python/commit/d1d2c01d8aa128080dea3d711e12cb3df95a2149))


### Bug Fixes

* **client:** raise a clear error when an httpx object is passed instead of an httpx2 one ([9447099](https://github.com/anthropics/anthropic-sdk-python/commit/94470992f0f00ed331f2faa585cfda2cb8412a08))
* repair custom-code merge in messages resources ([#580](https://github.com/anthropics/anthropic-sdk-python/issues/580)) ([85454ca](https://github.com/anthropics/anthropic-sdk-python/commit/85454cab9a323801b76500707b84c1992c1c0ee4))


### Chores

* **internal:** clean up code comments ([#578](https://github.com/anthropics/anthropic-sdk-python/issues/578)) ([d202327](https://github.com/anthropics/anthropic-sdk-python/commit/d2023274f69dd0609ddabbf8f6af08041f868d8b))
* **internal:** narrower codeowners scope ([daca8f1](https://github.com/anthropics/anthropic-sdk-python/commit/daca8f13032311676ed30a6e0527ee304e1419c9))
* **internal:** revert codeowners change ([41aa767](https://github.com/anthropics/anthropic-sdk-python/commit/41aa767464881bb51937f42fe9c426ae3081fd9f))


### Documentation

* **api:** update a few doc strings ([26c509d](https://github.com/anthropics/anthropic-sdk-python/commit/26c509d66192f2b40150b481fa89da97e532612c))

## 1.3.0 (2026-09-01)

Full Changelog: [v1.2.0...v1.3.0](https://github.com/anthropics/anthropic-sdk-python/compare/v1.2.0...v1.3.0)
Expand Down
12 changes: 0 additions & 12 deletions examples/memory/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ def conversation_loop():

messages: list[BetaMessageParam] = []

# Initialize tracking for debug
last_response_id: Optional[str] = None
last_usage = None

Expand All @@ -85,7 +84,6 @@ def conversation_loop():
print(" /memory_clear - Delete all memory")
print(" /debug - View conversation history and token usage")

# Display context management settings
print(f"\n🧹 Context Management")
print("=" * 60)

Expand Down Expand Up @@ -115,11 +113,9 @@ def conversation_loop():
elif user_input.lower() == "/debug":
print("\n🔍 Conversation history:")

# Show last response ID if available
if last_response_id:
print(f"📌 Last response ID: {last_response_id}")

# Show token usage if available
if last_usage:
usage = last_usage
input_tokens = usage.get("input_tokens", 0)
Expand Down Expand Up @@ -170,7 +166,6 @@ def conversation_loop():
spinner = Spinner("Thinking")
spinner.start()

# Use tool_runner with memory tool
try:
runner = client.beta.messages.tool_runner(
betas=["context-management-2025-06-27"],
Expand All @@ -185,22 +180,18 @@ def conversation_loop():
spinner.stop()
raise

# Process all messages from the runner
for message in runner:
spinner.stop()

# Store response ID and usage for debug display
last_response_id = message.id

if hasattr(message, "usage") and message.usage:
last_usage = message.usage.model_dump() if hasattr(message.usage, "model_dump") else dict(message.usage)

# Check for context management actions
if message.context_management:
for edit in message.context_management.applied_edits:
print(f"\n🧹 [Context Management: {edit.type} applied]")

# Process content blocks
assistant_content: list[BetaContentBlockParam] = []
for content in message.content:
if content.type == "text":
Expand All @@ -222,14 +213,11 @@ def conversation_loop():
}
)

# Store assistant message
if assistant_content:
messages.append({"role": "assistant", "content": assistant_content})

# Generate tool response automatically
tool_response = runner.generate_tool_call_response()
if tool_response and tool_response["content"]:
# Add tool results to messages
messages.append({"role": "user", "content": tool_response["content"]})

for result in tool_response["content"]:
Expand Down
1 change: 0 additions & 1 deletion examples/structured_outputs_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,5 @@ class Order(pydantic.BaseModel):
if event.type == "text":
print(event.parsed_snapshot())

# Get the final parsed output
final_message = stream.get_final_message()
print(f"\nFinal parsed order: {final_message.parsed_output}")
3 changes: 0 additions & 3 deletions examples/web_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,14 @@
],
)

# Print the full response
print("\nFull response:")
print(message.model_dump_json(indent=2))

# Extract and print the content
print("\nResponse content:")
for content_block in message.content:
if content_block.type == "text":
print(content_block.text)

# Print usage information
print("\nUsage statistics:")
print(f"Input tokens: {message.usage.input_tokens}")
print(f"Output tokens: {message.usage.output_tokens}")
Expand Down
4 changes: 0 additions & 4 deletions examples/web_search_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ async def main() -> None:
}
],
) as stream:
# Process streaming events
async for chunk in stream:
# Print text deltas as they arrive
if chunk.type == "content_block_delta" and chunk.delta.type == "text_delta":
print(chunk.delta.text, end="", flush=True)

Expand All @@ -34,7 +32,6 @@ async def main() -> None:
elif chunk.type == "content_block_stop" and chunk.content_block.type == "web_search_tool_result":
print("[Web search completed]", end="\n\n", flush=True)

# Get the final complete message
message = await stream.get_final_message()

print("\n\nFinal usage statistics:")
Expand All @@ -52,7 +49,6 @@ async def main() -> None:
for i, block in enumerate(message.content):
print(f"Content Block {i + 1}: Type = {block.type}")

# Show the entire message structure as JSON for debugging
print("\nComplete message structure (JSON):")
print(message.model_dump_json(indent=2))

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "anthropic"
version = "1.3.0"
version = "1.4.0"
description = "The official Python library for the anthropic API"
dynamic = ["readme"]
license = "MIT"
Expand Down
20 changes: 20 additions & 0 deletions src/anthropic/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,16 @@
HTTPX_DEFAULT_TIMEOUT = Timeout(5.0)


def _reject_httpx_object(name: str, value: object) -> None:
"""`httpx` objects don't work with `httpx2`, and would otherwise fail deep inside a request."""
for cls in type(value).__mro__:
module = getattr(cls, "__module__", None)
if isinstance(module, str) and module.partition(".")[0] == "httpx":
raise TypeError(
f"Invalid `{name}` argument; `httpx.{cls.__name__}` is from the `httpx` package, but this SDK uses `httpx2`. Use `httpx2.{cls.__name__}` instead."
)


class PageInfo:
"""Stores the necessary information to build the request to retrieve the next page.

Expand Down Expand Up @@ -400,6 +410,8 @@ def __init__(
custom_query: Mapping[str, object] | None = None,
middleware: Sequence[MiddlewareInput] | None = None,
) -> None:
_reject_httpx_object("timeout", timeout)

self._version = version
self._base_url = self._enforce_trailing_slash(URL(base_url))
self.max_retries = max_retries
Expand Down Expand Up @@ -500,6 +512,8 @@ def _build_request(
*,
retries_taken: int = 0,
) -> httpx2.Request:
_reject_httpx_object("timeout", options.timeout)

if log.isEnabledFor(logging.DEBUG):
log.debug(
"Request options: %s",
Expand Down Expand Up @@ -884,6 +898,7 @@ def _idempotency_key(self) -> str:

class _DefaultHttpxClient(httpx2.Client):
def __init__(self, **kwargs: Any) -> None:
_reject_httpx_object("transport", kwargs.get("transport"))
kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
kwargs.setdefault("follow_redirects", True)
Expand Down Expand Up @@ -973,6 +988,8 @@ def __init__(
middleware: Sequence[MiddlewareInput] | None = None,
_strict_response_validation: bool,
) -> None:
_reject_httpx_object("http_client", http_client)

if not is_given(timeout):
# if the user passed in a custom http client with a non-default
# timeout set then we use that timeout.
Expand Down Expand Up @@ -1557,6 +1574,7 @@ def get_api_list(

class _DefaultAsyncHttpxClient(httpx2.AsyncClient):
def __init__(self, **kwargs: Any) -> None:
_reject_httpx_object("transport", kwargs.get("transport"))
kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
kwargs.setdefault("follow_redirects", True)
Expand Down Expand Up @@ -1669,6 +1687,8 @@ def __init__(
custom_query: Mapping[str, object] | None = None,
middleware: Sequence[MiddlewareInput] | None = None,
) -> None:
_reject_httpx_object("http_client", http_client)

if not is_given(timeout):
# if the user passed in a custom http client with a non-default
# timeout set then we use that timeout.
Expand Down
33 changes: 32 additions & 1 deletion src/anthropic/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
is_mapping_t,
get_async_library,
)
from ._compat import cached_property
from ._compat import model_copy, cached_property
from ._models import FinalRequestOptions
from ._version import __version__
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
from ._exceptions import APIStatusError
Expand Down Expand Up @@ -121,6 +122,28 @@ def _warn_env_shadow(*, api_key: str | None, auth_token: str | None) -> None:
__all__ = ["Timeout", "RequestOptions", "Anthropic", "AsyncAnthropic", "Client", "AsyncClient"]


_CLIENT_LEVEL_HEADER_PARAMS = frozenset(("anthropic-workspace-id", "anthropic-user-profile-id"))


def _keep_client_header_params(options: FinalRequestOptions) -> FinalRequestOptions:
"""Per-request header params arrive as `Omit` when unset, which would strip a value the
client itself sends (e.g. `AnthropicAWS(workspace_id=...)`, a credentials profile's
`workspace_id`, or `default_headers`). Drop those so the client-level header survives."""
headers = options.headers
if not is_given(headers) or not any(
name.lower() in _CLIENT_LEVEL_HEADER_PARAMS and isinstance(value, Omit) for name, value in headers.items()
):
return options

options = model_copy(options)
options.headers = {
name: value
for name, value in headers.items()
if not (name.lower() in _CLIENT_LEVEL_HEADER_PARAMS and isinstance(value, Omit))
}
return options


class Anthropic(SyncAPIClient):
# client options
api_key: str | None
Expand Down Expand Up @@ -359,6 +382,10 @@ def default_headers(self) -> dict[str, str | Omit]:
**self._custom_headers,
}

@override
def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
return _keep_client_header_params(super()._prepare_options(options))

@override
def _validate_headers(self, headers: httpx2.Headers, omitted: frozenset[str]) -> None:
# --- credentials support (hand-written, upstream to Stainless) ---
Expand Down Expand Up @@ -781,6 +808,10 @@ def default_headers(self) -> dict[str, str | Omit]:
**self._custom_headers,
}

@override
async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
return _keep_client_header_params(await super()._prepare_options(options))

@override
def _validate_headers(self, headers: httpx2.Headers, omitted: frozenset[str]) -> None:
# --- credentials support (hand-written, upstream to Stainless) ---
Expand Down
2 changes: 1 addition & 1 deletion src/anthropic/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "anthropic"
__version__ = "1.3.0" # x-release-please-version
__version__ = "1.4.0" # x-release-please-version
1 change: 0 additions & 1 deletion src/anthropic/lib/_stainless_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ def _add(tag: str | None) -> None:
for message in messages:
_add(get_helper_tag(message))

# Check content blocks within messages
if isinstance(message, dict):
blocks: Any = cast(dict[str, Any], message).get("content")
else:
Expand Down
15 changes: 0 additions & 15 deletions src/anthropic/lib/bedrock/_mantle.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@
_DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]])


# --- Beta resources (messages-only) ---


class MantleBeta(SyncAPIResource):
@cached_property
def messages(self) -> BetaMessages:
Expand All @@ -56,9 +53,6 @@ def messages(self) -> AsyncBetaMessages:
return AsyncBetaMessages(self._client)


# --- Base ---


class BaseMantleClient(BaseClient[_HttpxClientT, _DefaultStreamT]):
@override
def _make_status_error(
Expand Down Expand Up @@ -100,9 +94,6 @@ def _make_status_error(
return APIStatusError(err_msg, response=response, body=body)


# --- Shared init logic ---


def _resolve_mantle_config(
*,
api_key: str | None,
Expand Down Expand Up @@ -158,9 +149,6 @@ def _resolve_mantle_config(
return resolved_api_key, base_url, use_sigv4, merged_headers


# --- Sync client ---


class AnthropicBedrockMantle(BaseMantleClient[httpx2.Client, Stream[Any]], SyncAPIClient):
messages: Messages
beta: MantleBeta
Expand Down Expand Up @@ -354,9 +342,6 @@ def with_middleware(self, *middleware: MiddlewareInput) -> Self:
return self.copy(middleware=[*self._middleware, *middleware])


# --- Async client ---


class AsyncAnthropicBedrockMantle(BaseMantleClient[httpx2.AsyncClient, AsyncStream[Any]], AsyncAPIClient):
messages: AsyncMessages
beta: AsyncMantleBeta
Expand Down
Loading
Loading