Compare commits

..
Author SHA1 Message Date
John Kennedyandopen-swe[bot] <open-swe@users.noreply.github.com> 3256ef01fc ci: cover Compose image updates
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-08-24 21:45:12 +00:00
langsmith-fleet[bot] c5dca8f4ab ci: cover SDK integration Docker dependencies 2026-08-24 09:14:21 +00:00
langsmith-fleet[bot] be851a2c9f ci: preserve uv dependency bounds in Dependabot 2026-08-24 09:14:21 +00:00
11 changed files with 136 additions and 249 deletions
+115
View File
@@ -21,6 +21,16 @@ updates:
directory: "/libs/checkpoint"
schedule:
interval: "monthly"
versioning-strategy: increase
ignore:
- dependency-name: "langgraph"
- dependency-name: "langgraph-cli"
- dependency-name: "langgraph-sdk"
- dependency-name: "langgraph-prebuilt"
- dependency-name: "langgraph-checkpoint"
- dependency-name: "langgraph-checkpoint-postgres"
- dependency-name: "langgraph-checkpoint-sqlite"
- dependency-name: "langgraph-checkpoint-conformance"
groups:
minor-and-patch:
patterns:
@@ -38,6 +48,16 @@ updates:
directory: "/libs/checkpoint-conformance"
schedule:
interval: "monthly"
versioning-strategy: increase
ignore:
- dependency-name: "langgraph"
- dependency-name: "langgraph-cli"
- dependency-name: "langgraph-sdk"
- dependency-name: "langgraph-prebuilt"
- dependency-name: "langgraph-checkpoint"
- dependency-name: "langgraph-checkpoint-postgres"
- dependency-name: "langgraph-checkpoint-sqlite"
- dependency-name: "langgraph-checkpoint-conformance"
groups:
minor-and-patch:
patterns:
@@ -55,6 +75,16 @@ updates:
directory: "/libs/checkpoint-postgres"
schedule:
interval: "monthly"
versioning-strategy: increase
ignore:
- dependency-name: "langgraph"
- dependency-name: "langgraph-cli"
- dependency-name: "langgraph-sdk"
- dependency-name: "langgraph-prebuilt"
- dependency-name: "langgraph-checkpoint"
- dependency-name: "langgraph-checkpoint-postgres"
- dependency-name: "langgraph-checkpoint-sqlite"
- dependency-name: "langgraph-checkpoint-conformance"
groups:
minor-and-patch:
patterns:
@@ -72,6 +102,16 @@ updates:
directory: "/libs/checkpoint-sqlite"
schedule:
interval: "monthly"
versioning-strategy: increase
ignore:
- dependency-name: "langgraph"
- dependency-name: "langgraph-cli"
- dependency-name: "langgraph-sdk"
- dependency-name: "langgraph-prebuilt"
- dependency-name: "langgraph-checkpoint"
- dependency-name: "langgraph-checkpoint-postgres"
- dependency-name: "langgraph-checkpoint-sqlite"
- dependency-name: "langgraph-checkpoint-conformance"
groups:
minor-and-patch:
patterns:
@@ -89,6 +129,16 @@ updates:
directory: "/libs/cli"
schedule:
interval: "monthly"
versioning-strategy: increase
ignore:
- dependency-name: "langgraph"
- dependency-name: "langgraph-cli"
- dependency-name: "langgraph-sdk"
- dependency-name: "langgraph-prebuilt"
- dependency-name: "langgraph-checkpoint"
- dependency-name: "langgraph-checkpoint-postgres"
- dependency-name: "langgraph-checkpoint-sqlite"
- dependency-name: "langgraph-checkpoint-conformance"
groups:
minor-and-patch:
patterns:
@@ -106,6 +156,16 @@ updates:
directory: "/libs/langgraph"
schedule:
interval: "monthly"
versioning-strategy: increase
ignore:
- dependency-name: "langgraph"
- dependency-name: "langgraph-cli"
- dependency-name: "langgraph-sdk"
- dependency-name: "langgraph-prebuilt"
- dependency-name: "langgraph-checkpoint"
- dependency-name: "langgraph-checkpoint-postgres"
- dependency-name: "langgraph-checkpoint-sqlite"
- dependency-name: "langgraph-checkpoint-conformance"
groups:
minor-and-patch:
patterns:
@@ -123,6 +183,16 @@ updates:
directory: "/libs/prebuilt"
schedule:
interval: "monthly"
versioning-strategy: increase
ignore:
- dependency-name: "langgraph"
- dependency-name: "langgraph-cli"
- dependency-name: "langgraph-sdk"
- dependency-name: "langgraph-prebuilt"
- dependency-name: "langgraph-checkpoint"
- dependency-name: "langgraph-checkpoint-postgres"
- dependency-name: "langgraph-checkpoint-sqlite"
- dependency-name: "langgraph-checkpoint-conformance"
groups:
minor-and-patch:
patterns:
@@ -140,6 +210,16 @@ updates:
directory: "/libs/sdk-py"
schedule:
interval: "monthly"
versioning-strategy: increase
ignore:
- dependency-name: "langgraph"
- dependency-name: "langgraph-cli"
- dependency-name: "langgraph-sdk"
- dependency-name: "langgraph-prebuilt"
- dependency-name: "langgraph-checkpoint"
- dependency-name: "langgraph-checkpoint-postgres"
- dependency-name: "langgraph-checkpoint-sqlite"
- dependency-name: "langgraph-checkpoint-conformance"
groups:
minor-and-patch:
patterns:
@@ -186,3 +266,38 @@ updates:
- "*"
update-types:
- "major"
# CI builds this maintained integration image and Compose stack.
- package-ecosystem: "docker"
directory: "/libs/sdk-py/integration"
schedule:
interval: "monthly"
groups:
minor-and-patch:
patterns:
- "*"
update-types:
- "minor"
- "patch"
major:
patterns:
- "*"
update-types:
- "major"
- package-ecosystem: "docker-compose"
directory: "/libs/sdk-py/integration"
schedule:
interval: "monthly"
groups:
minor-and-patch:
patterns:
- "*"
update-types:
- "minor"
- "patch"
major:
patterns:
- "*"
update-types:
- "major"
-8
View File
@@ -2,14 +2,6 @@
This repository is a monorepo. Each library lives in a subdirectory under `libs/`.
<corridor>
## Corridor security analysis
When Corridor's `analyzePlan` tool is available, create a plan and use the tool to analyze it before generating or modifying code. Apply the resulting security guidance before writing code.
</corridor>
When you modify code in any library, run the following commands in that library's directory before creating a pull request:
- `make format` run code formatters
-8
View File
@@ -2,14 +2,6 @@
This repository is a monorepo. Each library lives in a subdirectory under `libs/`.
<corridor>
## Corridor security analysis
Before generating or modifying code, create a plan and use Corridor's `analyzePlan` tool to analyze it. Apply the resulting security guidance before writing code.
</corridor>
When you modify code in any library, run the following commands in that library's directory before creating a pull request:
- `make format` run code formatters
-9
View File
@@ -39,15 +39,6 @@
- `client.threads.stream()` now accepts `transport="sse"` (default) or
`transport="websocket"` in place of the previous transport-agnostic default.
### Fixed
- Resource-scoped auth decorators now honor `actions=` and reject empty or
invalid action lists. Because unmatched custom-auth paths remain allowed,
deployments using action-scoped handlers should configure a global
default-deny handler; `langgraph-api` 0.10+ warns about uncovered paths at
startup. Resource-specific decorators retain matching `resources=` selectors
for backward compatibility; use `@auth.on(resources=...)` for other resources.
### Notes
- The v3 streaming surface (`AsyncThreadStream`, `SyncThreadStream`, and all
+1 -1
View File
@@ -3,7 +3,7 @@ from langgraph_sdk.client import get_client, get_sync_client
from langgraph_sdk.encryption import Encryption
from langgraph_sdk.encryption.types import DecryptResult, EncryptionContext
__version__ = "0.4.4"
__version__ = "0.4.3"
__all__ = [
"Auth",
+1 -4
View File
@@ -24,7 +24,7 @@ from langchain_core.language_models.chat_model_stream import AsyncChatModelStrea
from langchain_protocol import Event, SubscribeParams
from langgraph_sdk._async.http import HttpClient
from langgraph_sdk.schema import LangSmithTracing, QueryParamTypes
from langgraph_sdk.schema import QueryParamTypes
from langgraph_sdk.stream.controller import _SeenEventIds
from langgraph_sdk.stream.decoders import (
DataDecoder,
@@ -172,7 +172,6 @@ class RunModule:
input: Any = None,
config: dict[str, Any] | None = None,
metadata: dict[str, Any] | None = None,
langsmith_tracing: LangSmithTracing | None = None,
) -> dict[str, Any]:
"""Send `run.start` to the server. Returns the result (`{"run_id": ...}`)."""
params: dict[str, Any] = {"assistant_id": self._owner.assistant_id}
@@ -182,8 +181,6 @@ class RunModule:
params["config"] = config
if metadata is not None:
params["metadata"] = metadata
if langsmith_tracing is not None:
params["langsmith_tracer"] = langsmith_tracing
loop = asyncio.get_running_loop()
gate: asyncio.Future[None] = loop.create_future()
self._owner._run_start_ready = gate
+1 -4
View File
@@ -23,7 +23,7 @@ from langchain_core.language_models.chat_model_stream import ChatModelStream
from langchain_protocol import Event, SubscribeParams
from langgraph_sdk._sync.http import SyncHttpClient
from langgraph_sdk.schema import LangSmithTracing, QueryParamTypes
from langgraph_sdk.schema import QueryParamTypes
from langgraph_sdk.stream.decoders import (
DataDecoder,
Decoder,
@@ -215,7 +215,6 @@ class SyncRunModule:
input: Any = None,
config: dict[str, Any] | None = None,
metadata: dict[str, Any] | None = None,
langsmith_tracing: LangSmithTracing | None = None,
) -> dict[str, Any]:
"""Send `run.start` to the server. Returns the result (`{"run_id": ...}`)."""
params: dict[str, Any] = {"assistant_id": self._owner.assistant_id}
@@ -225,8 +224,6 @@ class SyncRunModule:
params["config"] = config
if metadata is not None:
params["metadata"] = metadata
if langsmith_tracing is not None:
params["langsmith_tracer"] = langsmith_tracing
result = self._owner._send_command("run.start", params)
self._owner._run_seen = True
controller = self._owner._controller
+16 -67
View File
@@ -341,15 +341,9 @@ VUpdate = typing.TypeVar("VUpdate", covariant=True)
VRead = typing.TypeVar("VRead", covariant=True)
VDelete = typing.TypeVar("VDelete", covariant=True)
VSearch = typing.TypeVar("VSearch", covariant=True)
ResourceActionT = typing.TypeVar("ResourceActionT", bound=str)
_ResourceAction = typing.Literal["create", "read", "update", "delete", "search"]
_ThreadAction = _ResourceAction | typing.Literal["create_run"]
class _ResourceOn(
typing.Generic[VCreate, VRead, VUpdate, VDelete, VSearch, ResourceActionT]
):
class _ResourceOn(typing.Generic[VCreate, VRead, VUpdate, VDelete, VSearch]):
"""
Generic base class for resource-specific handlers.
"""
@@ -398,8 +392,8 @@ class _ResourceOn(
def __call__(
self,
*,
resources: str | Sequence[str] | None = None,
actions: ResourceActionT | Sequence[ResourceActionT] | None = None,
resources: str | Sequence[str],
actions: str | Sequence[str] | None = None,
) -> Callable[
[_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]],
_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch],
@@ -414,7 +408,7 @@ class _ResourceOn(
) = None,
*,
resources: str | Sequence[str] | None = None,
actions: ResourceActionT | Sequence[ResourceActionT] | None = None,
actions: str | Sequence[str] | None = None,
) -> (
_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]
| Callable[
@@ -422,66 +416,24 @@ class _ResourceOn(
_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch],
]
):
if fn is not None:
_validate_handler(fn)
return typing.cast(
"_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]",
_register_handler(self.auth, self.resource, "*", fn),
)
def decorator(
handler: _ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch],
) -> _ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]:
_validate_handler(handler)
if resources is None:
resource_list = [self.resource]
elif isinstance(resources, str):
resource_list = [resources]
elif isinstance(resources, Sequence):
resource_list = list(resources)
else:
raise TypeError("resources must be a string or sequence of strings")
if resource_list != [self.resource]:
raise ValueError(
f"Resource-specific decorator for {self.resource!r} cannot "
f"register handlers for {resource_list!r}. Use @auth.on(...) "
"for other or multiple resources."
)
if actions is None:
action_list = ["*"]
elif isinstance(actions, str):
action_list = [actions]
elif isinstance(actions, Sequence):
action_list = list(actions)
else:
raise TypeError("actions must be a string or sequence of strings")
if not action_list:
raise ValueError("actions must not be empty")
if not all(isinstance(action, str) for action in action_list):
raise TypeError("actions must be a string or sequence of strings")
valid_actions = {
value.action
for value in vars(self).values()
if isinstance(value, _ResourceActionOn)
}
invalid_actions = (
sorted(set(action_list) - valid_actions) if actions is not None else []
return typing.cast(
"_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]",
_register_handler(self.auth, self.resource, "*", handler),
)
if invalid_actions:
raise ValueError(
f"Invalid action(s) for {self.resource}: {', '.join(invalid_actions)}"
)
if len(action_list) != len(set(action_list)):
raise ValueError("actions must not contain duplicates")
for action in action_list:
if (self.resource, action) in self.auth._handlers:
raise ValueError(
f"types.Handler already set for {self.resource}, {action}."
)
for action in action_list:
_register_handler(self.auth, self.resource, action, handler)
return handler
if fn is not None:
return decorator(
typing.cast(
"_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]",
fn,
)
)
# Accept keyword-only parameters for future filtering behavior; referenced to satisfy linters.
_ = resources, actions
return decorator
@@ -492,7 +444,6 @@ class _AssistantsOn(
types.AssistantsUpdate,
types.AssistantsDelete,
types.AssistantsSearch,
_ResourceAction,
]
):
value = (
@@ -516,7 +467,6 @@ class _ThreadsOn(
types.ThreadsUpdate,
types.ThreadsDelete,
types.ThreadsSearch,
_ThreadAction,
]
):
value = (
@@ -552,7 +502,6 @@ class _CronsOn(
types.CronsUpdate,
types.CronsDelete,
types.CronsSearch,
_ResourceAction,
]
):
value = type[
@@ -426,17 +426,11 @@ def test_sync_run_start_sends_command():
with httpx.Client(transport=fake.transport, base_url="http://test") as raw:
threads = SyncThreadsClient(SyncHttpClient(raw))
with threads.stream(thread_id="t-1", assistant_id="agent") as thread:
result = thread.run.start(
input={"x": 1},
langsmith_tracing={"project_name": "replica-project"},
)
result = thread.run.start(input={"x": 1})
assert result == {"run_id": "run-1"}
assert fake.received_commands[0]["method"] == "run.start"
assert fake.received_commands[0]["params"]["assistant_id"] == "agent"
assert fake.received_commands[0]["params"]["langsmith_tracer"] == {
"project_name": "replica-project"
}
def test_sync_events_iterates_raw_events():
@@ -287,7 +287,7 @@ async def test_command_ids_are_monotonic():
assert [c["id"] for c in fake.received_commands] == [1, 2]
async def test_run_start_forwards_config_metadata_and_langsmith_tracing():
async def test_run_start_forwards_config_and_metadata():
fake = FakeServer()
transport = httpx.ASGITransport(app=fake.app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as raw:
@@ -297,18 +297,10 @@ async def test_run_start_forwards_config_metadata_and_langsmith_tracing():
input={"x": 1},
config={"recursion_limit": 5},
metadata={"trace": "abc"},
langsmith_tracing={
"project_name": "replica-project",
"example_id": "example-1",
},
)
params = fake.received_commands[0]["params"]
assert params["config"] == {"recursion_limit": 5}
assert params["metadata"] == {"trace": "abc"}
assert params["langsmith_tracer"] == {
"project_name": "replica-project",
"example_id": "example-1",
}
async def test_run_start_raises_outside_context_manager():
-132
View File
@@ -1,132 +0,0 @@
import pytest
from langgraph_sdk import Auth
def test_handler_multiple_resources_and_actions() -> None:
auth = Auth()
@auth.on(resources=["threads", "assistants"], actions=["read", "search"])
async def allow_reads(ctx, value):
del value
return {"owner": ctx.user.identity}
assert auth._handlers == {
("threads", "read"): [allow_reads],
("threads", "search"): [allow_reads],
("assistants", "read"): [allow_reads],
("assistants", "search"): [allow_reads],
}
def test_resource_handler_actions_are_scoped() -> None:
auth = Auth()
@auth.on
async def deny_all(ctx, value):
del ctx, value
return False
@auth.on.threads(actions=["create", "search"])
async def handler(ctx, value):
del ctx, value
return None
@auth.on.threads(actions="create_run")
async def run_handler(ctx, value):
del ctx, value
return None
assert auth._handlers == {
("threads", "create"): [handler],
("threads", "search"): [handler],
("threads", "create_run"): [run_handler],
}
assert auth._global_handlers == [deny_all]
def test_resource_handler_preserves_wildcard() -> None:
auth = Auth()
@auth.on.threads
async def handler(ctx, value):
del ctx, value
return None
assert auth._handlers == {("threads", "*"): [handler]}
def test_resource_handler_preserves_wildcard_with_parentheses() -> None:
auth = Auth()
@auth.on.threads()
async def handler(ctx, value):
del ctx, value
return None
assert auth._handlers == {("threads", "*"): [handler]}
def test_resource_handler_accepts_matching_resource() -> None:
auth = Auth()
@auth.on.threads(resources=["threads"], actions="read")
async def handler(ctx, value):
del ctx, value
return None
assert auth._handlers == {("threads", "read"): [handler]}
@pytest.mark.parametrize(
"resources", [["assistants"], ["threads", "assistants"], [], [1]]
)
def test_resource_handler_rejects_nonmatching_resources(resources) -> None:
auth = Auth()
async def handler(ctx, value):
del ctx, value
return None
with pytest.raises(ValueError, match=r"Use @auth\.on"):
auth.on.threads(resources=resources)(handler)
assert auth._handlers == {}
@pytest.mark.parametrize(
("resource", "actions", "error"),
[
("threads", [], ValueError),
("threads", ["reed"], ValueError),
("threads", ["create", "create"], ValueError),
("threads", {"create": True}, TypeError),
("crons", ["create_run"], ValueError),
],
)
def test_resource_handler_rejects_invalid_actions(resource, actions, error) -> None:
auth = Auth()
async def handler(ctx, value):
del ctx, value
return None
with pytest.raises(error):
getattr(auth.on, resource)(actions=actions)(handler)
assert auth._handlers == {}
def test_resource_handler_registration_is_atomic() -> None:
auth = Auth()
@auth.on.threads.read
async def read_handler(ctx, value):
del ctx, value
return None
async def handler(ctx, value):
del ctx, value
return None
with pytest.raises(ValueError, match="already set"):
auth.on.threads(actions=["create", "read"])(handler)
assert auth._handlers == {("threads", "read"): [read_handler]}