Skip to content
Merged
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 @@
{
".": "0.109.2"
".": "0.110.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: 116
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic/anthropic-04d2899e1e4dd48e29347b98f1265e6345ee20b75e29c12eca1068a8f7c61095.yml
openapi_spec_hash: 989d596f7660ce55a7cea748a9292b45
config_hash: 221b6331246cafc1f7ff1861d35a3640
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic/anthropic-2ecf157aa324d82fa8f3636a325aea5bd96ecab93193f6e37864ebe665c48685.yml
openapi_spec_hash: 29873ea69a87e047c4f742a960648bf0
config_hash: 48f7cbc6648bf7f1e6c68ad3dab477fc
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## 0.110.0 (2026-06-18)

Full Changelog: [v0.109.2...v0.110.0](https://github.com/anthropics/anthropic-sdk-python/compare/v0.109.2...v0.110.0)

### Features

* **api:** add support for new code_execution_20260120 tool ([5e23212](https://github.com/anthropics/anthropic-sdk-python/commit/5e23212dc0883174c879b97ef8e7e33ead4e8da5))


### Bug Fixes

* append x-stainless-helper across header merges instead of clobbering ([#105](https://github.com/anthropics/anthropic-sdk-python/issues/105)) ([922558e](https://github.com/anthropics/anthropic-sdk-python/commit/922558e2ce52e18863dab27bcc04067068827364))
* **bedrock:** preserve stream event type ([#1682](https://github.com/anthropics/anthropic-sdk-python/issues/1682)) ([b27e343](https://github.com/anthropics/anthropic-sdk-python/commit/b27e3439699174dbc41e34e2d6ef5cb1e2930c18))
* **helpers:** single source of truth for x-stainless-helper key + closed value vocabulary ([#95](https://github.com/anthropics/anthropic-sdk-python/issues/95)) ([e6f7a56](https://github.com/anthropics/anthropic-sdk-python/commit/e6f7a56bb624f4c946cb15ba7973fd6fe052e10f))

## 0.109.2 (2026-06-15)

Full Changelog: [v0.109.1...v0.109.2](https://github.com/anthropics/anthropic-sdk-python/compare/v0.109.1...v0.109.2)
Expand Down
4 changes: 4 additions & 0 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ from anthropic.types import (
CodeExecutionTool20250522,
CodeExecutionTool20250825,
CodeExecutionTool20260120,
CodeExecutionTool20260521,
CodeExecutionToolResultBlock,
CodeExecutionToolResultBlockContent,
CodeExecutionToolResultBlockParam,
Expand Down Expand Up @@ -347,6 +348,7 @@ from anthropic.types.beta import (
BetaCodeExecutionTool20250522,
BetaCodeExecutionTool20250825,
BetaCodeExecutionTool20260120,
BetaCodeExecutionTool20260521,
BetaCodeExecutionToolResultBlock,
BetaCodeExecutionToolResultBlockContent,
BetaCodeExecutionToolResultBlockParam,
Expand Down Expand Up @@ -382,6 +384,7 @@ from anthropic.types.beta import (
BetaFallbackInfoParam,
BetaFallbackMessageIterationUsage,
BetaFallbackParam,
BetaFallbackRefusalTrigger,
BetaFileDocumentSource,
BetaFileImageSource,
BetaImageBlockParam,
Expand Down Expand Up @@ -1155,6 +1158,7 @@ from anthropic.types.beta import (
BetaWebhookSessionThreadCreatedEventData,
BetaWebhookSessionThreadIdledEventData,
BetaWebhookSessionThreadTerminatedEventData,
BetaWebhookSessionUpdatedEventData,
BetaWebhookVaultArchivedEventData,
BetaWebhookVaultCreatedEventData,
BetaWebhookVaultCredentialArchivedEventData,
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 = "0.109.2"
version = "0.110.0"
description = "The official Python library for the anthropic API"
dynamic = ["readme"]
license = "MIT"
Expand Down
69 changes: 66 additions & 3 deletions src/anthropic/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ def _make_status_error(

def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0) -> httpx.Headers:
custom_headers = options.headers or {}
headers_dict = _merge_mappings(
merged_headers = merge_headers(
{
"x-stainless-timeout": str(options.timeout.read)
if isinstance(options.timeout, Timeout)
Expand All @@ -465,6 +465,7 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0
},
custom_headers,
)
headers_dict = _strip_omit(merged_headers)
self._validate_headers(headers_dict, custom_headers)

# headers are case-insensitive while dictionaries are not.
Expand Down Expand Up @@ -2538,6 +2539,11 @@ def get_architecture() -> Arch:
return "unknown"


def _strip_omit(mapping: Mapping[_T_co, Union[_T, Omit]]) -> Dict[_T_co, _T]:
"""Drop entries whose value is an `Omit` removal marker."""
return {key: value for key, value in mapping.items() if not isinstance(value, Omit)}


def _merge_mappings(
obj1: Mapping[_T_co, Union[_T, Omit]],
obj2: Mapping[_T_co, Union[_T, Omit]],
Expand All @@ -2546,8 +2552,65 @@ def _merge_mappings(

In cases with duplicate keys the second mapping takes precedence.
"""
merged = {**obj1, **obj2}
return {key: value for key, value in merged.items() if not isinstance(value, Omit)}
return _strip_omit({**obj1, **obj2})


# Append-on-merge header support (hand-written, upstream to Stainless).
#
# Headers whose values accumulate across a merge instead of the later mapping's
# value replacing the earlier one. When multiple mappings set one of these, the
# values are concatenated into a single comma-separated value (order-preserving,
# deduplicated) rather than clobbered.
_APPEND_HEADERS = frozenset({"x-stainless-helper"})


def _append_header_value(existing: str, addition: str) -> str:
"""Append `addition` to a comma-separated header value, skipping tokens that
are already present so the same helper isn't recorded twice.

Values are joined with `", "`, the same format `lib._stainless_helpers` uses
when it builds the `x-stainless-helper` header.
"""
tokens = [token for token in (raw.strip() for raw in existing.split(",")) if token]
for token in (raw.strip() for raw in addition.split(",")):
if token and token not in tokens:
tokens.append(token)
return ", ".join(tokens)


def merge_headers(*mappings: Mapping[str, Union[str, Omit]]) -> Dict[str, str]:
"""Merge header mappings, with later mappings taking precedence on a key
clash, exactly like `_merge_mappings`.

The exception is the headers in `_APPEND_HEADERS`: those keys are matched
case-insensitively (and stored under their lowercase form) and their string
values accumulate into a single comma-separated, deduplicated value instead
of the later one overriding the earlier one.

`Omit` values are preserved (they mark a header for removal and are only
dropped at request-build time, e.g. with `_strip_omit`); the
`Dict[str, str]` return type is the same fudge the rest of the header
plumbing already uses for `Omit`-bearing header mappings.
"""
merged: Dict[str, Union[str, Omit]] = {}
for mapping in mappings:
for key, value in mapping.items():
lower = key.lower()
if lower not in _APPEND_HEADERS:
merged[key] = value
continue

# Append headers are stored under their lowercase key so every
# case-variant lands on (and appends to) the same entry.
existing = merged.get(lower)
if isinstance(existing, str) and isinstance(value, str):
merged[lower] = _append_header_value(existing, value)
else:
# `Omit` (removal) can't take part in an append; the later
# value overrides, as it does for any other header.
merged[lower] = value

return cast("Dict[str, str]", merged)


def _middleware_entry_mode(request: APIRequest) -> Literal["raw", "stream", "true"] | None:
Expand Down
5 changes: 3 additions & 2 deletions src/anthropic/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
DEFAULT_MAX_RETRIES,
SyncAPIClient,
AsyncAPIClient,
merge_headers,
)

# --- credentials support (hand-written, upstream to Stainless) ---
Expand Down Expand Up @@ -448,7 +449,7 @@ def copy(

headers = self._custom_headers
if default_headers is not None:
headers = {**headers, **default_headers}
headers = merge_headers(headers, default_headers)
elif set_default_headers is not None:
headers = set_default_headers

Expand Down Expand Up @@ -861,7 +862,7 @@ def copy(

headers = self._custom_headers
if default_headers is not None:
headers = {**headers, **default_headers}
headers = merge_headers(headers, default_headers)
elif set_default_headers is not None:
headers = set_default_headers

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__ = "0.109.2" # x-release-please-version
__version__ = "0.110.0" # x-release-please-version
16 changes: 6 additions & 10 deletions src/anthropic/lib/_scoped_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,21 @@

from __future__ import annotations

from typing import TYPE_CHECKING, Dict, Literal, TypeVar, cast
from typing import TYPE_CHECKING, Dict, TypeVar, cast

from ._stainless_helpers import STAINLESS_HELPER_HEADER, StainlessHelperHeaderValue

if TYPE_CHECKING:
from .._client import Anthropic, AsyncAnthropic


__all__ = ["HelperTag", "_copy_client_with_bearer_auth"]


# The closed set of ``x-stainless-helper`` telemetry tags the runner helpers
# stamp on outgoing requests. Constrained via ``Literal`` so a typo at any
# call site is a type error rather than silently mistagged telemetry.
HelperTag = Literal["environments-work-poller", "environments-worker", "session-tool-runner"]
__all__ = ["_copy_client_with_bearer_auth"]


ClientT = TypeVar("ClientT", "Anthropic", "AsyncAnthropic")


def _copy_client_with_bearer_auth(client: ClientT, *, auth_token: str, helper: HelperTag) -> ClientT:
def _copy_client_with_bearer_auth(client: ClientT, *, auth_token: str, helper: StainlessHelperHeaderValue) -> ClientT:
"""Return a copy of ``client`` authenticated with ``auth_token`` as Bearer.

The returned sub-client inherits the parent's full configuration via
Expand Down Expand Up @@ -61,7 +57,7 @@ def _copy_client_with_bearer_auth(client: ClientT, *, auth_token: str, helper: H
scoped = client.copy(
auth_token=auth_token,
credentials=None,
default_headers={"x-stainless-helper": helper},
default_headers={STAINLESS_HELPER_HEADER: helper},
)
scoped.api_key = None
# ``_custom_headers`` is typed as ``Mapping[str, str]`` (immutable
Expand Down
Loading
Loading