Compare commits

...
Author SHA1 Message Date
Victor MoreiraandRobin 018dcd3bed feat(cli): expose Agent Server flags without breaking scripts
Co-authored-by: Robin <open-swe@users.noreply.github.com>
2026-11-11 02:34:01 +00:00
victorm-lcandClaude Opus 5.5 0ecbdb1142 chore(cli): keep generated schema descriptions stable across Python versions
msgspec copies a class's raw __doc__ into the schema. Before Python 3.13
that keeps the docstring's indentation, and EncryptionConfig is not in
the generator's class list, so regenerating on 3.12 re-indented its
description. Run every $defs description through inspect.cleandoc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 17:46:41 -08:00
victorm-lcandClaude Opus 5.5 2c8b8617ed chore(cli): rename LangSmith Deployment to LangSmith Agent Servers
Update CLI copy to match the Agent Server rename in langchainplus#62081:
the platform is "LangSmith Agent Servers", the runtime is "Agent Server",
and each deployment is "an Agent Server". Positional DEPLOYMENT_ID
arguments now display as AGENT_SERVER_ID.

Commands, flags, env vars, --json keys and URLs are unchanged. Deploy-run
outcomes ("Deployment successful!") and strings matched against server
error text keep "deployment". The 403 fixtures keep the old server wording,
since older and self-hosted control planes still send it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 17:11:49 -08:00
Richard Scarrott e5e37b5d70 fix(langgraph): match whole namespace segments in get_subgraphs (#9615)
Follows up [this review
comment](https://github.com/langchain-ai/langgraph/pull/9614#discussion_r4493053567)
on #9614.

## Summary

`get_subgraphs(namespace=...)` matched node names by string prefix and
reassigned `namespace` inside the loop. It now matches whole segments.

```diff
 for name, node in self.nodes
-  skip unless ns.startswith(name)
+  skip unless ns == name or ns.startswith(name + NS_SEP)
-  ns = ns[len(name) + 1:]   # every later node sees this
-  recurse with ns
+  recurse with ns[len(name) + 1:]
```

`_subgraph_for_namespace` filters with `namespace=` again, and #9614's
workaround comment is gone.

**Behaviour change.** With `namespace=` and `recurse=True`,
`get_subgraphs` and `aget_subgraphs` yield only the subgraph at that
namespace. Take a `research` node whose subgraph has a `review` node,
plus top-level `research_review` and `review` nodes:

| `namespace=` | before | after |
| ------------------ | ---------------------------------------- |
------------------ |
| `research_review` | `research\|review`, the parent's `review` |
`research_review` |
| `research\|review` | `research\|review`, the parent's `review` |
`research\|review` |

The Agent Server's `GET
/assistants/{id}/subgraphs/{namespace}?recurse=true` is the only other
caller that passes a namespace, and its response changes the same way.

JS `getSubgraphs` has the same prefix match but doesn't reassign, so for
`research_review` it lists `research|review` before the right graph. JS
state reads compare exact names and are unaffected. JS should get the
same fix in a follow-up.

## Evidence

New sync and async tests:

- `test_get_subgraphs_matches_whole_namespace_segments` checks that each
namespace yields only its own graph. It fails on `main`.
- `test_nested_namespace_update_raises_despite_top_level_namesake`
checks that `update_state` on `research|review` raises when `research`
declares no `review`, instead of writing through the parent's `review`.
It fails if only `get_subgraphs` is reverted.

`make format lint test` passes.

## Merge Danger

**Door:** two-way

**Blast Radius:** small. Callers that pass `namespace=` get fewer
results, and each result they lose was at the wrong namespace.
2026-11-06 16:08:12 +00:00
Richard Scarrott 22025bbdbe fix(langgraph): read an undeclared subgraph namespace with the parent graph (#9614)
## Summary

deepagents runs subagents inside its `tools` node, so their checkpoints
live at `tools:<task_id>`, where the parent declares no subgraph.
Reading that namespace raised `Subgraph tools not found`, and the Python
Agent Server returned 400 on `/history?checkpoint_ns=...`. Reads now
fall back to the parent graph, as JS does since
langchain-ai/langgraphjs#2191.

> [!WARNING]
> **This is a tactical fix, and its reads can be wrong without any
error.** The subgraph that wrote the checkpoint isn't declared on the
parent (a tool invoked it), so the parent can't find it and reads the
checkpoint with its own channels and nodes. That works for keys both
graphs share, such as deepagents' `messages`. Beyond those:
>
> - Keys only the subgraph has are missing.
> - `next`, `tasks` and interrupts come from the parent's nodes. For a
paused subgraph they come back empty, unless its node names match the
parent's (as deepagents' `model`/`tools` do).
> - A namespace nested under a declared subgraph, such as
`agent:<id>|tools:<id>`, is read with the root graph, not `agent`.
> - A mistyped namespace returns an empty snapshot and history instead
of an error (200 from the Agent Server, not 400).
>
> JS made the same trade-off in langchain-ai/langgraphjs#2191. A real
fix needs a way to find the graph that wrote the checkpoint, which
neither SDK has.

```diff
 Pregel._subgraph_for_namespace(config, checkpointer, *, for_update=False)
-  first subgraph get_subgraphs(namespace=ns) yields   # prefix match
+  the subgraph whose name equals ns, walking every subgraph
-  else raise "Subgraph {ns} not found"
+  else, from (a)get_state / (a)get_state_history: None, so read with this graph's channels
+  else, from (a)bulk_update_state / (a)update_state: raise "Subgraph {ns} not found"
```

Updates still raise, as in JS: the parent's nodes and channels would
write the wrong state into the subgraph's namespace.

The exact-name walk replaces the `namespace=` filter. That filter
matches by prefix and reassigns `namespace` mid-loop. So with nodes
`research` and `research_review`, `research_review` could resolve to
`research|review`, or miss a declared `research_review` entirely. JS
already compares exact names and doesn't reassign its namespace while
looping, so it needs no change.

## Evidence

- **Before:** `get_state` / `get_state_history` on `tools:<task_id>`
raised `ValueError: Subgraph tools not found`. When `research`'s
subgraph has its own `review` node, `research_review` was read with
`research|review`'s graph, declared or not.
**After:** new sync and async tests in `test_subgraph_persistence.py` /
`test_subgraph_persistence_async.py` pass. They fail on `main`:

  ```text
  parent's tools node invokes subagents["researcher"]   # undeclared
get_state(tools:<id>) -> messages ["research apples", "researcher done"]
    get_state_history(tools:<id>)  -> 3 snapshots, all in tools:<id>
    update_state(tools:<id>)       -> raises "Subgraph tools not found"

nodes research (declared; its subgraph has a review node) +
research_review (declared, or not)
get_state(research:<id>) -> research's subgraph (has `notes`)
get_state(research_review:<id>) -> review's subgraph (has `verdict`), or
the parent (neither)
  ```

The `research_review` declared case also fails if the exact-name check
runs over `get_subgraphs(namespace=...)`. `make format lint test` passes
in `libs/langgraph`.

## Merge Danger

**Door:** two-way

**Blast Radius:** small, but the wrong results are silent (see the
warning above). Only a state read whose `checkpoint_ns` names no
subgraph behaves differently: it returns a snapshot read with the parent
graph instead of raising.
2026-11-06 15:25:10 +00:00
Elior Nataf Lackritz 5847733bcb release(cli): 0.4.42 (#9611)
Bumps langgraph-cli from 0.4.41 to 0.4.42.

Includes:

- Add `langgraph deploy metrics`, which prints a deployment's metrics as
compact JSON (#9479)
2026-11-05 22:00:30 -05:00
Elior Nataf Lackritz 17335c9782 release(sdk-py): 0.4.14 (#9610)
Bumps the Python SDK version from 0.4.13 to 0.4.14.

Includes:

- Add `strict_resume` to `runs.create`, `runs.stream` and `runs.wait`,
so a resume that answers an interrupt that isn't waiting fails with
`UnknownInterruptError` instead of being ignored (#9599)
- Add `tools` and `lifecycle` to `StreamMode` (#9601)
2026-11-05 22:00:26 -05:00
cf35b4a3a1 feat(cli): add deploy metrics command (#9479)
Adds `langgraph deploy metrics` with compact, agent-friendly JSON
grouped by section, chart, and series. Existing time-range and metric
filters are preserved; host-backend and its UI response schema are
unchanged.

- Default: real `project_metric`, inferred units, three-decimal values,
latest/min/max/avg, and `[ISO timestamp, value]` points; empty sections
are explicit.
- `--summary`: same shape without points. `--raw`: original host-backend
schema without reshaping or rounding. Mutually exclusive.
- Bounds always report the requested window: end defaults to now (UTC),
and start defaults to 15 minutes before the resolved end. With only
`--end-time`, start is 15 minutes before that value; with only
`--start-time`, end is now. The CLI sends both bounds explicitly and
reports identical values in the output. `step_seconds` is the most
common within-series timestamp gap (smallest on ties), uses an integer
for whole seconds, and is null only when no series has two timestamps.
Statistics ignore missing/non-finite values; latest means latest
non-missing sample.

Size comparison (realistic synthetic unit-test fixture, identical
compact JSON serialization; not a live deployment benchmark):

| Before: raw UI schema | After: default | After: summary |
| --- | --- | --- |
| 4,302 bytes | 1,226 bytes (72% smaller) | 806 bytes (81% smaller) |

Known gotcha (pre-existing, unchanged here): pass `--host-url` after the
subcommand. A group-level `langgraph deploy --host-url ... metrics <id>`
silently ignores that option and can hit prod instead, producing a
confusing 403.

```bash
langgraph deploy metrics --host-url https://dev.api.host.langchain.com <id>
```

Follow-up: https://github.com/langchain-ai/langgraph/pull/9562 adds
compute and Postgres tier scaling, stacked on this branch. Retarget it
to `main` after this PR merges.

## References
- [Slack
thread](https://langchain.slack.com/archives/C0BHM1C5R5L/p1793216413707489)

Made by [Open SWE](https://github.com/langchain-ai/open-swe) · [view
thread](https://openswe.langchain.dev/agents/5c8cafeb-c089-587f-aa85-af220f2f13f6)
· openai:gpt-6.1-sol (medium)

---------

Co-authored-by: Hari Dhanushkodi <hari-dhanushkodi@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:20:14 -05:00
Elior Nataf Lackritz 60cbb4af56 docs(langgraph): say each interrupt() call has its own id in UnknownInterruptError (#9600)
Since #9589 each `interrupt()` call in a task has its own id, so the
docstring's last paragraph was out of date: a retried answer is now
refused instead of answering the task's next question.
2026-11-05 11:37:40 -05:00
Elior Nataf Lackritz 2643345bfc feat(sdk-py): add strict_resume to runs.create, stream and wait (#9599)
Adds a keyword-only `strict_resume` parameter to `client.runs.create`,
`stream` and `wait`, in the async and sync clients.

With `strict_resume=True`, a resume that answers an interrupt that isn't
waiting (answered in another tab, or a wrong id) is refused instead of
ignored: the run fails with `UnknownInterruptError` and the thread stays
as it was. The check is langgraph's strict resume mode (#9574).

The parameter is sent only when set, and only on thread runs: the
stateless overloads leave it out. A server that doesn't support it
ignores the field.
2026-11-05 11:15:21 -05:00
f35dc299b3 chore(deps): bump filelock and langgraph-api for security fixes (#9606)
Co-authored-by: Vladislav Nechakhin <vladislav-nechakhin@users.noreply.github.com>
Co-authored-by: Robin <open-swe@users.noreply.github.com>
2026-11-05 09:34:14 -05:00
Elior Nataf Lackritz 9412cb7aef feat(sdk-py): add tools and lifecycle to StreamMode (#9601)
`StreamMode` gains `tools` and `lifecycle`, so `runs.stream` can ask for
them without a type error:

- `tools`: tool events (`on_tool_start`, `on_tool_event`, `on_tool_end`
and `on_tool_error`).
- `lifecycle`: an event when each subgraph starts and ends, with the id
of the tool call that started it.

Types only; nothing changes at runtime. Crons share `StreamMode`, as in
the JS SDK, so their types take these modes too; the server answers a
cron that asks for one with a 422.

Test plan:

- [x] `make lint` and `make test` in `libs/sdk-py` (693 passed).
- [x] `make lint_package` in `libs/langgraph`, whose `RemoteGraph` uses
the SDK's `StreamMode`.
- [x] `check_sdk_methods.py`.
2026-11-05 08:12:56 -05:00
Elior Nataf Lackritz 302a4d4caf release(cli): 0.4.41 (#9596)
Bumps langgraph-cli from 0.4.40 to 0.4.41.

Includes:

- Remove the outdated private-beta notice for `--agent-id` and
`--agent-environment` from `langgraph deploy` (#9506)
2026-11-04 21:28:17 -05:00
Elior Nataf Lackritz cf260dac7f ci(cli): compare the prerelease example's versions with its pins (#9597)
Dependabot bumped the `langgraph` pin in the CLI's
`graph_prerelease_reqs` example from 1.2.6 to 1.2.19 (#9513), but the
integration test compared the built image against versions written into
the workflow. Every CLI PR that runs that check now fails with
`LANGGRAPH_VERSION != 1.2.6; 1.2.19`. Dependabot's own run passed only
because the check needs the LangSmith secret, which dependabot runs
don't get.

The check now reads each `name==version` pin from the example's
`pyproject.toml` and compares it with the version installed in the
image, so later bumps can't break it. It fails if it finds no pins.
2026-11-04 21:19:51 -05:00
Elior Nataf Lackritz a4d4187e82 release(langgraph): 1.2.20 (#9595)
Bumps langgraph from 1.2.19 to 1.2.20.

Includes:

- Add an opt-in strict resume mode that raises `UnknownInterruptError`
instead of ignoring a stale or ambiguous interrupt answer (#9574)
- Give each `interrupt()` in a task its own id; an interrupt raised
after its task already got an answer now gets a new id (#9589)
- Point the multiple-interrupts error at the current docs (#9590)
2026-11-04 21:13:38 -05:00
Elior Nataf Lackritz 572e83cf6e release(sdk-py): 0.4.13 (#9594)
Bumps the Python SDK version from 0.4.12 to 0.4.13.

Includes:

- Add `select` to `threads.get_state` and `threads.get_history`, so the
server can skip loading state keys you don't need (#9568)
- Add `cancel_reason` to `Run` (#9571)
- Show the run's id and status in the `runs.stream` example's end event
(#9588)
2026-11-04 21:07:57 -05:00
Elior Nataf Lackritz 6ea1751a93 feat(langgraph): give each interrupt() in a task its own id (#9589)
Depends on #9574.

Every `interrupt()` call in one task got the same id, a hash of the
task's namespace. So in strict resume mode (#9574), an answer retried
after a node asked its next question was taken as the answer to that
question: the shared id was still pending. Sera found this on #9574.

Now an interrupt's id also hashes how many answers its task already has,
which is the slot its answer fills. With none, it stays the bare
namespace hash, so a node that asks once keeps its id. The count is used
rather than call order because parallel `interrupt()` calls in one task
are numbered in whatever order they run. When a resume map has no entry
for the task's next interrupt, the first id still answers it, so ids
saved by older versions keep working.

A retry policy reruns a node from its first line, but the retried node
kept counting its `interrupt()` calls from the failed attempt, so it
asked again a question it already had an answer to. On main the second
answer still goes through, because all of a task's interrupts share one
id; with the new ids it was dropped. A retry now counts the calls from
the first again, so the node reuses its answers and doesn't ask again.
Sera found this too.

This changes the id of an interrupt raised after its task already got an
answer. Clients that take ids from the state or the stream aren't
affected. Several interrupts from one task, such as parallel tools in
one `ToolNode`, are still answered in the order the task raises them.

## Test plan

- `tests/test_strict_resume.py`:
- a second interrupt in one node gets its own id, and the first keeps
the namespace hash;
- an answer retried in one node is refused (`not_pending`), and the
second question is then answered by its own id;
- parallel interrupts in one node each take an answer, even when the one
called second is the one the node raises;
- a later interrupt saved with the first id, as older versions did, is
still answered;
- a node that fails after its answer and is retried reuses the answer
instead of asking again, sync and async.
- Without the new ids, the first two fail; keyed by call order instead
of the answer count, the parallel case fails; without the fallback to
the first id, the saved-id case fails; without the reset in
`run_with_retry` or `arun_with_retry`, its retry test fails.
- Full suite with `NO_DOCKER=true`: 2886 passed. `libs/prebuilt` with
`NO_DOCKER=true`: 256 passed.
2026-11-04 15:53:32 -05:00
Elior Nataf Lackritz 22ac52e278 fix(langgraph): point the multiple-interrupts error at the current docs (#9590)
The error for one resume value while several interrupts are pending
linked to
`add-human-in-the-loop#resume-multiple-interrupts-with-one-invocation`,
which now returns 404. It now links to the "Handling multiple
interrupts" section of the interrupts page, which shows how to map each
interrupt id to its answer.

Sera found it on #9589.
2026-11-04 15:38:18 -05:00
Elior Nataf Lackritz dbc60dd112 feat(langgraph): add a strict resume mode that refuses stale interrupt answers (#9574)
Today a `Command(resume=...)` that names an interrupt id that is no
longer pending is silently ignored. The interrupted tasks run again up
to `interrupt()`, and the graph pauses on the same interrupts, with no
error. A client that answers an interrupt twice, or answers one that was
dropped, can't tell anything went wrong.

This adds an opt-in strict mode, turned on by the private configurable
key `__pregel_strict_resume` (`CONFIG_KEY_STRICT_RESUME`). With it set,
the root graph raises `UnknownInterruptError` before writing anything
when the resume:

- names an interrupt id that is not pending;
- is an empty map;
- is one value while several interrupts are pending (today this raises
`RuntimeError`).

One value with no interrupt pending is still accepted, because it
resumes a graph paused at a static breakpoint (`interrupt_before` or
`interrupt_after`).

The error's `reason` (`not_pending`, `empty_map` or `ambiguous`) says
which case it was, and `interrupt_ids` and `pending_ids` carry the ids
involved.

Known gap: the interrupts one task raises share an id, so an answer
retried after the task asked its next question answers that question.
#9589 fixes it by giving each `interrupt()` in a task its own id.

The check reads the same pending interrupts the resume is applied to.
That covers subgraph interrupts, functional API tasks and a pinned
checkpoint. It is skipped for nested graphs, and when resuming at a
checkpoint the thread has moved past, because there a finished task can
rerun and interrupt again.

Without the key, nothing changes. `RemoteGraph` doesn't forward the key,
so the remote side decides for its own runs.

A matching langgraphjs change follows.

## Test plan

- `tests/test_strict_resume.py`, 44 cases over the in-memory and SQLite
savers and all three durability modes:
- an unknown id, an answered id, `{}`, and one value with two interrupts
pending are refused, and nothing is written;
- a subgraph interrupt, a second `interrupt()` in the same node, a
resumed task that raised, a pinned head checkpoint, and one value at a
static breakpoint all pass;
  - a superseded checkpoint isn't checked;
- an answer retried in one node reaches its next interrupt (the known
gap);
  - without the key, today's behavior is kept.
- Without the loop change, the 26 cases that expect a refusal fail and
the 18 others pass.
- `test_sanitize_config` covers the `RemoteGraph` drop.
- Full suite with `NO_DOCKER=true`: 2875 passed.
2026-11-04 15:01:48 -05:00
Caspar Broekhuizen b1baa6e0c3 feat(sdk-py): add cancel_reason to Run (#9571)
Adds `cancel_reason: NotRequired[str | None]` to `Run` and to
`RunSelectField`. The Agent Server already returns the field and accepts
it in `select`. It is `NotRequired` because older servers omit it.

JS SDK: langchain-ai/langgraphjs#3206.

Verified with `make format`, `make lint` and `make test` in
`libs/sdk-py`.
2026-11-04 11:39:54 -08:00
Elior Nataf Lackritz 25ccdc8091 docs(sdk-py): show the run's status in the stream example's end event (#9588)
The `runs.stream` examples now show the `end` event's `run_id` and
`status` instead of `data=None`.

JS twin: langchain-ai/langgraphjs#3219.
2026-11-04 12:53:13 -05:00
45 changed files with 2811 additions and 495 deletions

No files matched your search

+10 -15
View File
@@ -115,21 +115,16 @@ jobs:
echo "LANGSMITH_API_KEY=${{ secrets.LANGSMITH_API_KEY }}" >> .env
timeout --kill-after=30 300 python ../../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-h
echo "Finished starting up langgraph-test-h"
LANGGRAPH_VERSION=$(docker run --rm --entrypoint "" langgraph-test-h python -c "import sys; from importlib.metadata import version; v = version('langgraph'); print(v);")
if [ "$LANGGRAPH_VERSION" != "1.2.6" ]; then
echo "LANGGRAPH_VERSION != 1.2.6; $LANGGRAPH_VERSION"
exit 1
fi
LANGCHAIN_OPENAI_VERSION=$(docker run --rm --entrypoint "" langgraph-test-h python -c "import sys; from importlib.metadata import version; v = version('langchain-openai'); print(v);")
if [ "$LANGCHAIN_OPENAI_VERSION" != "1.1.14" ]; then
echo "LANGCHAIN_OPENAI_VERSION != 1.1.14; $LANGCHAIN_OPENAI_VERSION"
exit 1
fi
LANGCHAIN_ANTHROPIC_VERSION=$(docker run --rm --entrypoint "" langgraph-test-h python -c "import sys; from importlib.metadata import version; v = version('langchain-anthropic'); print(v);")
if [ "$LANGCHAIN_ANTHROPIC_VERSION" != "1.4.6" ]; then
echo "LANGCHAIN_ANTHROPIC_VERSION != 1.4.6; $LANGCHAIN_ANTHROPIC_VERSION"
exit 1
fi
pins=$(grep -oE '"[a-z0-9-]+==[^"]+"' pyproject.toml | tr -d '"')
[ -n "$pins" ] || { echo "no == pins found in pyproject.toml"; exit 1; }
for pin in $pins; do
pkg=${pin%%==*}
installed=$(docker run --rm --entrypoint "" langgraph-test-h python -c "from importlib.metadata import version; print(version('$pkg'))")
if [ "$installed" != "${pin#*==}" ]; then
echo "$pkg $installed != pinned ${pin#*==}"
exit 1
fi
done
- name: Build and test prerelease reqs fail service
if: ${{ (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') &&matrix.example.name == 'A' }}
+67 -2
View File
@@ -40,7 +40,7 @@ langgraph new [PATH] --template TEMPLATE_NAME
### `langgraph dev` 🏃‍♀️
Run LangGraph API server in development mode with hot reloading.
Run Agent Server in development mode with hot reloading.
```bash
langgraph dev [OPTIONS]
@@ -54,7 +54,7 @@ langgraph dev [OPTIONS]
### `langgraph up` 🚀
Launch LangGraph API server in Docker.
Launch Agent Server in Docker.
```bash
langgraph up [OPTIONS]
@@ -86,6 +86,71 @@ langgraph dockerfile SAVE_PATH [OPTIONS]
-c, --config FILE Config file path
```
### `langgraph deploy metrics`
Fetch Agent Server monitoring time series as JSON through the LangSmith control plane.
Use `langgraph deploy list` to find the Agent Server ID. Authentication uses the same
API key and workspace configuration as the other `deploy` commands.
```bash
langgraph deploy metrics AGENT_SERVER_ID \
--start-time 2026-10-28T00:00:00Z \
--end-time 2026-10-28T01:00:00Z \
--metrics cpu_usage,memory_usage,worker_count
```
`--metrics` accepts a comma-separated list of these names (also listed in
`langgraph deploy metrics --help`):
- Resource usage: `memory_usage`, `cpu_usage`, `disk_usage`
- Counts: `restart_count`, `replica_count`, `worker_count`, `lg_run_count`
- HTTP traffic: `responses_per_second`, `error_responses_per_second`, `p95_latency`
- Queue: `run_queue_wait_time`
These are exact metric names, not free-text search terms. Availability depends on
the Agent Server and platform; a supported metric may have no data. Use
`lg_run_count` for Agent Server runs, not the UI schema's placeholder `run_count`.
Omit `--metrics` to fetch all available metrics. Time bounds default to the last
15 minutes, and the backend picks the resolution from the time
range. This command never prompts; for an organization-scoped key, set
`LANGSMITH_TENANT_ID` to the workspace ID.
Default output is compact JSON grouped by section title, chart title, and series
name. Each series contains its real `project_metric` as `metric`, inferred `unit`
(`percent`, `count`, or `null`), `latest`, `min`, `max`, `avg`, and
`points: [[iso_timestamp, value], ...]`. Values are rounded to three decimals;
statistics use unrounded finite values and ignore missing samples. `latest` is
the latest non-missing value. Missing samples remain `null` gaps, and series with
no values have null statistics. Empty sections are marked `{"_empty":true}`.
The top level includes `deployment_id`, `start_time`, `end_time`, and
`step_seconds`. Bounds always describe the requested window, not the observed
point bounds. The CLI resolves omitted bounds once: end defaults to now (UTC),
and start defaults to 15 minutes before the resolved end. With only `--end-time`,
start is 15 minutes before that value; with only `--start-time`, end is now. Both
values are sent explicitly to the backend and output, even when there are no points. `step_seconds` is the most common gap between consecutive
timestamps within series, with ties broken by the smallest gap. Whole seconds
are integers; fractional seconds are preserved. It is `null` only when every
series has fewer than two timestamps. UI metadata and repeated series IDs are
omitted. The host-backend response itself is unchanged.
```bash
langgraph deploy metrics AGENT_SERVER_ID --summary
langgraph deploy metrics AGENT_SERVER_ID --raw
```
`--summary` returns the same shape without `points`. `--raw` returns the original
UI chart schema without reshaping or rounding. These flags are mutually exclusive.
Neither mode returns traces.
Size comparison using the realistic synthetic unit-test fixture (compact JSON
serialization for all modes; not a live Agent Server benchmark):
| Before: raw UI schema | After: default | After: summary |
| --- | --- | --- |
| 4,302 bytes | 1,226 bytes (72% smaller) | 806 bytes (81% smaller) |
## Configuration
The CLI uses a `langgraph.json` configuration file with these key settings:
+4
View File
@@ -107,6 +107,10 @@ def add_descriptions_to_schema(schema, cls):
# Recursively process nested definitions
if "$defs" in schema:
for def_name, def_schema in schema["$defs"].items():
# msgspec copies raw __doc__, which keeps indentation before Python
# 3.13, so clean it here too for classes not listed below.
if def_schema.get("description"):
def_schema["description"] = inspect.cleandoc(def_schema["description"])
# Find the class that corresponds to this definition
for potential_cls in [
Config,
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.4.40"
__version__ = "0.4.42"
+17 -17
View File
@@ -1,4 +1,4 @@
"""CLI entrypoint for LangGraph API server."""
"""CLI entrypoint for Agent Server."""
import os
import pathlib
@@ -45,7 +45,7 @@ OPT_CONFIG = click.option(
\b
Config file must be a JSON file that has the following keys:
- "dependencies": array of dependencies for langgraph API server. Dependencies can be one of the following:
- "dependencies": array of dependencies for the Agent Server. Dependencies can be one of the following:
- ".", which would look for local python packages, as well as pyproject.toml, setup.py or requirements.txt in the app directory
- "./local_package"
- "<package_name>
@@ -160,7 +160,7 @@ OPT_API_VERSION = click.option(
"--api-version",
type=str,
help=(
"API server version to use for the base image. If unspecified, the "
"Agent Server version to use for the base image. If unspecified, the "
"latest stable version will be used. Compatible ranges like "
"~=0.11.0.dev5 stay on 0.11.0.dev5 while only newer dev builds exist, "
"then resolve to the newest matching rc or stable release, for example "
@@ -264,7 +264,7 @@ cli.add_command(deploy)
@click.option(
"--base-image",
default=None,
help="Base image to use for the LangGraph API server. Pin to specific versions using version tags. Defaults to langchain/langgraph-api or langchain/langgraphjs-api."
help="Base image to use for the Agent Server. Pin to specific versions using version tags. Defaults to langchain/langgraph-api or langchain/langgraphjs-api."
"\n\n \b\nExamples:\n --base-image langchain/langgraph-server:0.2.18 # Pin to a specific patch version"
"\n --base-image langchain/langgraph-server:0.2 # Pin to a minor version (Python)",
)
@@ -273,7 +273,7 @@ cli.add_command(deploy)
is_flag=True,
help="Wait for services to start before returning. Implies --detach",
)
@cli.command(help="🚀 Launch LangGraph API server.")
@cli.command(help="🚀 Launch Agent Server.")
@log_command
def up(
config: pathlib.Path,
@@ -292,9 +292,9 @@ def up(
image: str | None,
base_image: str | None,
):
click.secho("Starting LangGraph API server...", fg="green")
click.secho("Starting Agent Server...", fg="green")
click.secho(
"""For local dev, requires env var LANGSMITH_API_KEY with access to LangSmith Deployment.
"""For local dev, requires env var LANGSMITH_API_KEY with access to LangSmith Agent Servers.
For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KEY.""",
)
with Runner() as runner, Progress(message="Pulling...") as set:
@@ -394,7 +394,7 @@ For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KE
)
@click.option(
"--base-image",
help="Base image to use for the LangGraph API server. Pin to specific versions using version tags. Defaults to langchain/langgraph-api or langchain/langgraphjs-api."
help="Base image to use for the Agent Server. Pin to specific versions using version tags. Defaults to langchain/langgraph-api or langchain/langgraphjs-api."
"\n\n \b\nExamples:\n --base-image langchain/langgraph-server:0.2.18 # Pin to a specific patch version"
"\n --base-image langchain/langgraph-server:0.2 # Pin to a minor version (Python)",
)
@@ -410,7 +410,7 @@ For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KE
)
@click.argument("docker_build_args", nargs=-1, type=click.UNPROCESSED)
@cli.command(
help="📦 Build LangGraph API server Docker image.",
help="📦 Build Agent Server Docker image.",
context_settings=dict(
ignore_unknown_options=True,
),
@@ -527,12 +527,12 @@ tests
@OPT_CONFIG
@click.argument("save_path", type=click.Path(resolve_path=True))
@cli.command(
help="🐳 Generate a Dockerfile for the LangGraph API server, with Docker Compose options."
help="🐳 Generate a Dockerfile for the Agent Server, with Docker Compose options."
)
@click.option(
"--add-docker-compose",
help=(
"Add additional files for running the LangGraph API server with "
"Add additional files for running the Agent Server with "
"docker-compose. These files include a docker-compose.yml, .env file, "
"and a .dockerignore file."
),
@@ -540,7 +540,7 @@ tests
)
@click.option(
"--base-image",
help="Base image to use for the LangGraph API server. Pin to specific versions using version tags. Defaults to langchain/langgraph-api or langchain/langgraphjs-api."
help="Base image to use for the Agent Server. Pin to specific versions using version tags. Defaults to langchain/langgraph-api or langchain/langgraphjs-api."
"\n\n \b\nExamples:\n --base-image langchain/langgraph-server:0.2.18 # Pin to a specific patch version"
"\n --base-image langchain/langgraph-server:0.2 # Pin to a minor version (Python)",
)
@@ -632,7 +632,7 @@ def dockerfile(
"\n",
"# LANGSMITH_API_KEY=your-api-key",
"\n",
"# Or if you have a LangSmith Deployment license key, "
"# Or if you have an Agent Server license key, "
"then uncomment the following line: ",
"\n",
"# LANGGRAPH_CLOUD_LICENSE_KEY=your-license-key",
@@ -729,7 +729,7 @@ def dockerfile(
"--server-log-level",
type=str,
default="WARNING",
help="Set the log level for the API server.",
help="Set the log level for the Agent Server.",
)
@click.option(
"--ssl-certfile",
@@ -757,7 +757,7 @@ def dockerfile(
)
@cli.command(
"dev",
help="🏃‍♀️‍➡️ Run LangGraph API server in development mode with hot reloading and debugging support",
help="🏃‍♀️‍➡️ Run Agent Server in development mode with hot reloading and debugging support",
)
@log_command
def dev(
@@ -776,7 +776,7 @@ def dev(
ssl_certfile: pathlib.Path | None,
ssl_keyfile: pathlib.Path | None,
):
"""CLI entrypoint for running the LangGraph API server."""
"""CLI entrypoint for running the Agent Server."""
if (ssl_certfile is None) != (ssl_keyfile is None):
raise click.UsageError(
"Both --ssl-certfile and --ssl-keyfile must be provided to enable HTTPS."
@@ -998,7 +998,7 @@ def prepare(
image: str | None = None,
base_image: str | None = None,
) -> tuple[list[str], str]:
"""Prepare the arguments and stdin for running the LangGraph API server."""
"""Prepare the arguments and stdin for running the Agent Server."""
config_json = langgraph_cli.config.validate_config_file(config_path)
warn_non_wolfi_distro(config_json)
# pull latest images
+180 -83
View File
@@ -11,8 +11,9 @@ import time
from collections.abc import Callable, Mapping, Sequence
from contextlib import contextmanager
from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from datetime import datetime, timedelta, timezone
from typing import Generic, Protocol, TypeVar
from uuid import UUID
import click
import click.exceptions
@@ -23,6 +24,7 @@ from langgraph_cli.analytics import log_command
from langgraph_cli.config import Config
from langgraph_cli.constants import DEFAULT_CONFIG
from langgraph_cli.dependency_tracking import find_tracked_packages
from langgraph_cli.deployment_metrics import compact_deployment_metrics
from langgraph_cli.docker import build_docker_image, can_build_locally
from langgraph_cli.exec import CommandRunner, Runner, subp_exec
from langgraph_cli.host_backend import (
@@ -136,7 +138,7 @@ class BuildResult:
progress_message: str = ""
timeout_seconds: int = 300
poll_interval_seconds: int = 1
no_result_message: str = "Deployment updated"
no_result_message: str = "Agent Server updated"
on_poll: Callable[[str, str, Callable[[str], None]], None] | None = None
on_interrupt: Callable[[str], None] | None = None
show_build_logs_on_failure: bool = False
@@ -463,7 +465,7 @@ def deployment_selector(deployment_id: str | None, name: str | None) -> ById | B
return ById(deployment_id)
if name:
return ByName(name)
raise click.UsageError("Either --deployment-id or --name is required.")
raise click.UsageError("Either --agent-server-id or --name is required.")
def validate_deploy_commands(
@@ -511,9 +513,9 @@ def find_deployment_by_name(
def normalize_name(value: str | None) -> str:
"""Sanitize a deployment/directory name into a valid deployment name.
"""Sanitize an Agent Server/directory name into a valid Agent Server name.
LangSmith Deployment names only allow lowercase
Agent Server names only allow lowercase
alphanumeric characters and hyphens ([a-z0-9-]).
Invalid characters are replaced with hyphens.
"""
@@ -569,8 +571,8 @@ def _validate_prebuilt_image(
if image_platform != _DEPLOYMENT_PLATFORM:
detected = image_platform or "unknown"
raise click.ClickException(
f"Docker image '{image}' targets {detected}, but LangSmith Deployment "
f"requires {_DEPLOYMENT_PLATFORM}. Rebuild or pull the image for "
f"Docker image '{image}' targets {detected}, but LangSmith Agent Servers "
f"require {_DEPLOYMENT_PLATFORM}. Rebuild or pull the image for "
f"{_DEPLOYMENT_PLATFORM} before deploying with --image."
)
_get_emitter().info(f"Image is available for {_DEPLOYMENT_PLATFORM}")
@@ -641,7 +643,7 @@ class DeploymentRow:
identifier = _text(deployment, "id")
if identifier is None:
raise HostBackendError(
"The control plane returned a deployment without an id."
"The control plane returned an Agent Server without an id."
)
return cls(identifier, _text(deployment, "name"), _deployment_url(deployment))
@@ -653,7 +655,7 @@ class DeploymentRow:
DEPLOYMENTS_TABLE = Table(
("Deployment ID", "Deployment Name", "Deployment URL"), DeploymentRow.cells
("Agent Server ID", "Agent Server Name", "Agent Server URL"), DeploymentRow.cells
)
LISTENERS_TABLE = Table(("Listener ID", "Compute ID", "Namespaces"), Listener.cells)
@@ -804,7 +806,7 @@ def _log_deploy_step(step: int, message: str, **extra: object) -> None:
def _fetch_deployment(
client: HostBackendClient, step: int, selector: ById
) -> tuple[ExistingDeployment, int]:
_log_deploy_step(step, f"Using deployment {selector.deployment_id}")
_log_deploy_step(step, f"Using Agent Server {selector.deployment_id}")
resource = _call_host_backend_with_optional_tenant(
client, lambda c: c.get_deployment(selector.deployment_id)
)
@@ -831,7 +833,7 @@ def _find_deployment(
)
if len(existing) > 1:
raise click.ClickException(
"This control plane does not filter deployments by agent, so the "
"This control plane does not filter Agent Servers by agent, so the "
f"CLI cannot tell which one belongs to '{selector.agent_id}' in "
f"{selector.environment}. Deploy by --name instead."
)
@@ -844,7 +846,7 @@ def _find_deployment(
None,
)
else:
_log_deploy_step(step, f"Looking up deployment '{selector.name}'")
_log_deploy_step(step, f"Looking up Agent Server '{selector.name}'")
found = _call_host_backend_with_optional_tenant(
client, lambda c: find_deployment_by_name(c, selector.name)
)
@@ -852,7 +854,7 @@ def _find_deployment(
if found is None:
em.warn(not_found_message)
else:
em.info(f"Found existing deployment (ID: {found.id})")
em.info(f"Found existing Agent Server (ID: {found.id})")
return found, step + 1
@@ -875,9 +877,9 @@ def _create_deployment(
) -> tuple[CreatedDeployment, int]:
_log_deploy_step(
step,
f"Creating deployment for agent '{agent['agent_id']}' in {agent['environment']}"
f"Creating Agent Server for agent '{agent['agent_id']}' in {agent['environment']}"
if agent is not None
else f"Creating deployment '{name}'",
else f"Creating Agent Server '{name}'",
)
try:
created = client.create_deployment(
@@ -891,7 +893,7 @@ def _create_deployment(
except HostBackendError as err:
if agent is not None and err.status_code == 409:
raise HostBackendError(
"This agent already has a deployment in this environment.",
"This agent already has an Agent Server in this environment.",
status_code=409,
) from None
raise
@@ -901,8 +903,8 @@ def _create_deployment(
"POST /v2/deployments succeeded but response missing a valid 'id'"
)
if agent is not None:
_get_emitter().info(f"Deployment name: {created.get('name')}")
_get_emitter().info(f"Deployment ID: {created_id}", deployment_id=created_id)
_get_emitter().info(f"Agent Server name: {created.get('name')}")
_get_emitter().info(f"Agent Server ID: {created_id}", deployment_id=created_id)
return CreatedDeployment(created_id, created), step + 1
@@ -1322,10 +1324,10 @@ def _run_local_build(
in err.message
):
raise click.ClickException(
f"Deployment '{deployment_id}' was not created by 'langgraph deploy' "
f"Agent Server '{deployment_id}' was not created by 'langgraph deploy' "
"and cannot be updated with this command.\n"
"Please create a new deployment by running 'langgraph deploy' "
"without --deployment-id, or use a different --name."
"Please create a new Agent Server by running 'langgraph deploy' "
"without --agent-server-id, or use a different --name."
) from None
raise
deployment_token = push_data.get("token")
@@ -1389,7 +1391,7 @@ def _run_local_build(
verbose=verbose,
)
_log_deploy_step(step, f"Updating deployment {deployment_id}")
_log_deploy_step(step, f"Updating Agent Server {deployment_id}")
updated = client.update_deployment(
deployment_id,
resolved_image,
@@ -1398,7 +1400,7 @@ def _run_local_build(
tracked_packages=tracked_packages,
)
return _image_revision_result(updated, "Deployment updated")
return _image_revision_result(updated, "Agent Server updated")
def _run_remote_build(
@@ -1486,7 +1488,7 @@ def _run_remote_build(
def _handle_interrupt(revision_id: str) -> None:
em.warn(
f"\nInterrupted. Deployment ID: {deployment_id}, Revision ID: {revision_id}"
f"\nInterrupted. Agent Server ID: {deployment_id}, Revision ID: {revision_id}"
)
em.warn("The build will continue remotely.")
@@ -1556,7 +1558,7 @@ def _resolve_or_create(
"Scale to zero deployments are not supported in AWS environments"
):
raise HostBackendError(
f"{err.message}\nUse --deployment-type dev instead.",
f"{err.message}\nUse --agent-server-type dev instead.",
status_code=err.status_code,
detail=err.detail,
) from None
@@ -1611,10 +1613,10 @@ def _available_listeners(client: HostBackendClient) -> tuple[Listener, ...]:
def _ensure_customer_registry_source(existing: ExistingDeployment) -> None:
if existing.source != _CUSTOMER_REGISTRY_SOURCE:
raise click.UsageError(
f"Deployment {existing.id} was not created from an external image "
f"Agent Server {existing.id} was not created from an external image "
"and cannot be updated with --push-to or --image-uri. Run without "
"either flag to keep its current build mode, or use a different "
"--name to create a new deployment."
"--name to create a new Agent Server."
)
@@ -1628,7 +1630,7 @@ class ManagedRegistrySource:
deployment_id, step = _resolve_or_create(
ctx,
source="internal_docker",
not_found_message="No deployment found. Will create after build.",
not_found_message="No Agent Server found. Will create after build.",
)
build_result = _run_local_build(
client=ctx.client,
@@ -1652,7 +1654,7 @@ class RemoteBuildSource:
deployment_id, step = _resolve_or_create(
ctx,
source="internal_source",
not_found_message="No deployment found. Will create.",
not_found_message="No Agent Server found. Will create.",
)
build_result = _run_remote_build(
client=ctx.client,
@@ -1721,24 +1723,24 @@ class CreateOnlySettings:
if push_to is None and image_uri is None and self.requested:
raise click.UsageError(
"--listener-id, --k8s-namespace, and --image-pull-secret only "
"apply when creating a deployment with --push-to or --image-uri."
"apply when creating an Agent Server with --push-to or --image-uri."
)
def ensure_not_existing(self, deployment_id: str) -> None:
if self.placement.requested:
raise click.UsageError(
"Listener and namespace are fixed when a deployment is "
f"created. Deployment {deployment_id} already exists, so "
"Listener and namespace are fixed when an Agent Server is "
f"created. Agent Server {deployment_id} already exists, so "
"drop --listener-id and --k8s-namespace, or create a new "
"deployment with a different --name."
"Agent Server with a different --name."
)
if self.image_pull_secrets:
raise click.UsageError(
"langgraph deploy does not update image pull secrets on an "
f"existing deployment. Deployment {deployment_id} already "
f"existing Agent Server. Agent Server {deployment_id} already "
"exists, so drop --image-pull-secret and update resource_spec "
"through the API or the LangSmith UI instead, or create a "
"new deployment with a different --name."
"new Agent Server with a different --name."
)
@@ -1755,7 +1757,7 @@ class CustomerRegistrySource:
ctx.client,
1,
ctx.selector,
not_found_message="No deployment found. Will create after push.",
not_found_message="No Agent Server found. Will create after push.",
)
if found is not None:
return self._update(ctx, found, step)
@@ -1769,7 +1771,7 @@ class CustomerRegistrySource:
_ensure_customer_registry_source(existing)
self.create_only.ensure_not_existing(existing.id)
image_uri, step = self._publish(ctx, step)
_log_deploy_step(step, f"Updating deployment {existing.id}")
_log_deploy_step(step, f"Updating Agent Server {existing.id}")
updated = ctx.client.update_deployment(
existing.id,
image_uri,
@@ -1778,7 +1780,7 @@ class CustomerRegistrySource:
tracked_packages=ctx.tracked_packages,
)
return DeployOutcome(
existing.id, _image_revision_result(updated, "Deployment updated")
existing.id, _image_revision_result(updated, "Agent Server updated")
)
def _resolve_placement(self, ctx: DeployContext) -> Placement:
@@ -1827,7 +1829,7 @@ class CustomerRegistrySource:
) from None
raise
return DeployOutcome(
created.id, _image_revision_result(created.resource, "Deployment created")
created.id, _image_revision_result(created.resource, "Agent Server created")
)
def _publish(self, ctx: DeployContext, step: int) -> tuple[str, int]:
@@ -2050,8 +2052,8 @@ def _call_host_backend_with_optional_tenant(
if err.status_code == 403 and "not enabled" in err.message.lower():
smith_base = client.endpoints.dashboard_url
raise HostBackendError(
"LangSmith Deployment is not enabled for this organization. "
f"Enable it at {smith_base}/host/deployments"
"LangSmith Agent Servers are not enabled for this organization. "
f"Enable them at {smith_base}/host/deployments"
" (ensure this matches the organization for your API key).",
status_code=403,
) from None
@@ -2062,6 +2064,15 @@ def _call_host_backend_with_optional_tenant(
# Click options shared by deploy commands
# ---------------------------------------------------------------------------
def _normalize_deploy_token(token: str) -> str:
"""Accept legacy option names without advertising them in help or errors."""
return {
"deployment-id": "agent-server-id",
"deployment-type": "agent-server-type",
}.get(token, token)
OPT_HOST_API_KEY = click.option(
"--api-key",
envvar="LANGGRAPH_HOST_API_KEY",
@@ -2075,9 +2086,9 @@ OPT_HOST_DEPLOYMENT_NAME = click.option(
"--name",
envvar=_DEPLOYMENT_NAME_ENV,
help=(
"Deployment name. Can also be set via LANGSMITH_DEPLOYMENT_NAME "
"Agent Server name. Can also be set via LANGSMITH_DEPLOYMENT_NAME "
"environment variable or .env file. Defaults to current directory name "
"if --deployment-id is not provided."
"if --agent-server-id is not provided."
),
)
@@ -2205,19 +2216,21 @@ def _deploy_base_options(
OPT_AGENT_ID,
OPT_AGENT_ENVIRONMENT,
click.option(
"--deployment-id",
"--agent-server-id",
"deployment_id",
help=(
"ID of an existing deployment to update. If omitted, "
"--name is used to find or create the deployment."
"ID of an existing Agent Server to update. If omitted, "
"--name is used to find or create the Agent Server."
),
),
click.option(
"--deployment-type",
"--agent-server-type",
"deployment_type",
type=click.Choice(["serverless", "dedicated", "dev", "prod"]),
default="serverless",
show_default=True,
help=(
"Deployment type (used when creating a new deployment): "
"Agent Server type (used when creating a new Agent Server): "
"serverless (scale to zero) or dedicated (prod); "
"legacy dev and prod are also accepted. "
"Ignored with --push-to."
@@ -2236,7 +2249,7 @@ def _deploy_base_options(
"--tag",
"-t",
default=None,
help="Tag to use for the pushed deployment image. [default: latest]",
help="Tag to use for the pushed Agent Server image. [default: latest]",
),
click.option(
"--image",
@@ -2269,16 +2282,16 @@ def _deploy_base_options(
click.option(
"--listener-id",
help=(
"Listener that will run the deployment, for workspaces that "
"Listener that will run the Agent Server, for workspaces that "
"deploy through a listener in your own cluster. Only used when "
"creating a deployment with --push-to or --image-uri."
"creating an Agent Server with --push-to or --image-uri."
),
),
click.option(
"--k8s-namespace",
help=(
"Kubernetes namespace the listener deploys into. Only used when "
"creating a deployment with --push-to or --image-uri."
"creating an Agent Server with --push-to or --image-uri."
),
),
click.option(
@@ -2287,9 +2300,9 @@ def _deploy_base_options(
multiple=True,
help=(
"Name of a Kubernetes secret (type kubernetes.io/"
"dockerconfigjson) in the deployment's namespace to use for "
"dockerconfigjson) in the Agent Server's namespace to use for "
"pulling the image from a private registry. Repeat to give "
"more than one. Only used when creating a deployment with "
"more than one. Only used when creating an Agent Server with "
"--push-to or --image-uri."
),
),
@@ -2356,14 +2369,18 @@ def _deploy_base_options(
@click.group(
cls=DeployGroup,
help=(
"[Beta] Build and deploy a LangGraph image to LangSmith Deployment.\n\n"
"[Beta] Build and deploy an Agent Server image to LangSmith Agent Servers.\n\n"
"This command is in beta and under active development. "
"Expect frequent updates and improvements.\n\n"
"Run from the root of your LangGraph project (where langgraph.json "
"is located). This command also accepts build flags (--base-image, "
"--config, --pull, etc.). See 'langgraph build --help' for details."
),
context_settings=dict(ignore_unknown_options=True, allow_extra_args=True),
context_settings=dict(
ignore_unknown_options=True,
allow_extra_args=True,
token_normalize_func=_normalize_deploy_token,
),
invoke_without_command=True, # allow `deploy` click group to execute without command
)
@_deploy_base_options(include_docker_args=False, validate_config_path=False)
@@ -2387,7 +2404,11 @@ def deploy(ctx: click.Context, **_: object):
@_deploy_base_options()
@click.command(context_settings=dict(ignore_unknown_options=True))
@click.command(
context_settings=dict(
ignore_unknown_options=True, token_normalize_func=_normalize_deploy_token
)
)
def _deploy_cmd(
config: pathlib.Path,
pull: bool,
@@ -2437,13 +2458,13 @@ def _deploy_cmd(
)
if name is not None or deployment_id is not None:
raise click.UsageError(
"--agent-id and --agent-environment cannot be combined with --name or --deployment-id."
"--agent-id and --agent-environment cannot be combined with --name or --agent-server-id."
)
agent = {"agent_id": agent_id, "environment": environment}
if not config.exists():
message = (
"We couldn't find a langgraph.json file. Run `langgraph deploy` from "
"the root of a LangSmith Deployment project. To get started, visit "
"the root of your LangGraph project. To get started, visit "
"https://docs.langchain.com/langsmith/deployment-quickstart."
)
if json_output:
@@ -2462,14 +2483,14 @@ def _deploy_cmd(
if no_input:
name = default_name
else:
name = click.prompt("Deployment name", default=default_name)
name = click.prompt("Agent Server name", default=default_name)
if name and not deployment_id:
name = normalize_name(name)
if not no_input:
env_path = _resolve_env_path(config_json, config)
if env_path is not None:
set_key(str(env_path), _DEPLOYMENT_NAME_ENV, name)
em.info(f"Saved deployment name to {env_path}")
em.info(f"Saved Agent Server name to {env_path}")
secrets = _secrets_from_env(_env_without_deployment_name(env_vars))
@@ -2566,7 +2587,7 @@ def _deploy_cmd(
client,
outcome.deployment_id,
last_status,
dashboard_label="Deployment dashboard",
dashboard_label="Agent Server dashboard",
status_url=dep_status_url,
)
@@ -2583,16 +2604,16 @@ def _deploy_cmd(
@click.option(
"--name-contains",
default="",
help="Only show deployments whose names contain this value.",
help="Only show Agent Servers whose names contain this value.",
)
@click.option(
"--json",
"json_output",
is_flag=True,
default=False,
help="Print the deployments as a JSON array of {id, name} instead of a table.",
help="Print the Agent Servers as a JSON array of {id, name} instead of a table.",
)
@deploy.command("list", help="[Beta] List LangSmith Deployments.")
@deploy.command("list", help="[Beta] List Agent Servers.")
def deploy_list(
api_key: str | None,
host_url: str | None,
@@ -2624,7 +2645,7 @@ def deploy_list(
if json_output:
click.echo(json_mod.dumps([deployment.as_json() for deployment in deployments]))
elif not deployments:
click.echo("No deployments found.")
click.echo("No Agent Servers found.")
else:
click.echo(DEPLOYMENTS_TABLE.render(deployments))
@@ -2634,6 +2655,81 @@ def deploy_list(
# ---------------------------------------------------------------------------
@OPT_HOST_API_KEY
@OPT_HOST_URL
@click.argument("deployment_id", type=click.UUID, metavar="AGENT_SERVER_ID")
@click.option(
"--start-time",
help="ISO8601 start time. Defaults to 15 minutes before --end-time.",
)
@click.option("--end-time", help="ISO8601 end time. Defaults to now.")
@click.option(
"--metrics",
help=(
"Comma-separated metric names. Defaults to all metrics. Supported names: "
"memory_usage, cpu_usage, disk_usage, restart_count, replica_count, "
"worker_count, lg_run_count, responses_per_second, "
"error_responses_per_second, p95_latency, run_queue_wait_time. "
"Availability depends on the Agent Server and platform."
),
)
@click.option(
"--summary", is_flag=True, help="Omit points and print per-series statistics only."
)
@click.option(
"--raw", is_flag=True, help="Print the original host-backend chart schema."
)
@deploy.command(
"metrics", help="[Beta] Fetch Agent Server monitoring time series as compact JSON."
)
def deploy_metrics(
api_key: str | None,
host_url: str | None,
deployment_id: UUID,
start_time: str | None,
end_time: str | None,
metrics: str | None,
summary: bool,
raw: bool,
) -> None:
if summary and raw:
raise click.UsageError("--summary and --raw are mutually exclusive.")
end_time = end_time or datetime.now(timezone.utc).isoformat()
if start_time is None:
try:
end = datetime.fromisoformat(end_time.replace("Z", "+00:00"))
except ValueError as exc:
raise click.BadParameter(
"Expected an ISO8601 timestamp.", param_hint="--end-time"
) from exc
end = end.replace(tzinfo=end.tzinfo or timezone.utc).astimezone(timezone.utc)
start_time = (end - timedelta(minutes=15)).isoformat()
params = {
key: value
for key, value in {
"start_time": start_time,
"end_time": end_time,
"metrics": metrics,
}.items()
if value is not None
}
client = _create_host_backend_client(host_url, api_key, no_input=True)
result = _call_host_backend_with_optional_tenant(
client,
lambda c: c.get_deployment_metrics(str(deployment_id), params),
no_input=True,
)
if not raw:
result = compact_deployment_metrics(
result,
str(deployment_id),
start_time=start_time,
end_time=end_time,
summary=summary,
)
click.echo(json_mod.dumps(result, separators=(",", ":")))
@deploy.group(
"listeners",
cls=NestedHelpGroup,
@@ -2685,7 +2781,7 @@ def deploy_listeners_list(
@deploy.group(
"revisions", cls=NestedHelpGroup, help="[Beta] Manage deployment revisions."
"revisions", cls=NestedHelpGroup, help="[Beta] Manage Agent Server revisions."
)
def deploy_revisions() -> None:
pass
@@ -2700,12 +2796,12 @@ def deploy_revisions() -> None:
show_default=True,
help="Maximum number of revisions to return.",
)
@click.argument("deployment_id")
@click.argument("deployment_id", metavar="AGENT_SERVER_ID")
@deploy_revisions.command(
"list",
help=(
"[Beta] List revisions for a LangSmith Deployment.\n\n"
"Use the `deploy list` command to list deployment IDs."
"[Beta] List revisions for an Agent Server.\n\n"
"Use the `deploy list` command to list Agent Server IDs."
),
)
def deploy_revisions_list(
@@ -2717,7 +2813,7 @@ def deploy_revisions_list(
lambda c: c.list_revisions(deployment_id, limit=limit),
)
if not revisions:
click.echo(f"No revisions found for deployment {deployment_id}.")
click.echo(f"No revisions found for Agent Server {deployment_id}.")
return
click.echo(REVISIONS_TABLE.render(_with_replaced_status(revisions)))
@@ -2735,12 +2831,12 @@ def deploy_revisions_list(
default=False,
help="Delete without prompting for confirmation.",
)
@click.argument("deployment_id")
@click.argument("deployment_id", metavar="AGENT_SERVER_ID")
@deploy.command(
"delete",
help=(
"[Beta] Delete a LangSmith Deployment.\n\n"
"Use the `deploy list` command to list deployment IDs."
"[Beta] Delete an Agent Server.\n\n"
"Use the `deploy list` command to list Agent Server IDs."
),
)
def deploy_delete(
@@ -2749,7 +2845,7 @@ def deploy_delete(
if not force:
response = click.prompt(
click.style(
f"Are you sure you want to delete deployment ID {deployment_id}? (Y/n)",
f"Are you sure you want to delete Agent Server ID {deployment_id}? (Y/n)",
fg="yellow",
),
default="Y",
@@ -2762,7 +2858,7 @@ def deploy_delete(
client,
lambda c: c.delete_deployment(deployment_id),
)
click.secho(f"Deleted deployment {deployment_id}.", fg="green")
click.secho(f"Deleted Agent Server {deployment_id}.", fg="green")
# ---------------------------------------------------------------------------
@@ -2773,8 +2869,9 @@ def deploy_delete(
@OPT_HOST_API_KEY
@OPT_HOST_DEPLOYMENT_NAME
@click.option(
"--deployment-id",
help="Deployment ID. If omitted, --name is used to find the deployment.",
"--agent-server-id",
"deployment_id",
help="Agent Server ID. If omitted, --name is used to find the Agent Server.",
)
@click.option(
"--type",
@@ -2783,8 +2880,8 @@ def deploy_delete(
default="deploy",
show_default=True,
help=(
"Log stream to fetch: 'deploy' shows agent server runtime logs; "
"'build' shows build logs (for deployments built remotely)."
"Log stream to fetch: 'deploy' shows Agent Server runtime logs; "
"'build' shows build logs (for Agent Servers built remotely)."
),
)
@click.option(
@@ -2829,7 +2926,7 @@ def deploy_delete(
@deploy.command(
"logs",
help=(
"[Beta] Fetch LangSmith Deployment logs. Use 'deploy' for agent runtime "
"[Beta] Fetch Agent Server logs. Use 'deploy' for Agent Server runtime "
"logs, or 'build' for remote build logs."
),
)
@@ -2861,14 +2958,14 @@ def deploy_logs(
client, lambda c: find_deployment_by_name(c, name_to_find)
)
if found is None:
raise click.ClickException(f"Deployment '{name_to_find}' not found.")
raise click.ClickException(f"Agent Server '{name_to_find}' not found.")
dep_id = found.id
if log_type == "build" and not revision_id:
revisions = client.list_revisions(dep_id, limit=1)
if not revisions:
raise click.ClickException(
"No revisions found for this deployment. Cannot fetch build logs."
"No revisions found for this Agent Server. Cannot fetch build logs."
)
revision_id = str(revisions[0]["id"])
click.secho(f"Using latest revision: {revision_id}", fg="cyan")
@@ -0,0 +1,95 @@
"""Compact deployment chart responses for command-line consumers."""
import math
from collections import Counter, defaultdict
from datetime import datetime, timezone
from statistics import fmean
from typing import Any
def _timestamp(value: str) -> datetime:
timestamp = datetime.fromisoformat(value.replace("Z", "+00:00"))
return timestamp.replace(tzinfo=timestamp.tzinfo or timezone.utc).astimezone(
timezone.utc
)
def _iso(value: datetime) -> str:
return value.isoformat().replace("+00:00", "Z")
def _unit(title: str, metric: str | None) -> str | None:
if "%" in title:
return "percent"
if metric in {
"restart_count",
"replica_count",
"worker_count",
"lg_run_count",
"run_count",
}:
return "count"
return None
def compact_deployment_metrics(
response: dict[str, Any],
deployment_id: str,
*,
start_time: str,
end_time: str,
summary: bool = False,
) -> dict[str, Any]:
"""Reshape UI charts without mutating the response; step is in seconds."""
sections = {}
spacings: Counter[float] = Counter()
source_sections = list(response.get("sub_sections") or [])
if response.get("charts"):
source_sections.insert(0, response)
for section in source_sections:
charts = {}
for chart in section.get("charts") or []:
by_series = defaultdict(list)
for point in chart.get("data") or []:
timestamp = _timestamp(point["timestamp"])
value = point.get("value")
if value is not None and not math.isfinite(value):
value = None
by_series[point["series_id"]].append((timestamp, value))
series_output = {}
for series in chart.get("series") or []:
points = sorted(by_series[series["id"]], key=lambda point: point[0])
times = sorted({timestamp for timestamp, _ in points})
spacings.update(
(b - a).total_seconds()
for a, b in zip(times, times[1:], strict=False)
)
values = [value for _, value in points if value is not None]
metric = series.get("project_metric")
stats = {
"metric": metric,
"unit": _unit(chart["title"], metric),
"latest": round(values[-1], 3) if values else None,
"min": round(min(values), 3) if values else None,
"max": round(max(values), 3) if values else None,
"avg": round(fmean(values), 3) if values else None,
}
if not summary:
stats["points"] = [
[
_iso(timestamp),
round(value, 3) if value is not None else None,
]
for timestamp, value in points
]
series_output[series["name"]] = stats
charts[chart["title"]] = series_output or {"_empty": True}
sections[section["title"]] = charts or {"_empty": True}
step = min(spacings, key=lambda gap: (-spacings[gap], gap)) if spacings else None
return {
"deployment_id": deployment_id,
"start_time": start_time,
"end_time": end_time,
"step_seconds": int(step) if step is not None and step.is_integer() else step,
"sections": sections or {"_empty": True},
}
+1 -1
View File
@@ -197,7 +197,7 @@ def compose_as_dict(
postgres_uri: str | None = None,
# If you are running against an already-built image, you can pass it here
image: str | None = None,
# Base image to use for the LangGraph API server
# Base image to use for the Agent Server
base_image: str | None = None,
# API version of the base image
api_version: str | None = None,
+7
View File
@@ -362,6 +362,13 @@ class HostBackendClient:
f"/v2/deployments/{deployment_id}/revisions/{revision_id}",
)
def get_deployment_metrics(
self, deployment_id: str, params: dict[str, Any]
) -> dict[str, Any]:
return self._request(
"GET", f"/v2/deployments/{deployment_id}/metrics", params=params
)
def get_build_logs(
self, project_id: str, revision_id: str, payload: dict[str, Any]
) -> Any:
+9 -9
View File
@@ -317,7 +317,7 @@ class AuthConfig(TypedDict, total=False):
"""Optional. Whether to disable LangSmith API-key authentication for requests originating the Studio.
Defaults to False, meaning that if a particular header is set, the server will verify the `x-api-key` header
value is a valid API key for the deployment's workspace. If `True`, all requests will go through your custom
value is a valid API key for the Agent Server's workspace. If `True`, all requests will go through your custom
authentication logic, regardless of origin of the request.
"""
openapi: SecurityConfig
@@ -461,7 +461,7 @@ class ConfigurableHeaderConfig(TypedDict, total=False):
class HttpConfig(TypedDict, total=False):
"""Configuration for the built-in HTTP server that powers your deployment's routes and endpoints."""
"""Configuration for the built-in HTTP server that powers your Agent Server's routes and endpoints."""
app: str
"""Optional. Import path to a custom Starlette/FastAPI application to mount.
@@ -490,12 +490,12 @@ class HttpConfig(TypedDict, total=False):
Default is False.
"""
disable_mcp: bool
"""Optional. If `True`, /mcp routes are removed, disabling default support to expose the deployment as an MCP server.
"""Optional. If `True`, /mcp routes are removed, disabling default support to expose the Agent Server as an MCP server.
Default is False.
"""
disable_a2a: bool
"""Optional. If `True`, /a2a routes are removed, disabling default support to expose the deployment as an agent-to-agent (A2A) server.
"""Optional. If `True`, /a2a routes are removed, disabling default support to expose the Agent Server as an agent-to-agent (A2A) server.
Default is False.
"""
@@ -638,16 +638,16 @@ class Config(TypedDict, total=False):
python_version: str
"""Optional. Python version in 'major.minor' format (e.g. '3.11').
Must be at least 3.11 or greater for this deployment to function properly.
Must be at least 3.11 or greater for this Agent Server to function properly.
"""
node_version: str | None
"""Optional. Node.js version as a major version (e.g. '20'), if your deployment needs Node.
"""Optional. Node.js version as a major version (e.g. '20'), if your Agent Server needs Node.
Must be >= 20 if provided.
"""
api_version: str | None
"""Optional. Which semantic version of the LangGraph API server to use.
"""Optional. Which semantic version of the Agent Server to use.
Defaults to latest. Check the
[changelog](https://docs.langchain.com/langgraph-platform/langgraph-server-changelog)
@@ -658,7 +658,7 @@ class Config(TypedDict, total=False):
"""
base_image: str | None
"""Optional. Base image to use for the LangGraph API server.
"""Optional. Base image to use for the Agent Server.
Defaults to langchain/langgraph-api or langchain/langgraphjs-api."""
@@ -740,7 +740,7 @@ class Config(TypedDict, total=False):
"""
env: dict[str, str] | str
"""Optional. Environment variables to set for your deployment.
"""Optional. Environment variables to set for your Agent Server.
- If given as a dict, keys are variable names and values are their values.
- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.
+16 -16
View File
@@ -12,7 +12,7 @@
"properties": {
"python_version": {
"type": "string",
"description": "Optional. Python version in 'major.minor' format (e.g. '3.11').\nMust be at least 3.11 or greater for this deployment to function properly.\n",
"description": "Optional. Python version in 'major.minor' format (e.g. '3.11').\nMust be at least 3.11 or greater for this Agent Server to function properly.\n",
"enum": [
"3.11",
"3.12",
@@ -50,7 +50,7 @@
"type": "null"
}
],
"description": "Optional. Which semantic version of the LangGraph API server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
"description": "Optional. Which semantic version of the Agent Server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
},
"auth": {
"anyOf": [
@@ -72,7 +72,7 @@
"type": "null"
}
],
"description": "Optional. Base image to use for the LangGraph API server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
"description": "Optional. Base image to use for the Agent Server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
},
"checkpointer": {
"anyOf": [
@@ -122,7 +122,7 @@
"type": "string"
}
],
"description": "Optional. Environment variables to set for your deployment.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
"description": "Optional. Environment variables to set for your Agent Server.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
},
"graphs": {
"type": "object",
@@ -265,7 +265,7 @@
"properties": {
"python_version": {
"type": "string",
"description": "Optional. Python version in 'major.minor' format (e.g. '3.11').\nMust be at least 3.11 or greater for this deployment to function properly.\n",
"description": "Optional. Python version in 'major.minor' format (e.g. '3.11').\nMust be at least 3.11 or greater for this Agent Server to function properly.\n",
"enum": [
"3.11",
"3.12",
@@ -303,7 +303,7 @@
"type": "null"
}
],
"description": "Optional. Which semantic version of the LangGraph API server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
"description": "Optional. Which semantic version of the Agent Server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
},
"auth": {
"anyOf": [
@@ -325,7 +325,7 @@
"type": "null"
}
],
"description": "Optional. Base image to use for the LangGraph API server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
"description": "Optional. Base image to use for the Agent Server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
},
"checkpointer": {
"anyOf": [
@@ -375,7 +375,7 @@
"type": "string"
}
],
"description": "Optional. Environment variables to set for your deployment.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
"description": "Optional. Environment variables to set for your Agent Server.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
},
"graphs": {
"type": "object",
@@ -520,7 +520,7 @@
"type": "null"
}
],
"description": "Optional. Node.js version as a major version (e.g. '20'), if your deployment needs Node.\nMust be >= 20 if provided.\n"
"description": "Optional. Node.js version as a major version (e.g. '20'), if your Agent Server needs Node.\nMust be >= 20 if provided.\n"
},
"_INTERNAL_docker_tag": {
"anyOf": [
@@ -542,7 +542,7 @@
"type": "null"
}
],
"description": "Optional. Which semantic version of the LangGraph API server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
"description": "Optional. Which semantic version of the Agent Server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
},
"auth": {
"anyOf": [
@@ -564,7 +564,7 @@
"type": "null"
}
],
"description": "Optional. Base image to use for the LangGraph API server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
"description": "Optional. Base image to use for the Agent Server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
},
"checkpointer": {
"anyOf": [
@@ -614,7 +614,7 @@
"type": "string"
}
],
"description": "Optional. Environment variables to set for your deployment.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
"description": "Optional. Environment variables to set for your Agent Server.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
},
"graphs": {
"type": "object",
@@ -765,7 +765,7 @@
},
"disable_studio_auth": {
"type": "boolean",
"description": "Optional. Whether to disable LangSmith API-key authentication for requests originating the Studio.\n\nDefaults to False, meaning that if a particular header is set, the server will verify the `x-api-key` header\nvalue is a valid API key for the deployment's workspace. If `True`, all requests will go through your custom\nauthentication logic, regardless of origin of the request.\n"
"description": "Optional. Whether to disable LangSmith API-key authentication for requests originating the Studio.\n\nDefaults to False, meaning that if a particular header is set, the server will verify the `x-api-key` header\nvalue is a valid API key for the Agent Server's workspace. If `True`, all requests will go through your custom\nauthentication logic, regardless of origin of the request.\n"
},
"openapi": {
"$ref": "#/$defs/SecurityConfig",
@@ -1018,7 +1018,7 @@
},
"HttpConfig": {
"title": "HttpConfig",
"description": "Configuration for the built-in HTTP server that powers your deployment's routes and endpoints.",
"description": "Configuration for the built-in HTTP server that powers your Agent Server's routes and endpoints.",
"type": "object",
"properties": {
"app": {
@@ -1049,7 +1049,7 @@
},
"disable_a2a": {
"type": "boolean",
"description": "Optional. If `True`, /a2a routes are removed, disabling default support to expose the deployment as an agent-to-agent (A2A) server.\n\nDefault is False.\n"
"description": "Optional. If `True`, /a2a routes are removed, disabling default support to expose the Agent Server as an agent-to-agent (A2A) server.\n\nDefault is False.\n"
},
"disable_assistants": {
"type": "boolean",
@@ -1057,7 +1057,7 @@
},
"disable_mcp": {
"type": "boolean",
"description": "Optional. If `True`, /mcp routes are removed, disabling default support to expose the deployment as an MCP server.\n\nDefault is False.\n"
"description": "Optional. If `True`, /mcp routes are removed, disabling default support to expose the Agent Server as an MCP server.\n\nDefault is False.\n"
},
"disable_meta": {
"type": "boolean",
+16 -16
View File
@@ -12,7 +12,7 @@
"properties": {
"python_version": {
"type": "string",
"description": "Optional. Python version in 'major.minor' format (e.g. '3.11').\nMust be at least 3.11 or greater for this deployment to function properly.\n",
"description": "Optional. Python version in 'major.minor' format (e.g. '3.11').\nMust be at least 3.11 or greater for this Agent Server to function properly.\n",
"enum": [
"3.11",
"3.12",
@@ -50,7 +50,7 @@
"type": "null"
}
],
"description": "Optional. Which semantic version of the LangGraph API server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
"description": "Optional. Which semantic version of the Agent Server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
},
"auth": {
"anyOf": [
@@ -72,7 +72,7 @@
"type": "null"
}
],
"description": "Optional. Base image to use for the LangGraph API server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
"description": "Optional. Base image to use for the Agent Server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
},
"checkpointer": {
"anyOf": [
@@ -122,7 +122,7 @@
"type": "string"
}
],
"description": "Optional. Environment variables to set for your deployment.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
"description": "Optional. Environment variables to set for your Agent Server.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
},
"graphs": {
"type": "object",
@@ -265,7 +265,7 @@
"properties": {
"python_version": {
"type": "string",
"description": "Optional. Python version in 'major.minor' format (e.g. '3.11').\nMust be at least 3.11 or greater for this deployment to function properly.\n",
"description": "Optional. Python version in 'major.minor' format (e.g. '3.11').\nMust be at least 3.11 or greater for this Agent Server to function properly.\n",
"enum": [
"3.11",
"3.12",
@@ -303,7 +303,7 @@
"type": "null"
}
],
"description": "Optional. Which semantic version of the LangGraph API server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
"description": "Optional. Which semantic version of the Agent Server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
},
"auth": {
"anyOf": [
@@ -325,7 +325,7 @@
"type": "null"
}
],
"description": "Optional. Base image to use for the LangGraph API server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
"description": "Optional. Base image to use for the Agent Server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
},
"checkpointer": {
"anyOf": [
@@ -375,7 +375,7 @@
"type": "string"
}
],
"description": "Optional. Environment variables to set for your deployment.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
"description": "Optional. Environment variables to set for your Agent Server.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
},
"graphs": {
"type": "object",
@@ -520,7 +520,7 @@
"type": "null"
}
],
"description": "Optional. Node.js version as a major version (e.g. '20'), if your deployment needs Node.\nMust be >= 20 if provided.\n"
"description": "Optional. Node.js version as a major version (e.g. '20'), if your Agent Server needs Node.\nMust be >= 20 if provided.\n"
},
"_INTERNAL_docker_tag": {
"anyOf": [
@@ -542,7 +542,7 @@
"type": "null"
}
],
"description": "Optional. Which semantic version of the LangGraph API server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
"description": "Optional. Which semantic version of the Agent Server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
},
"auth": {
"anyOf": [
@@ -564,7 +564,7 @@
"type": "null"
}
],
"description": "Optional. Base image to use for the LangGraph API server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
"description": "Optional. Base image to use for the Agent Server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
},
"checkpointer": {
"anyOf": [
@@ -614,7 +614,7 @@
"type": "string"
}
],
"description": "Optional. Environment variables to set for your deployment.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
"description": "Optional. Environment variables to set for your Agent Server.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
},
"graphs": {
"type": "object",
@@ -765,7 +765,7 @@
},
"disable_studio_auth": {
"type": "boolean",
"description": "Optional. Whether to disable LangSmith API-key authentication for requests originating the Studio.\n\nDefaults to False, meaning that if a particular header is set, the server will verify the `x-api-key` header\nvalue is a valid API key for the deployment's workspace. If `True`, all requests will go through your custom\nauthentication logic, regardless of origin of the request.\n"
"description": "Optional. Whether to disable LangSmith API-key authentication for requests originating the Studio.\n\nDefaults to False, meaning that if a particular header is set, the server will verify the `x-api-key` header\nvalue is a valid API key for the Agent Server's workspace. If `True`, all requests will go through your custom\nauthentication logic, regardless of origin of the request.\n"
},
"openapi": {
"$ref": "#/$defs/SecurityConfig",
@@ -1018,7 +1018,7 @@
},
"HttpConfig": {
"title": "HttpConfig",
"description": "Configuration for the built-in HTTP server that powers your deployment's routes and endpoints.",
"description": "Configuration for the built-in HTTP server that powers your Agent Server's routes and endpoints.",
"type": "object",
"properties": {
"app": {
@@ -1049,7 +1049,7 @@
},
"disable_a2a": {
"type": "boolean",
"description": "Optional. If `True`, /a2a routes are removed, disabling default support to expose the deployment as an agent-to-agent (A2A) server.\n\nDefault is False.\n"
"description": "Optional. If `True`, /a2a routes are removed, disabling default support to expose the Agent Server as an agent-to-agent (A2A) server.\n\nDefault is False.\n"
},
"disable_assistants": {
"type": "boolean",
@@ -1057,7 +1057,7 @@
},
"disable_mcp": {
"type": "boolean",
"description": "Optional. If `True`, /mcp routes are removed, disabling default support to expose the deployment as an MCP server.\n\nDefault is False.\n"
"description": "Optional. If `True`, /mcp routes are removed, disabling default support to expose the Agent Server as an MCP server.\n\nDefault is False.\n"
},
"disable_meta": {
"type": "boolean",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$id": "https://github.com/langchain-ai/langgraph/libs/cli/schemas/version.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "LangGraph Platform configuration (when building via the langgraph-cli).",
"title": "Agent Server configuration (when building via the langgraph-cli).",
"type": "object",
"oneOf": [
{
+20 -18
View File
@@ -303,7 +303,7 @@ def test_top_level_help_shows_deploy_subcommands() -> None:
assert "deploy delete" in result.output
assert "deploy revisions" in result.output
assert "deploy revisions list" in result.output
assert "[Beta] List LangSmith Deployments." in result.output
assert "[Beta] List Agent Servers." in result.output
def test_top_level_help_truncates_command_descriptions_to_single_line() -> None:
@@ -320,7 +320,7 @@ def test_top_level_help_truncates_command_descriptions_to_single_line() -> None:
assert not lines[lines.index(deploy_line) + 1].startswith(" ")
assert "..." in deploy_line
assert "[Beta] List LangSmith Deployments." in deploy_list_line
assert "[Beta] List Agent Servers." in deploy_list_line
def test_deploy_missing_config_shows_actionable_error(tmp_path, monkeypatch) -> None:
@@ -447,9 +447,9 @@ def test_deploy_list_command(monkeypatch) -> None:
"tenant_id": "",
"name_contains": "alp",
}
assert "Deployment ID" in result.output
assert "Deployment Name" in result.output
assert "Deployment URL" in result.output
assert "Agent Server ID" in result.output
assert "Agent Server Name" in result.output
assert "Agent Server URL" in result.output
assert "dep-123" in result.output
assert "https://beta.example.com" in result.output
@@ -479,7 +479,7 @@ def test_deploy_list_command_no_results(monkeypatch) -> None:
)
assert result.exit_code == 0, result.output
assert result.output.strip() == "No deployments found."
assert result.output.strip() == "No Agent Servers found."
def test_deploy_list_prints_an_aligned_table(monkeypatch) -> None:
@@ -513,10 +513,10 @@ def test_deploy_list_prints_an_aligned_table(monkeypatch) -> None:
assert result.exit_code == 0, result.output
assert result.output.splitlines() == [
"Deployment ID Deployment Name Deployment URL ",
"------------- --------------- -------------------------",
"dep-123 alpha https://alpha.example.com",
"dep-4567 beta-agent - ",
"Agent Server ID Agent Server Name Agent Server URL ",
"--------------- ----------------- -------------------------",
"dep-123 alpha https://alpha.example.com",
"dep-4567 beta-agent - ",
]
@@ -600,7 +600,7 @@ def test_deploy_list_rejects_a_deployment_without_an_id(monkeypatch) -> None:
result = _deploy_list_json(monkeypatch, [{"name": "alpha"}])
assert result.exit_code != 0
assert "deployment without an id" in result.output
assert "Agent Server without an id" in result.output
def test_deploy_list_json_keeps_the_agent_note_off_stdout(monkeypatch) -> None:
@@ -1012,7 +1012,7 @@ def test_deploy_revisions_list_command_no_results(monkeypatch) -> None:
)
assert result.exit_code == 0, result.output
assert result.output.strip() == "No revisions found for deployment dep-123."
assert result.output.strip() == "No revisions found for Agent Server dep-123."
def test_deploy_revisions_list_marks_older_deployed_revisions_as_replaced(
@@ -1096,7 +1096,7 @@ def test_deploy_revisions_list_command_with_explicit_limit(monkeypatch) -> None:
assert result.exit_code == 0, result.output
assert captured == {"deployment_id": "dep-123", "limit": "25"}
assert result.output.strip() == "No revisions found for deployment dep-123."
assert result.output.strip() == "No revisions found for Agent Server dep-123."
def test_deploy_revisions_list_missing_deployment_id_shows_usage() -> None:
@@ -1105,7 +1105,7 @@ def test_deploy_revisions_list_missing_deployment_id_shows_usage() -> None:
result = runner.invoke(cli, ["deploy", "revisions", "list"])
assert result.exit_code == 2, result.output
assert "Missing argument 'DEPLOYMENT_ID'" in result.output
assert "Missing argument 'AGENT_SERVER_ID'" in result.output
def test_deploy_delete_command(monkeypatch) -> None:
@@ -1146,10 +1146,10 @@ def test_deploy_delete_command(monkeypatch) -> None:
"deployment_id": "dep-123",
}
assert (
"Are you sure you want to delete deployment ID dep-123? (Y/n):"
"Are you sure you want to delete Agent Server ID dep-123? (Y/n):"
in click.unstyle(result.output)
)
assert result.output.strip().endswith("Deleted deployment dep-123.")
assert result.output.strip().endswith("Deleted Agent Server dep-123.")
def test_deploy_delete_command_cancelled(monkeypatch) -> None:
@@ -1217,14 +1217,16 @@ def test_deploy_delete_command_force(monkeypatch) -> None:
)
assert result.exit_code == 0, result.output
assert "Are you sure you want to delete deployment ID dep-123?" not in result.output
assert (
"Are you sure you want to delete Agent Server ID dep-123?" not in result.output
)
assert captured == {
"host_url": "https://api.example.com",
"api_key": "test-key",
"tenant_id": "",
"deployment_id": "dep-123",
}
assert result.output.strip() == "Deleted deployment dep-123."
assert result.output.strip() == "Deleted Agent Server dep-123."
def test_dockerfile_command_basic() -> None:
@@ -359,6 +359,68 @@ def deploy_project(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> DeployPro
return DeployProject(control_plane, docker, timeline, uploads)
@pytest.mark.parametrize(
("command", "expected"),
[
([], ["--agent-server-id", "--agent-server-type"]),
(["logs"], ["--agent-server-id"]),
(["delete"], ["AGENT_SERVER_ID"]),
(["revisions", "list"], ["AGENT_SERVER_ID"]),
(["metrics"], ["AGENT_SERVER_ID"]),
],
)
def test_deploy_help_uses_agent_server_names(command: list[str], expected: list[str]):
result = CliRunner().invoke(cli, ["deploy", *command, "--help"])
assert result.exit_code == 0, result.output
assert all(name in result.output for name in expected)
assert "Agent Server" in result.output
assert "--deployment-" not in result.output
assert "DEPLOYMENT_ID" not in result.output
@pytest.mark.parametrize("command", [[], ["logs"]])
@pytest.mark.parametrize("option", ["--agent-server-id", "--deployment-id"])
def test_missing_agent_server_id_uses_public_option_name(
command: list[str], option: str
):
result = CliRunner().invoke(cli, ["deploy", *command, option])
assert result.exit_code == 2, result.output
assert "Option '--agent-server-id' requires an argument" in result.output
assert "--deployment-id" not in result.output
@pytest.mark.parametrize("option", ["--agent-server-type", "--deployment-type"])
@pytest.mark.parametrize("values", [[], ["invalid"]])
def test_invalid_agent_server_type_uses_public_option_name(
option: str, values: list[str]
):
result = CliRunner().invoke(cli, ["deploy", option, *values])
assert result.exit_code == 2, result.output
assert "'--agent-server-type'" in result.output
assert "--deployment-type" not in result.output
@pytest.mark.parametrize("option", ["--agent-server-id", "--deployment-id"])
def test_deploy_logs_accepts_agent_server_id_aliases(
deploy_project, mocker, option: str
):
fetch_logs = mocker.patch.object(
HostBackendClient, "get_deploy_logs", return_value={"logs": []}
)
result = CliRunner().invoke(
cli, ["deploy", "logs", "--api-key", "test-key", option, "dep-existing"]
)
assert result.exit_code == 0, result.output
fetch_logs.assert_called_once_with(
"dep-existing", {"limit": 100, "order": "desc"}, None
)
@pytest.mark.parametrize("build_mode", ["--no-remote", "--remote"])
@pytest.mark.parametrize(
("detail", "expects_dev_hint"),
@@ -376,7 +438,7 @@ def test_create_failure_preserves_backend_error_and_only_hints_for_aws_rejection
assert result.exit_code == 1, result.output
assert detail in result.output
assert ("Use --deployment-type dev instead." in result.output) is expects_dev_hint
assert ("Use --agent-server-type dev instead." in result.output) is expects_dev_hint
assert deploy_project.control_plane.bodies[CREATE_DEPLOYMENT]["source_config"] == {
"deployment_type": "dev_zero"
}
@@ -415,7 +477,7 @@ def test_first_local_deploy_creates_then_builds_pushes_and_updates_in_order(
"docker inspect-digest",
_patch(CREATED_ID),
]
assert "Deployment updated" in result.output
assert "Agent Server updated" in result.output
@pytest.mark.parametrize(
@@ -428,10 +490,15 @@ def test_first_local_deploy_creates_then_builds_pushes_and_updates_in_order(
],
)
@pytest.mark.parametrize("build_mode", ["--no-remote", "--remote"])
@pytest.mark.parametrize("option", ["--agent-server-type", "--deployment-type"])
def test_deployment_type_aliases(
deploy_project: DeployProject, deployment_type: str, wire_type: str, build_mode: str
deploy_project: DeployProject,
deployment_type: str,
wire_type: str,
build_mode: str,
option: str,
) -> None:
result = deploy_project.run(build_mode, "--deployment-type", deployment_type)
result = deploy_project.run(build_mode, option, deployment_type)
assert result.exit_code == 0, result.output
assert deploy_project.control_plane.bodies[CREATE_DEPLOYMENT]["source_config"] == {
@@ -770,7 +837,7 @@ def test_push_to_builds_pushes_then_creates_an_external_deployment(
"source_revision_config": {"image_uri": EXTERNAL_DIGEST},
"secrets": [],
}
assert "Deployment created" in result.output
assert "Agent Server created" in result.output
def test_push_to_builds_directly_with_the_push_reference(
@@ -873,16 +940,17 @@ def test_push_to_rejects_a_non_external_deployment_before_any_docker_work(
assert deploy_project.docker.verbs() == []
@pytest.mark.parametrize("option", ["--agent-server-id", "--deployment-id"])
@pytest.mark.parametrize("equals", [False, True])
def test_push_to_with_deployment_id_fetches_the_deployment_once(
deploy_project: DeployProject,
deploy_project: DeployProject, option: str, equals: bool
) -> None:
deploy_project.control_plane.existing_deployments = [
{"id": "dep-ext", "name": "another-name", "source": "external_docker"}
]
result = deploy_project.run(
"--deployment-id", "dep-ext", "--push-to", PUSH_REPOSITORY
)
id_args = [f"{option}=dep-ext"] if equals else [option, "dep-ext"]
result = deploy_project.run(*id_args, "--push-to", PUSH_REPOSITORY)
assert result.exit_code == 0, result.output
assert deploy_project.timeline == [
@@ -1159,7 +1227,7 @@ def test_image_pull_secret_without_push_to_or_image_uri_is_refused(
result = deploy_project.run("--image-pull-secret", "regcred")
assert result.exit_code != 0
assert "only apply when creating a deployment with --push-to" in result.output
assert "only apply when creating an Agent Server with --push-to" in result.output
assert deploy_project.timeline == []
@@ -1241,7 +1309,7 @@ def test_listener_flags_are_refused_for_a_deployment_id_without_any_call(
)
assert result.exit_code != 0
assert "fixed when a deployment is created" in result.output
assert "fixed when an Agent Server is created" in result.output
assert deploy_project.timeline == []
@@ -1262,7 +1330,7 @@ def test_listener_flags_are_refused_on_an_existing_deployment(
)
assert result.exit_code != 0
assert "fixed when a deployment is created" in result.output
assert "fixed when an Agent Server is created" in result.output
assert deploy_project.docker.verbs() == []
@@ -0,0 +1,166 @@
import json
from datetime import datetime, timezone
import httpx
import pytest
from click.testing import CliRunner
import langgraph_cli.deploy as deploy_module
from langgraph_cli.cli import cli
from langgraph_cli.deployment_metrics import compact_deployment_metrics
from langgraph_cli.host_backend import HostBackendClient
DEPLOYMENT_ID = "019a0000-0000-7000-8000-000000000001"
def invoke(monkeypatch, args, handler, input=None):
client = HostBackendClient(
"https://api.example.com",
"key",
tenant_id="workspace-id",
transport=httpx.MockTransport(handler),
)
monkeypatch.setattr(deploy_module, "HostBackendClient", lambda *a, **kw: client)
return CliRunner().invoke(
cli,
["deploy", *args, "--api-key", "key"],
input=input,
)
@pytest.mark.parametrize(
"bounds", [[], ["start_time"], ["end_time"], ["start_time", "end_time"]]
)
@pytest.mark.parametrize("mode", [None, "--summary", "--raw"])
def test_metrics(monkeypatch, bounds, mode, deployment_metrics_response):
charts = deployment_metrics_response
class FrozenDatetime(datetime):
@classmethod
def now(cls, tz=None):
return cls(2026, 10, 28, 23, 2, 33, tzinfo=timezone.utc)
monkeypatch.setattr(deploy_module, "datetime", FrozenDatetime)
explicit = {
"start_time": "2026-10-27T22:32:33Z",
"end_time": "2026-10-28T21:32:33Z",
}
options = {key: explicit[key] for key in bounds}
if bounds:
options["metrics"] = "cpu_usage,memory_usage"
params = {
"start_time": (
"2026-10-28T21:17:33+00:00"
if "end_time" in bounds
else "2026-10-28T22:47:33+00:00"
),
"end_time": "2026-10-28T23:02:33+00:00",
**options,
}
def handler(request):
assert request.method == "GET"
assert request.url.path == f"/v2/deployments/{DEPLOYMENT_ID}/metrics"
assert dict(request.url.params) == params
assert request.headers["x-tenant-id"] == "workspace-id"
assert request.headers["x-api-key"] == "key"
return httpx.Response(200, json=charts)
args = ["metrics", DEPLOYMENT_ID]
for key, value in options.items():
args.extend([f"--{key.replace('_', '-')}", value])
if mode:
args.append(mode)
result = invoke(monkeypatch, args, handler)
assert result.exit_code == 0, result.output
expected = (
charts
if mode == "--raw"
else compact_deployment_metrics(
charts,
DEPLOYMENT_ID,
start_time=params.get("start_time"),
end_time=params.get("end_time"),
summary=mode == "--summary",
)
)
assert json.loads(result.output) == expected
if mode != "--raw":
assert expected["start_time"] == params["start_time"]
assert expected["end_time"] == params["end_time"]
assert result.output.strip() == json.dumps(expected, separators=(",", ":"))
@pytest.mark.parametrize(
"args",
[
["metrics", DEPLOYMENT_ID, "--step-seconds", "60"],
["metrics", "invalid-id"],
["metrics", DEPLOYMENT_ID, "--summary", "--raw"],
],
)
def test_invalid_options_do_not_send_requests(monkeypatch, args):
def handler(request):
pytest.fail("Invalid options must not send a request")
result = invoke(monkeypatch, args, handler)
assert result.exit_code == 2, result.output
def test_end_only_with_timezone_offset(monkeypatch):
def handler(request):
assert request.url.params["end_time"] == "2026-10-28T01:05:00+02:00"
assert request.url.params["start_time"] == "2026-10-27T22:50:00+00:00"
return httpx.Response(200, json={"charts": [], "sub_sections": []})
result = invoke(
monkeypatch,
["metrics", DEPLOYMENT_ID, "--end-time", "2026-10-28T01:05:00+02:00"],
handler,
)
assert result.exit_code == 0, result.output
output = json.loads(result.output)
assert output["start_time"] == "2026-10-27T22:50:00+00:00"
assert output["end_time"] == "2026-10-28T01:05:00+02:00"
def test_invalid_end_only_does_not_send_request(monkeypatch):
def handler(request):
pytest.fail("Invalid end time must not send a request")
result = invoke(
monkeypatch, ["metrics", DEPLOYMENT_ID, "--end-time", "not-a-date"], handler
)
assert result.exit_code == 2, result.output
assert "--end-time" in result.output
assert "ISO8601" in result.output
def test_metrics_help_lists_supported_filters():
result = CliRunner().invoke(cli, ["deploy", "metrics", "--help"])
assert result.exit_code == 0, result.output
for metric in (
"memory_usage",
"cpu_usage",
"disk_usage",
"restart_count",
"replica_count",
"worker_count",
"lg_run_count",
"responses_per_second",
"error_responses_per_second",
"p95_latency",
"run_queue_wait_time",
):
assert metric in result.output
@pytest.mark.parametrize("status", [403, 422, 429])
def test_backend_errors(monkeypatch, status):
def handler(request):
return httpx.Response(status, json={"detail": "Operation rejected"})
args = ["metrics", DEPLOYMENT_ID]
result = invoke(monkeypatch, args, handler)
assert result.exit_code == 1
assert "Operation rejected" in result.output
+7
View File
@@ -1,9 +1,16 @@
import json
import os
from pathlib import Path
from unittest.mock import patch
import pytest
@pytest.fixture
def deployment_metrics_response():
return json.loads(Path(__file__).with_name("deployment_metrics.json").read_text())
@pytest.fixture(autouse=True)
def disable_analytics_env() -> None:
"""Disable analytics for unit tests LANGGRAPH_CLI_NO_ANALYTICS."""
@@ -0,0 +1,215 @@
{
"id": "019a0000-0000-7000-8000-000000000001-metrics",
"title": "Metrics for deployment",
"charts": [],
"sub_sections": [
{
"id": "019a0000-0000-7000-8000-000000000001-queue",
"title": "Queue",
"description": "Queue metrics",
"index": 0,
"charts": [
{
"id": "019a0000-0000-7000-8000-000000000001-Memory Usage %",
"title": "Memory Usage %",
"description": "Resource usage over time",
"index": 0,
"chart_type": "line",
"series": [
{
"id": "019a0000-0000-7000-8000-000000000001-Avg Memory Usage %",
"name": "Avg Memory Usage %",
"metric": "run_count",
"project_metric": "memory_usage",
"metadata": null,
"filters": null,
"feedback_key": null,
"workspace_id": null,
"metric_definition": null,
"group_by_definitions": null,
"filter_definition": null,
"group_by": null
},
{
"id": "019a0000-0000-7000-8000-000000000001-Max Memory Usage %",
"name": "Max Memory Usage %",
"metric": "run_count",
"project_metric": "memory_usage",
"metadata": null,
"filters": null,
"feedback_key": null,
"workspace_id": null,
"metric_definition": null,
"group_by_definitions": null,
"filter_definition": null,
"group_by": null
}
],
"data": [
{
"series_id": "019a0000-0000-7000-8000-000000000001-Max Memory Usage %",
"timestamp": "2026-10-28T00:02:00+00:00",
"value": 50.33333,
"group": null
},
{
"series_id": "019a0000-0000-7000-8000-000000000001-Max Memory Usage %",
"timestamp": "2026-10-28T00:01:00+00:00",
"value": 40.22222,
"group": null
},
{
"series_id": "019a0000-0000-7000-8000-000000000001-Max Memory Usage %",
"timestamp": "2026-10-28T00:00:00+00:00",
"value": 30.11111,
"group": null
},
{
"series_id": "019a0000-0000-7000-8000-000000000001-Avg Memory Usage %",
"timestamp": "2026-10-28T00:02:00+00:00",
"value": null,
"group": null
},
{
"series_id": "019a0000-0000-7000-8000-000000000001-Avg Memory Usage %",
"timestamp": "2026-10-28T00:01:00+00:00",
"value": 20.23456,
"group": null
},
{
"series_id": "019a0000-0000-7000-8000-000000000001-Avg Memory Usage %",
"timestamp": "2026-10-28T00:00:00+00:00",
"value": 10.12345,
"group": null
}
]
},
{
"id": "019a0000-0000-7000-8000-000000000001-Restarts",
"title": "Restarts",
"description": "Resource usage over time",
"index": 0,
"chart_type": "line",
"series": [
{
"id": "019a0000-0000-7000-8000-000000000001-Restarts",
"name": "Restarts",
"metric": "run_count",
"project_metric": "restart_count",
"metadata": null,
"filters": null,
"feedback_key": null,
"workspace_id": null,
"metric_definition": null,
"group_by_definitions": null,
"filter_definition": null,
"group_by": null
}
],
"data": [
{
"series_id": "019a0000-0000-7000-8000-000000000001-Restarts",
"timestamp": "2026-10-28T00:02:00+00:00",
"value": 2,
"group": null
},
{
"series_id": "019a0000-0000-7000-8000-000000000001-Restarts",
"timestamp": "2026-10-28T00:01:00+00:00",
"value": 1,
"group": null
},
{
"series_id": "019a0000-0000-7000-8000-000000000001-Restarts",
"timestamp": "2026-10-28T00:00:00+00:00",
"value": 0,
"group": null
}
]
},
{
"id": "019a0000-0000-7000-8000-000000000001-Queue wait time",
"title": "Queue wait time",
"description": "Resource usage over time",
"index": 0,
"chart_type": "line",
"series": [
{
"id": "019a0000-0000-7000-8000-000000000001-Queue wait time",
"name": "Queue wait time",
"metric": "run_count",
"project_metric": "run_queue_wait_time",
"metadata": null,
"filters": null,
"feedback_key": null,
"workspace_id": null,
"metric_definition": null,
"group_by_definitions": null,
"filter_definition": null,
"group_by": null
}
],
"data": []
}
]
},
{
"id": "019a0000-0000-7000-8000-000000000001-api",
"title": "API",
"description": "API metrics",
"index": 1,
"charts": [
{
"id": "019a0000-0000-7000-8000-000000000001-Latency",
"title": "Latency",
"description": "Resource usage over time",
"index": 0,
"chart_type": "line",
"series": [
{
"id": "019a0000-0000-7000-8000-000000000001-P95 Latency",
"name": "P95 Latency",
"metric": "run_count",
"project_metric": "p95_latency",
"metadata": null,
"filters": null,
"feedback_key": null,
"workspace_id": null,
"metric_definition": null,
"group_by_definitions": null,
"filter_definition": null,
"group_by": null
}
],
"data": [
{
"series_id": "019a0000-0000-7000-8000-000000000001-P95 Latency",
"timestamp": "2026-10-28T00:02:00+00:00",
"value": 0.34567,
"group": null
},
{
"series_id": "019a0000-0000-7000-8000-000000000001-P95 Latency",
"timestamp": "2026-10-28T00:01:00+00:00",
"value": 0.23456,
"group": null
},
{
"series_id": "019a0000-0000-7000-8000-000000000001-P95 Latency",
"timestamp": "2026-10-28T00:00:00+00:00",
"value": 0.12345,
"group": null
}
]
}
]
},
{
"id": "019a0000-0000-7000-8000-000000000001-pg",
"title": "Postgres",
"description": "Postgres metrics",
"index": 2,
"charts": []
}
]
}
+1 -1
View File
@@ -1204,7 +1204,7 @@ ENV LANGSERVE_GRAPHS='{{"agent": "/deps/outer-graphs/src/agent.py:graph"}}'
assert additional_contexts == {}
# node.js build used for LangSmith Deployment
# node.js build used for LangSmith Agent Servers
def test_config_to_docker_nodejs():
graphs = {"agent": "./graphs/agent.js:graph"}
actual_docker_stdin, additional_contexts = config_to_docker(
@@ -118,5 +118,5 @@ def test_agent_lookup_refuses_a_control_plane_that_ignores_the_filter(deployment
result = CliRunner().invoke(cli, AGENT_ARGS)
assert result.exit_code != 0
assert "does not filter deployments by agent" in result.output
assert "does not filter Agent Servers by agent" in result.output
assert len(requests) == 1
@@ -187,7 +187,7 @@ class TestValidatePrebuiltImage:
monkeypatch.setattr(deploy_mod, "subp_exec", fake_subp_exec)
with pytest.raises(click.ClickException, match="requires linux/amd64"):
with pytest.raises(click.ClickException, match="require linux/amd64"):
_validate_prebuilt_image(_FakeRunner(), "repo/app:arm", verbose=False)
@@ -327,6 +327,9 @@ class TestCallHostBackendWithOptionalTenant:
)
assert exc_info.value.status_code == 403
assert "smith.langchain.com" in exc_info.value.message
assert exc_info.value.message.startswith(
"LangSmith Agent Servers are not enabled for this organization."
)
def test_403_not_enabled_eu_url(self):
detail = (
@@ -871,17 +874,17 @@ class TestSelectSource:
),
pytest.param(
{"placement": RequestedPlacement(listener_id="listener-1")},
"only apply when creating a deployment with --push-to",
"only apply when creating an Agent Server with --push-to",
id="listener_without_push_to",
),
pytest.param(
{"placement": RequestedPlacement(k8s_namespace="agents")},
"only apply when creating a deployment with --push-to",
"only apply when creating an Agent Server with --push-to",
id="namespace_without_push_to",
),
pytest.param(
{"image_pull_secrets": ("regcred",)},
"only apply when creating a deployment with --push-to",
"only apply when creating an Agent Server with --push-to",
id="image_pull_secret_without_push_to",
),
pytest.param(
@@ -1406,7 +1409,7 @@ class TestCreateOnlySettings:
settings = CreateOnlySettings(RequestedPlacement(listener_id="listener-1"))
with pytest.raises(
click.UsageError,
match="Listener and namespace are fixed when a deployment is created",
match="Listener and namespace are fixed when an Agent Server is created",
):
settings.ensure_not_existing("dep-1")
@@ -0,0 +1,139 @@
from copy import deepcopy
import pytest
from langgraph_cli.deployment_metrics import compact_deployment_metrics
def reshape(response, deployment_id, **kwargs):
return compact_deployment_metrics(
response,
deployment_id,
start_time=kwargs.pop("start_time", "2026-10-28T00:00:00Z"),
end_time=kwargs.pop("end_time", "2026-10-28T01:00:00Z"),
**kwargs,
)
def test_compact_metrics(deployment_metrics_response):
original = deepcopy(deployment_metrics_response)
result = reshape(deployment_metrics_response, "deployment-id")
assert deployment_metrics_response == original
assert result["deployment_id"] == "deployment-id"
assert result["start_time"] == "2026-10-28T00:00:00Z"
assert result["end_time"] == "2026-10-28T01:00:00Z"
assert result["step_seconds"] == 60
assert result["sections"]["Postgres"] == {"_empty": True}
queue = result["sections"]["Queue"]
memory = queue["Memory Usage %"]
assert memory["Avg Memory Usage %"] == {
"metric": "memory_usage",
"unit": "percent",
"latest": 20.235,
"min": 10.123,
"max": 20.235,
"avg": 15.179,
"points": [
["2026-10-28T00:00:00Z", 10.123],
["2026-10-28T00:01:00Z", 20.235],
["2026-10-28T00:02:00Z", None],
],
}
assert memory["Max Memory Usage %"]["max"] == 50.333
assert queue["Restarts"]["Restarts"]["unit"] == "count"
assert queue["Queue wait time"]["Queue wait time"] == {
"metric": "run_queue_wait_time",
"unit": None,
"latest": None,
"min": None,
"max": None,
"avg": None,
"points": [],
}
assert result["sections"]["API"]["Latency"]["P95 Latency"]["unit"] is None
def test_summary_and_requested_bounds(deployment_metrics_response):
result = reshape(
deployment_metrics_response,
"id",
start_time="2026-10-27T23:00:00Z",
end_time="2026-10-28T01:00:00Z",
summary=True,
)
assert result["start_time"] == "2026-10-27T23:00:00Z"
assert result["end_time"] == "2026-10-28T01:00:00Z"
for section in result["sections"].values():
if section == {"_empty": True}:
continue
for chart in section.values():
for series in chart.values():
assert set(series) == {"metric", "unit", "latest", "min", "max", "avg"}
@pytest.mark.parametrize("values", [[], [None], [float("nan"), float("inf")]])
def test_empty_and_nonfinite_values(deployment_metrics_response, values):
chart = deployment_metrics_response["sub_sections"][0]["charts"][0]
chart["series"] = chart["series"][:1]
chart["data"] = [
{
"series_id": chart["series"][0]["id"],
"timestamp": f"2026-10-28T00:0{i}:00Z",
"value": value,
}
for i, value in enumerate(values)
]
result = reshape(deployment_metrics_response, "id")
series = result["sections"]["Queue"]["Memory Usage %"]["Avg Memory Usage %"]
assert all(series[key] is None for key in ["latest", "min", "max", "avg"])
assert all(point[1] is None for point in series["points"])
def test_tied_spacing_and_top_level_charts(deployment_metrics_response):
response = deployment_metrics_response
chart = response["sub_sections"][1]["charts"][0]
chart["data"][0]["timestamp"] = "2026-10-28T00:02:30Z"
response["charts"] = [chart]
response["sub_sections"] = []
result = reshape(response, "id")
assert result["step_seconds"] == 60
assert "Latency" in result["sections"][response["title"]]
def test_missing_point_preserves_common_spacing(deployment_metrics_response):
chart = deployment_metrics_response["sub_sections"][0]["charts"][0]
series_id = chart["series"][0]["id"]
chart["data"] = [
point
for point in chart["data"]
if not (point["series_id"] == series_id and "00:01:00" in point["timestamp"])
]
result = reshape(deployment_metrics_response, "id")
assert result["step_seconds"] == 60
assert type(result["step_seconds"]) is int
def test_only_single_point_and_empty_series(deployment_metrics_response):
for section in deployment_metrics_response["sub_sections"]:
for chart in section["charts"]:
chart["data"] = chart["data"][:1]
assert reshape(deployment_metrics_response, "id")["step_seconds"] is None
def test_fractional_spacing(deployment_metrics_response):
chart = deployment_metrics_response["sub_sections"][1]["charts"][0]
chart["data"][0]["timestamp"] = "2026-10-28T00:00:01Z"
chart["data"][1]["timestamp"] = "2026-10-28T00:00:00.500Z"
deployment_metrics_response["sub_sections"] = []
deployment_metrics_response["charts"] = [chart]
assert reshape(deployment_metrics_response, "id")["step_seconds"] == 0.5
def test_no_data():
assert reshape({"charts": [], "sub_sections": []}, "id") == {
"deployment_id": "id",
"start_time": "2026-10-28T00:00:00Z",
"end_time": "2026-10-28T01:00:00Z",
"step_seconds": None,
"sections": {"_empty": True},
}
+217 -141
View File
@@ -506,11 +506,28 @@ wheels = [
[[package]]
name = "filelock"
version = "3.28.0"
version = "4.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d6/17/6e8890271880903e3538660a21d63a6c1fea969ac71d0d6b608b78727fa9/filelock-3.28.0.tar.gz", hash = "sha256:4ed1010aae813c4ee8d9c660e4792475ee60c4a0ba76073ceaf862bd317e3ca6", size = 56474, upload-time = "2026-04-14T22:54:33.625Z" }
resolution-markers = [
"python_full_version < '3.11'",
]
sdist = { url = "https://files.pythonhosted.org/packages/4c/58/6fd434bec86eff7c38a3168454cb132b762b2bea9b3ac094101a2f7bc32a/filelock-4.1.0.tar.gz", hash = "sha256:ad7f724afef953e731b1cc39bcd3a09166d72ed7fcdf29e6e88b1c3235c6715d", size = 561277, upload-time = "2026-10-09T19:57:20.34Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/21/2f728888c45033d34a417bfcd248ea2564c9e08ab1bfd301377cf05d5586/filelock-3.28.0-py3-none-any.whl", hash = "sha256:de9af6712788e7171df1b28b15eba2446c69721433fa427a9bee07b17820a9db", size = 39189, upload-time = "2026-04-14T22:54:32.037Z" },
{ url = "https://files.pythonhosted.org/packages/ee/86/032133892a5de43b5a98200b01aadcad68cc255e274a762f08b8a76d2912/filelock-4.1.0-py3-none-any.whl", hash = "sha256:2ce9818e3e2d8f284c1a964414447ef148d42a5fd5e2a477a7118e574b293ec1", size = 133003, upload-time = "2026-10-09T19:57:18.716Z" },
]
[[package]]
name = "filelock"
version = "4.2.9"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.15'",
"python_full_version >= '3.12' and python_full_version < '3.15' and sys_platform == 'emscripten'",
"python_full_version == '3.11.*' or (python_full_version >= '3.12' and python_full_version < '3.15' and sys_platform != 'emscripten')",
]
sdist = { url = "https://files.pythonhosted.org/packages/a3/1d/d730c664fcb4f1d264a722722a3134cf0881854f73c1fd58abf87572f232/filelock-4.2.9.tar.gz", hash = "sha256:96530b7c538d4ea08493ab067247aab7948c54d8cd622cce711c97cccc5d9896", size = 663116, upload-time = "2026-11-04T18:37:10.014Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/00/05/76184fe08339723d643ab47887a8e4de8f2921ca4e008617ab7490af7857/filelock-4.2.9-py3-none-any.whl", hash = "sha256:a379c50a729e5b68c8519a4d26489f755d9769960a236ecd838190e2a78db2e9", size = 164734, upload-time = "2026-11-04T18:37:08.273Z" },
]
[[package]]
@@ -533,139 +550,139 @@ wheels = [
[[package]]
name = "grpcio"
version = "1.80.0"
version = "1.81.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" } },
]
sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/b5/1ff353970a87eda4c98251e34d2dfd214abd4982dc89119c9252a2a482d2/grpcio-1.81.1.tar.gz", hash = "sha256:6fa10a767143a5e82e8eaab53918af0cd8909a57a27f8cb2288b80a613ac671b", size = 13026582, upload-time = "2026-06-11T12:46:51.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9d/cd/bb7b7e54084a344c03d68144450da7ddd5564e51a298ae1662de65f48e2d/grpcio-1.80.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:886457a7768e408cdce226ad1ca67d2958917d306523a0e21e1a2fdaa75c9c9c", size = 6050363, upload-time = "2026-03-30T08:46:20.894Z" },
{ url = "https://files.pythonhosted.org/packages/16/02/1417f5c3460dea65f7a2e3c14e8b31e77f7ffb730e9bfadd89eda7a9f477/grpcio-1.80.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:7b641fc3f1dc647bfd80bd713addc68f6d145956f64677e56d9ebafc0bd72388", size = 12026037, upload-time = "2026-03-30T08:46:25.144Z" },
{ url = "https://files.pythonhosted.org/packages/43/98/c910254eedf2cae368d78336a2de0678e66a7317d27c02522392f949b5c6/grpcio-1.80.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:33eb763f18f006dc7fee1e69831d38d23f5eccd15b2e0f92a13ee1d9242e5e02", size = 6602306, upload-time = "2026-03-30T08:46:27.593Z" },
{ url = "https://files.pythonhosted.org/packages/7c/f8/88ca4e78c077b2b2113d95da1e1ab43efd43d723c9a0397d26529c2c1a56/grpcio-1.80.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:52d143637e3872633fc7dd7c3c6a1c84e396b359f3a72e215f8bf69fd82084fc", size = 7301535, upload-time = "2026-03-30T08:46:29.556Z" },
{ url = "https://files.pythonhosted.org/packages/f9/96/f28660fe2fe0f153288bf4a04e4910b7309d442395135c88ed4f5b3b8b40/grpcio-1.80.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c51bf8ac4575af2e0678bccfb07e47321fc7acb5049b4482832c5c195e04e13a", size = 6808669, upload-time = "2026-03-30T08:46:31.984Z" },
{ url = "https://files.pythonhosted.org/packages/47/eb/3f68a5e955779c00aeef23850e019c1c1d0e032d90633ba49c01ad5a96e0/grpcio-1.80.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:50a9871536d71c4fba24ee856abc03a87764570f0c457dd8db0b4018f379fed9", size = 7409489, upload-time = "2026-03-30T08:46:34.684Z" },
{ url = "https://files.pythonhosted.org/packages/5b/a7/d2f681a4bfb881be40659a309771f3bdfbfdb1190619442816c3f0ffc079/grpcio-1.80.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a72d84ad0514db063e21887fbacd1fd7acb4d494a564cae22227cd45c7fbf199", size = 8423167, upload-time = "2026-03-30T08:46:36.833Z" },
{ url = "https://files.pythonhosted.org/packages/97/8a/29b4589c204959aa35ce5708400a05bba72181807c45c47b3ec000c39333/grpcio-1.80.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f7691a6788ad9196872f95716df5bc643ebba13c97140b7a5ee5c8e75d1dea81", size = 7846761, upload-time = "2026-03-30T08:46:40.091Z" },
{ url = "https://files.pythonhosted.org/packages/6b/d2/ed143e097230ee121ac5848f6ff14372dba91289b10b536d54fb1b7cbae7/grpcio-1.80.0-cp310-cp310-win32.whl", hash = "sha256:46c2390b59d67f84e882694d489f5b45707c657832d7934859ceb8c33f467069", size = 4156534, upload-time = "2026-03-30T08:46:42.026Z" },
{ url = "https://files.pythonhosted.org/packages/d5/c9/df8279bb49b29409995e95efa85b72973d62f8aeff89abee58c91f393710/grpcio-1.80.0-cp310-cp310-win_amd64.whl", hash = "sha256:dc053420fc75749c961e2a4c906398d7c15725d36ccc04ae6d16093167223b58", size = 4889869, upload-time = "2026-03-30T08:46:44.219Z" },
{ url = "https://files.pythonhosted.org/packages/5d/db/1d56e5f5823257b291962d6c0ce106146c6447f405b60b234c4f222a7cde/grpcio-1.80.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:dfab85db094068ff42e2a3563f60ab3dddcc9d6488a35abf0132daec13209c8a", size = 6055009, upload-time = "2026-03-30T08:46:46.265Z" },
{ url = "https://files.pythonhosted.org/packages/6e/18/c83f3cad64c5ca63bca7e91e5e46b0d026afc5af9d0a9972472ceba294b3/grpcio-1.80.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5c07e82e822e1161354e32da2662f741a4944ea955f9f580ec8fb409dd6f6060", size = 12035295, upload-time = "2026-03-30T08:46:49.099Z" },
{ url = "https://files.pythonhosted.org/packages/0f/8e/e14966b435be2dda99fbe89db9525ea436edc79780431a1c2875a3582644/grpcio-1.80.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba0915d51fd4ced2db5ff719f84e270afe0e2d4c45a7bdb1e8d036e4502928c2", size = 6610297, upload-time = "2026-03-30T08:46:52.123Z" },
{ url = "https://files.pythonhosted.org/packages/cc/26/d5eb38f42ce0e3fdc8174ea4d52036ef8d58cc4426cb800f2610f625dd75/grpcio-1.80.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3cb8130ba457d2aa09fa6b7c3ed6b6e4e6a2685fce63cb803d479576c4d80e21", size = 7300208, upload-time = "2026-03-30T08:46:54.859Z" },
{ url = "https://files.pythonhosted.org/packages/25/51/bd267c989f85a17a5b3eea65a6feb4ff672af41ca614e5a0279cc0ea381c/grpcio-1.80.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09e5e478b3d14afd23f12e49e8b44c8684ac3c5f08561c43a5b9691c54d136ab", size = 6813442, upload-time = "2026-03-30T08:46:57.056Z" },
{ url = "https://files.pythonhosted.org/packages/9e/d9/d80eef735b19e9169e30164bbf889b46f9df9127598a83d174eb13a48b26/grpcio-1.80.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:00168469238b022500e486c1c33916acf2f2a9b2c022202cf8a1885d2e3073c1", size = 7414743, upload-time = "2026-03-30T08:46:59.682Z" },
{ url = "https://files.pythonhosted.org/packages/de/f2/567f5bd5054398ed6b0509b9a30900376dcf2786bd936812098808b49d8d/grpcio-1.80.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8502122a3cc1714038e39a0b071acb1207ca7844208d5ea0d091317555ee7106", size = 8426046, upload-time = "2026-03-30T08:47:02.474Z" },
{ url = "https://files.pythonhosted.org/packages/62/29/73ef0141b4732ff5eacd68430ff2512a65c004696997f70476a83e548e7e/grpcio-1.80.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce1794f4ea6cc3ca29463f42d665c32ba1b964b48958a66497917fe9069f26e6", size = 7851641, upload-time = "2026-03-30T08:47:05.462Z" },
{ url = "https://files.pythonhosted.org/packages/46/69/abbfa360eb229a8623bab5f5a4f8105e445bd38ce81a89514ba55d281ad0/grpcio-1.80.0-cp311-cp311-win32.whl", hash = "sha256:51b4a7189b0bef2aa30adce3c78f09c83526cf3dddb24c6a96555e3b97340440", size = 4154368, upload-time = "2026-03-30T08:47:08.027Z" },
{ url = "https://files.pythonhosted.org/packages/6f/d4/ae92206d01183b08613e846076115f5ac5991bae358d2a749fa864da5699/grpcio-1.80.0-cp311-cp311-win_amd64.whl", hash = "sha256:02e64bb0bb2da14d947a49e6f120a75e947250aebe65f9629b62bb1f5c14e6e9", size = 4894235, upload-time = "2026-03-30T08:47:10.839Z" },
{ url = "https://files.pythonhosted.org/packages/5c/e8/a2b749265eb3415abc94f2e619bbd9e9707bebdda787e61c593004ec927a/grpcio-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:c624cc9f1008361014378c9d776de7182b11fe8b2e5a81bc69f23a295f2a1ad0", size = 6015616, upload-time = "2026-03-30T08:47:13.428Z" },
{ url = "https://files.pythonhosted.org/packages/3e/97/b1282161a15d699d1e90c360df18d19165a045ce1c343c7f313f5e8a0b77/grpcio-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f49eddcac43c3bf350c0385366a58f36bed8cc2c0ec35ef7b74b49e56552c0c2", size = 12014204, upload-time = "2026-03-30T08:47:15.873Z" },
{ url = "https://files.pythonhosted.org/packages/6e/5e/d319c6e997b50c155ac5a8cb12f5173d5b42677510e886d250d50264949d/grpcio-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d334591df610ab94714048e0d5b4f3dd5ad1bee74dfec11eee344220077a79de", size = 6563866, upload-time = "2026-03-30T08:47:18.588Z" },
{ url = "https://files.pythonhosted.org/packages/ae/f6/fdd975a2cb4d78eb67769a7b3b3830970bfa2e919f1decf724ae4445f42c/grpcio-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0cb517eb1d0d0aaf1d87af7cc5b801d686557c1d88b2619f5e31fab3c2315921", size = 7273060, upload-time = "2026-03-30T08:47:21.113Z" },
{ url = "https://files.pythonhosted.org/packages/db/f0/a3deb5feba60d9538a962913e37bd2e69a195f1c3376a3dd44fe0427e996/grpcio-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e78c4ac0d97dc2e569b2f4bcbbb447491167cb358d1a389fc4af71ab6f70411", size = 6782121, upload-time = "2026-03-30T08:47:23.827Z" },
{ url = "https://files.pythonhosted.org/packages/ca/84/36c6dcfddc093e108141f757c407902a05085e0c328007cb090d56646cdf/grpcio-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2ed770b4c06984f3b47eb0517b1c69ad0b84ef3f40128f51448433be904634cd", size = 7383811, upload-time = "2026-03-30T08:47:26.517Z" },
{ url = "https://files.pythonhosted.org/packages/7c/ef/f3a77e3dc5b471a0ec86c564c98d6adfa3510d38f8ee99010410858d591e/grpcio-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:256507e2f524092f1473071a05e65a5b10d84b82e3ff24c5b571513cfaa61e2f", size = 8393860, upload-time = "2026-03-30T08:47:29.439Z" },
{ url = "https://files.pythonhosted.org/packages/9b/8d/9d4d27ed7f33d109c50d6b5ce578a9914aa68edab75d65869a17e630a8d1/grpcio-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a6284a5d907c37db53350645567c522be314bac859a64a7a5ca63b77bb7958f", size = 7830132, upload-time = "2026-03-30T08:47:33.254Z" },
{ url = "https://files.pythonhosted.org/packages/14/e4/9990b41c6d7a44e1e9dee8ac11d7a9802ba1378b40d77468a7761d1ad288/grpcio-1.80.0-cp312-cp312-win32.whl", hash = "sha256:c71309cfce2f22be26aa4a847357c502db6c621f1a49825ae98aa0907595b193", size = 4140904, upload-time = "2026-03-30T08:47:35.319Z" },
{ url = "https://files.pythonhosted.org/packages/2f/2c/296f6138caca1f4b92a31ace4ae1b87dab692fc16a7a3417af3bb3c805bf/grpcio-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe648599c0e37594c4809d81a9e77bd138cc82eb8baa71b6a86af65426723ff", size = 4880944, upload-time = "2026-03-30T08:47:37.831Z" },
{ url = "https://files.pythonhosted.org/packages/2f/3a/7c3c25789e3f069e581dc342e03613c5b1cb012c4e8c7d9d5cf960a75856/grpcio-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e9e408fc016dffd20661f0126c53d8a31c2821b5c13c5d67a0f5ed5de93319ad", size = 6017243, upload-time = "2026-03-30T08:47:40.075Z" },
{ url = "https://files.pythonhosted.org/packages/04/19/21a9806eb8240e174fd1ab0cd5b9aa948bb0e05c2f2f55f9d5d7405e6d08/grpcio-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:92d787312e613754d4d8b9ca6d3297e69994a7912a32fa38c4c4e01c272974b0", size = 12010840, upload-time = "2026-03-30T08:47:43.11Z" },
{ url = "https://files.pythonhosted.org/packages/18/3a/23347d35f76f639e807fb7a36fad3068aed100996849a33809591f26eca6/grpcio-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac393b58aa16991a2f1144ec578084d544038c12242da3a215966b512904d0f", size = 6567644, upload-time = "2026-03-30T08:47:46.806Z" },
{ url = "https://files.pythonhosted.org/packages/ff/40/96e07ecb604a6a67ae6ab151e3e35b132875d98bc68ec65f3e5ab3e781d7/grpcio-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:68e5851ac4b9afe07e7f84483803ad167852570d65326b34d54ca560bfa53fb6", size = 7277830, upload-time = "2026-03-30T08:47:49.643Z" },
{ url = "https://files.pythonhosted.org/packages/9b/e2/da1506ecea1f34a5e365964644b35edef53803052b763ca214ba3870c856/grpcio-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:873ff5d17d68992ef6605330127425d2fc4e77e612fa3c3e0ed4e668685e3140", size = 6783216, upload-time = "2026-03-30T08:47:52.817Z" },
{ url = "https://files.pythonhosted.org/packages/44/83/3b20ff58d0c3b7f6caaa3af9a4174d4023701df40a3f39f7f1c8e7c48f9d/grpcio-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2bea16af2750fd0a899bf1abd9022244418b55d1f37da2202249ba4ba673838d", size = 7385866, upload-time = "2026-03-30T08:47:55.687Z" },
{ url = "https://files.pythonhosted.org/packages/47/45/55c507599c5520416de5eefecc927d6a0d7af55e91cfffb2e410607e5744/grpcio-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba0db34f7e1d803a878284cd70e4c63cb6ae2510ba51937bf8f45ba997cefcf7", size = 8391602, upload-time = "2026-03-30T08:47:58.303Z" },
{ url = "https://files.pythonhosted.org/packages/10/bb/dd06f4c24c01db9cf11341b547d0a016b2c90ed7dbbb086a5710df7dd1d7/grpcio-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8eb613f02d34721f1acf3626dfdb3545bd3c8505b0e52bf8b5710a28d02e8aa7", size = 7826752, upload-time = "2026-03-30T08:48:01.311Z" },
{ url = "https://files.pythonhosted.org/packages/f9/1e/9d67992ba23371fd63d4527096eb8c6b76d74d52b500df992a3343fd7251/grpcio-1.80.0-cp313-cp313-win32.whl", hash = "sha256:93b6f823810720912fd131f561f91f5fed0fda372b6b7028a2681b8194d5d294", size = 4142310, upload-time = "2026-03-30T08:48:04.594Z" },
{ url = "https://files.pythonhosted.org/packages/cf/e6/283326a27da9e2c3038bc93eeea36fb118ce0b2d03922a9cda6688f53c5b/grpcio-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:e172cf795a3ba5246d3529e4d34c53db70e888fa582a8ffebd2e6e48bc0cba50", size = 4882833, upload-time = "2026-03-30T08:48:07.363Z" },
{ url = "https://files.pythonhosted.org/packages/c5/6d/e65307ce20f5a09244ba9e9d8476e99fb039de7154f37fb85f26978b59c3/grpcio-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3d4147a97c8344d065d01bbf8b6acec2cf86fb0400d40696c8bdad34a64ffc0e", size = 6017376, upload-time = "2026-03-30T08:48:10.005Z" },
{ url = "https://files.pythonhosted.org/packages/69/10/9cef5d9650c72625a699c549940f0abb3c4bfdb5ed45a5ce431f92f31806/grpcio-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8e11f167935b3eb089ac9038e1a063e6d7dbe995c0bb4a661e614583352e76f", size = 12018133, upload-time = "2026-03-30T08:48:12.927Z" },
{ url = "https://files.pythonhosted.org/packages/04/82/983aabaad82ba26113caceeb9091706a0696b25da004fe3defb5b346e15b/grpcio-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f14b618fc30de822681ee986cfdcc2d9327229dc4c98aed16896761cacd468b9", size = 6574748, upload-time = "2026-03-30T08:48:16.386Z" },
{ url = "https://files.pythonhosted.org/packages/07/d7/031666ef155aa0bf399ed7e19439656c38bbd143779ae0861b038ce82abd/grpcio-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4ed39fbdcf9b87370f6e8df4e39ca7b38b3e5e9d1b0013c7b6be9639d6578d14", size = 7277711, upload-time = "2026-03-30T08:48:19.627Z" },
{ url = "https://files.pythonhosted.org/packages/e8/43/f437a78f7f4f1d311804189e8f11fb311a01049b2e08557c1068d470cb2e/grpcio-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dcc70e9f0ba987526e8e8603a610fb4f460e42899e74e7a518bf3c68fe1bf05", size = 6785372, upload-time = "2026-03-30T08:48:22.373Z" },
{ url = "https://files.pythonhosted.org/packages/93/3d/f6558e9c6296cb4227faa5c43c54a34c68d32654b829f53288313d16a86e/grpcio-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448c884b668b868562b1bda833c5fce6272d26e1926ec46747cda05741d302c1", size = 7395268, upload-time = "2026-03-30T08:48:25.638Z" },
{ url = "https://files.pythonhosted.org/packages/06/21/0fdd77e84720b08843c371a2efa6f2e19dbebf56adc72df73d891f5506f0/grpcio-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a1dc80fe55685b4a543555e6eef975303b36c8db1023b1599b094b92aa77965f", size = 8392000, upload-time = "2026-03-30T08:48:28.974Z" },
{ url = "https://files.pythonhosted.org/packages/f5/68/67f4947ed55d2e69f2cc199ab9fd85e0a0034d813bbeef84df6d2ba4d4b7/grpcio-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:31b9ac4ad1aa28ffee5503821fafd09e4da0a261ce1c1281c6c8da0423c83b6e", size = 7828477, upload-time = "2026-03-30T08:48:32.054Z" },
{ url = "https://files.pythonhosted.org/packages/44/b6/8d4096691b2e385e8271911a0de4f35f0a6c7d05aff7098e296c3de86939/grpcio-1.80.0-cp314-cp314-win32.whl", hash = "sha256:367ce30ba67d05e0592470428f0ec1c31714cab9ef19b8f2e37be1f4c7d32fae", size = 4218563, upload-time = "2026-03-30T08:48:34.538Z" },
{ url = "https://files.pythonhosted.org/packages/e5/8c/bbe6baf2557262834f2070cf668515fa308b2d38a4bbf771f8f7872a7036/grpcio-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:3b01e1f5464c583d2f567b2e46ff0d516ef979978f72091fd81f5ab7fa6e2e7f", size = 5019457, upload-time = "2026-03-30T08:48:37.308Z" },
{ url = "https://files.pythonhosted.org/packages/9a/d5/f2b159d8eec08be2a855ef698f5b6f7f9fdda022e4dd9e4f5d968affd678/grpcio-1.81.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:6f9a0c9c1cc15c112d1c053064fd032b64917062292c3d70aea280e02ae10b77", size = 6086868, upload-time = "2026-06-11T12:44:19.364Z" },
{ url = "https://files.pythonhosted.org/packages/80/41/9c95232b94b219ed8b14029d9cd000e0381cafba869c451dda60af84f4ba/grpcio-1.81.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:69ef28e54fc85397f91b8c19592b8ef3d81952080366914823bd8572a2958120", size = 12062291, upload-time = "2026-06-11T12:44:27.142Z" },
{ url = "https://files.pythonhosted.org/packages/83/8b/bd9284bdd665ddf877a3e8bc2930d1bcf6ebdbae7b0da5c783dc26bd6e33/grpcio-1.81.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:15641444eca4a29358107b3dceb74c1c6305c55c822fd199b458aaea4068a7fb", size = 6635242, upload-time = "2026-06-11T12:44:30.741Z" },
{ url = "https://files.pythonhosted.org/packages/60/24/78fa025517a925f1a17da71c4ef9d5f1c6f9fa65af22dfb523c5c6317a21/grpcio-1.81.1-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d4b2dddfc219f54f956ccd53cf76a1d338ffe68fc7f2849ec9c7feb9927ff692", size = 7332974, upload-time = "2026-06-11T12:44:33.72Z" },
{ url = "https://files.pythonhosted.org/packages/f7/11/402295b388dd35861007f8a26a37c2e2f284212d57bdf407c31f36043746/grpcio-1.81.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ca1cc11d82677b9662082e5478b7528e2b7db7beaa6bdff42bd62789d81be399", size = 6836597, upload-time = "2026-06-11T12:44:36.108Z" },
{ url = "https://files.pythonhosted.org/packages/4d/71/37b10fd4fd579ffade6e695c14e9df5e8cba9e2365b81c131da438b67c34/grpcio-1.81.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa2ba7d2ad6df4d80127cea65e5b8d5e2c3adbf153ff4804452836328aca7c54", size = 7440660, upload-time = "2026-06-11T12:44:38.664Z" },
{ url = "https://files.pythonhosted.org/packages/4a/d5/40203f828abc83d458b634666df6df13778032f178c03845ad5a93682388/grpcio-1.81.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:592b5fee597faa91cce2dd294dd7d9a1c83d76c4dbf877e33ec1adb866b2fbed", size = 8443171, upload-time = "2026-06-11T12:44:41.678Z" },
{ url = "https://files.pythonhosted.org/packages/eb/2c/0ed82ea35b5ec595e10444940c1db8c0e0ef57aa46bc8797d5ff838a219e/grpcio-1.81.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62481553b1793a27e9b9c3cf9e5bd483ef045ca72462592074b46d42b0c4d9b9", size = 7868905, upload-time = "2026-06-11T12:44:44.854Z" },
{ url = "https://files.pythonhosted.org/packages/9e/1f/dcbdc1a68a07cc2b631c3098953794f17d75f93426a019240b90ce5423d6/grpcio-1.81.1-cp310-cp310-win32.whl", hash = "sha256:bb693b1e3d9a2f3fd228e2110daf4b5aeedb36761ca1e4282f74725f6d89f611", size = 4202215, upload-time = "2026-06-11T12:44:47.165Z" },
{ url = "https://files.pythonhosted.org/packages/75/a1/d7ab9f1f42efcb7d9e6111d38be6b367737a72ea2c534e1f55c81e1b6436/grpcio-1.81.1-cp310-cp310-win_amd64.whl", hash = "sha256:88268ca418cacea64cecb0d1d600d3c6b3a8038fcba02e1e205178c5b1f47661", size = 4936582, upload-time = "2026-06-11T12:44:49.479Z" },
{ url = "https://files.pythonhosted.org/packages/52/ea/1c2fa386b718ff493225e61cfc052ef400b4d6ffc54cbe261026432624b5/grpcio-1.81.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:d71d30f2d92f67d944631c523713934fee37292469e182ebcd2c1dd8a64ce53f", size = 6093112, upload-time = "2026-06-11T12:44:52.131Z" },
{ url = "https://files.pythonhosted.org/packages/2b/18/acf45fa8bd1bc5d7b0c2fd3dc4c209379fbd5bb396b440b68a83342226b7/grpcio-1.81.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b137f4bf3ada9dc44d411478decc6ff09a79ed30b306cd2abaa98408c3588137", size = 12074277, upload-time = "2026-06-11T12:44:55.354Z" },
{ url = "https://files.pythonhosted.org/packages/48/d7/ee86a60699b7db039f772a2c4a7e4facc7138984ff42c0130933a0063884/grpcio-1.81.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a3acb384427816dd5d470f47e62137b87f74da694faa8a50147012cf40df276a", size = 6640348, upload-time = "2026-06-11T12:44:59.223Z" },
{ url = "https://files.pythonhosted.org/packages/26/ee/d2de5e47378ffc207d476c230fea3be4d2601edbce9995f4fe45535d4896/grpcio-1.81.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f9a0ebbe45c29b5e5866593c12b78bd9035f0f0f0d4bc8361680cd580d99db49", size = 7331842, upload-time = "2026-06-11T12:45:02.001Z" },
{ url = "https://files.pythonhosted.org/packages/23/d6/abeda5c2b896a0b341584fe5ac411bbf72e197a9a374c355fb90965e08d2/grpcio-1.81.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a37165cc80b1a368384b383e63a4c38116a10467ae44c904d2d7468c4470ec2", size = 6842229, upload-time = "2026-06-11T12:45:04.76Z" },
{ url = "https://files.pythonhosted.org/packages/10/1c/1f0da7d590b4aeee006826ba568d0e419ca14b23e18f901a3da3e9fba613/grpcio-1.81.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6282caffb41ec326d4cb67ca9cf53b739d1b2f975a2acb498c7418e9f7d9a416", size = 7446096, upload-time = "2026-06-11T12:45:07.499Z" },
{ url = "https://files.pythonhosted.org/packages/6a/81/5c505d508f7c887aa7982d21443a4126597c80d34b0bcf40f9cec576d7f3/grpcio-1.81.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a35009284d0d3d5c2c9601c164a911b8b4331608d98a9a66d47d97bb2f522b70", size = 8445238, upload-time = "2026-06-11T12:45:10.243Z" },
{ url = "https://files.pythonhosted.org/packages/f7/b2/524847365122ee509ca17bcc4e092198b700e94af7bfd5bb5e6dd9f3ee66/grpcio-1.81.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1b22c80559854b789a01fd89e8929b3798a156c0829b5282a8939f33ad4115ad", size = 7873989, upload-time = "2026-06-11T12:45:13.102Z" },
{ url = "https://files.pythonhosted.org/packages/18/fa/07c037c50b006909d1d13a5848774f8aa7b242f70dc03a035c64eea0e6db/grpcio-1.81.1-cp311-cp311-win32.whl", hash = "sha256:428bec0161b48d8cf583c068591bc0016d0d9cfff52462b72b3884861ea768c5", size = 4202223, upload-time = "2026-06-11T12:45:16.166Z" },
{ url = "https://files.pythonhosted.org/packages/41/ed/6bff15376920942fac6b95b9802752b837437172c9e8fc2d3170546b89cc/grpcio-1.81.1-cp311-cp311-win_amd64.whl", hash = "sha256:30e825f6848d9f18bba350ed6c75c1b02a0b5184474a31db9a32b1fa66fd8c79", size = 4941303, upload-time = "2026-06-11T12:45:18.724Z" },
{ url = "https://files.pythonhosted.org/packages/85/07/9a979c81738863a738dc23d65177056e71fbb2db817740ed870b33434e7a/grpcio-1.81.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:8b39472beafc0bdcafc4c8c73ad082ebfdb449d566897a61e7acb4fa88089115", size = 6053264, upload-time = "2026-06-11T12:45:21.017Z" },
{ url = "https://files.pythonhosted.org/packages/75/95/539706ca0d3bd40dbad583dc56fd883da941f37556b629132da5762781b9/grpcio-1.81.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:12b7524c88d4026d3dcb7b0ebe16b6714f3b4af402ddd0f0639ab064a00c87c3", size = 12052560, upload-time = "2026-06-11T12:45:23.652Z" },
{ url = "https://files.pythonhosted.org/packages/e0/44/f257b7e0bd69c93b06c6cb8ac8d1b901ccb42bedabd83c1a4c77a71f8810/grpcio-1.81.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1e123f9b37edb8375fd74130d1f69c944bbf0a7b06761ae7211154b8759e94d2", size = 6595983, upload-time = "2026-06-11T12:45:26.963Z" },
{ url = "https://files.pythonhosted.org/packages/b9/f3/19782aa04c960968bef8c5539329d8e3bbc3364e2e46d19eb5e5cc5e43b7/grpcio-1.81.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2c2e2ae6867c2966b8daccc836d54a13218e0007e9a490aeb81dd05be64d22d7", size = 7303455, upload-time = "2026-06-11T12:45:29.707Z" },
{ url = "https://files.pythonhosted.org/packages/eb/8c/dea020b6d91508cd84463917a63149ec196ee7db505d032ae43fcb3303b9/grpcio-1.81.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:766bc7c9a9c340342f4c864ccbda8e78111e4751f13b895812b9c148fb79e9d0", size = 6809167, upload-time = "2026-06-11T12:45:32.52Z" },
{ url = "https://files.pythonhosted.org/packages/1c/c7/3030dd940408083bd32cd95d634777a71605ade4887154d93e8a89244946/grpcio-1.81.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b259a04a737cb3496be0901328eb8b7552ed8df4865d8c8f1cf1bffcfc0776a3", size = 7412536, upload-time = "2026-06-11T12:45:35.403Z" },
{ url = "https://files.pythonhosted.org/packages/e0/dd/1172a9e42b168edcafefad6115346ef619a3fc02158bb170e66ced24bcdd/grpcio-1.81.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:85b10a45b8993d195c4f3ff57025b8d1e11834909ee475c403bfa60cb4caefaf", size = 8408276, upload-time = "2026-06-11T12:45:37.78Z" },
{ url = "https://files.pythonhosted.org/packages/25/7a/71437c7f3596e5246155c515852795a85a1a8d228190212432b13b97a95d/grpcio-1.81.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8ea1936c26b99999b27479853039a7f34713f56c49375ad52b38535ec93a796c", size = 7849660, upload-time = "2026-06-11T12:45:40.627Z" },
{ url = "https://files.pythonhosted.org/packages/65/40/7debc0da45d2efebafb82da75644be347497fe4ee250514b8cd3b86ae8bf/grpcio-1.81.1-cp312-cp312-win32.whl", hash = "sha256:a185a04039df6cae8648bc8ab6d6fde7bf94f7188ecf7828e76ac52eef1e41d6", size = 4185819, upload-time = "2026-06-11T12:45:43.027Z" },
{ url = "https://files.pythonhosted.org/packages/2e/b9/8fe3ba5ed462067774ebc1f9c7f26aa7ebcc280ddd476be107153de1339e/grpcio-1.81.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ad74f8bb1a18963914c5452d289422830b39459e8776ebbcd207be1fbfb1d94", size = 4930461, upload-time = "2026-06-11T12:45:45.775Z" },
{ url = "https://files.pythonhosted.org/packages/7a/42/dcc2e4b600538ef18327c0839d56b7d3c3812337c5d710df5877dbb39b1e/grpcio-1.81.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b10e1ff4756ed27d5a29d7fc79cfce7ef1ff56ad20025b89bac7cf79e09abbbe", size = 6054466, upload-time = "2026-06-11T12:45:48.43Z" },
{ url = "https://files.pythonhosted.org/packages/7b/4a/a36e03210183a8a7d4c80c3936acee679f4bd77d5861f369db47b2cc5f05/grpcio-1.81.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:819edbdcb42ab8598b494bcf0222684bbb7a3c772bd1b1f0be7e029a6063c28e", size = 12048795, upload-time = "2026-06-11T12:45:54.011Z" },
{ url = "https://files.pythonhosted.org/packages/b0/d5/d68e30b29098f63beab6fe501100fe82674ff142b32c672532da86a99b3a/grpcio-1.81.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5bf2dc311127d91230cc79b92188c082634a06cf66c5234db49a43b910183b0", size = 6599094, upload-time = "2026-06-11T12:45:57.799Z" },
{ url = "https://files.pythonhosted.org/packages/3d/b3/e837954d279754f638a11cca5dcf6b24a005efb398984cefaf7735945a54/grpcio-1.81.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e8ca6a1fcdb2943c9cbc1804a1baf3acb6071d72a471591678ded84218006e14", size = 7307182, upload-time = "2026-06-11T12:46:00.568Z" },
{ url = "https://files.pythonhosted.org/packages/0d/1e/b47957057e729adc6cdf519a47f8be2562b7140e280f1418443eb4022192/grpcio-1.81.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e64dd101d380a115cc5a0c7856788adb535f1a4e21fc543775602f8be95180ae", size = 6810962, upload-time = "2026-06-11T12:46:03.312Z" },
{ url = "https://files.pythonhosted.org/packages/40/26/569868e364e05b19ec8f969da53d230bcd89c962cd198f7c29943155c4d3/grpcio-1.81.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:98a07f9bf591e3a8919797bee1c53f026ba4acd587e5a4404c8e57c9ec36b2a5", size = 7415698, upload-time = "2026-06-11T12:46:06.005Z" },
{ url = "https://files.pythonhosted.org/packages/36/0c/5440a0582cb5653fc42a6e262eeb22700943313f8076f9dc927491b20a59/grpcio-1.81.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c261d74b1a945cf895a9d6eccd1685a8e837531beaab782da4d630a8d12deffb", size = 8407779, upload-time = "2026-06-11T12:46:08.84Z" },
{ url = "https://files.pythonhosted.org/packages/ff/aa/66fe9f39871d766987d869a03ee0842a026f499c7b1e62decb9e78a8088e/grpcio-1.81.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58ad1131c300d3c9b933802b3cc4dc69d380822935ba50b28703156ea826fbf7", size = 7844521, upload-time = "2026-06-11T12:46:12.171Z" },
{ url = "https://files.pythonhosted.org/packages/f0/9e/69bb7194861bcd28fb3193261d4f9c3831b4446993f002cf59068943e7ab/grpcio-1.81.1-cp313-cp313-win32.whl", hash = "sha256:78e29211f26da2fdd0e9c6d2b79f489476140cf7029b6a64808ade7ca4156a42", size = 4182786, upload-time = "2026-06-11T12:46:15.192Z" },
{ url = "https://files.pythonhosted.org/packages/0d/20/3da8bb0d637feccdc3e1e419bb511ce93651ce7d54164f95de22cc0b8b34/grpcio-1.81.1-cp313-cp313-win_amd64.whl", hash = "sha256:edb59506291b647a30884b1d51a599d605f40b20af4a7dc3d33786a47a31de60", size = 4928648, upload-time = "2026-06-11T12:46:17.823Z" },
{ url = "https://files.pythonhosted.org/packages/b6/58/19414622b1bf6981bc9c05a365bd548e71876c89000083b3af489251e9c0/grpcio-1.81.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:506f48f2f9c29b143fca3dad7b0d518c188b6c9648c75a2ae6e2d9f2c13a060b", size = 6055336, upload-time = "2026-06-11T12:46:20.557Z" },
{ url = "https://files.pythonhosted.org/packages/32/f1/2ec88adb92b0eba970dd0e0e7dd086341daa3c75eba4f735f9e44bf684b0/grpcio-1.81.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d865db4a6318e1c1bea83292e0ed231090538fc4ca45425b0f0480eb338bbc6e", size = 12056279, upload-time = "2026-06-11T12:46:24.255Z" },
{ url = "https://files.pythonhosted.org/packages/41/36/e8c5f8c6ec71de73733695ebc809e98b178b534ec6d8eaa31a7ebab4ad4c/grpcio-1.81.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2aa72e3ce1770317ef534f63d397b55e130725f5149bd36077c3b539019db27", size = 6608225, upload-time = "2026-06-11T12:46:27.601Z" },
{ url = "https://files.pythonhosted.org/packages/30/22/96fc577a845ab093326d9ab1adb874bd4936c8cf98ac8ed2f3db13a0a2fb/grpcio-1.81.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0490c30c261eded63f3f354979f9dc4502a9fb944cccb60cd9dc85f5a7349854", size = 7306576, upload-time = "2026-06-11T12:46:30.514Z" },
{ url = "https://files.pythonhosted.org/packages/76/7b/61dab5d5969f28d97fb1009cead1df0a5cd987d3315e1b37f18a4449f8bc/grpcio-1.81.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:410482da976329fe5f4067270401b12cf2bd552ff8020f054ecfaddb5475f9d6", size = 6812165, upload-time = "2026-06-11T12:46:33.699Z" },
{ url = "https://files.pythonhosted.org/packages/82/78/6e501929d4f5f96462fd82fd9f0f06e5f9612207582b862868d68757b27d/grpcio-1.81.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3657301562ac3cb8018d30d0d3ebfa39932239f7b5703422057ef14b69949f5", size = 7422962, upload-time = "2026-06-11T12:46:36.511Z" },
{ url = "https://files.pythonhosted.org/packages/2a/7e/f2157589e66daa78ebb3165942d05a08bdea93b9d11c2bc1e172aef89685/grpcio-1.81.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24c8e57504c8f45b237e40b99262d181071e5099a07053695b75d97bb53053a0", size = 8408176, upload-time = "2026-06-11T12:46:39.803Z" },
{ url = "https://files.pythonhosted.org/packages/da/df/c6717fef716e00d235ffb96123baf6dce76d6004f6233fa767c502861460/grpcio-1.81.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b427c19380991a4eaab2f6144b64b99b412043314c6bf4ab544f97bb31ee4190", size = 7846681, upload-time = "2026-06-11T12:46:43.013Z" },
{ url = "https://files.pythonhosted.org/packages/36/84/3502e9f210a6a5c4438c8aca3f88edd2e04f6a27f3d41b26cf0a0024b096/grpcio-1.81.1-cp314-cp314-win32.whl", hash = "sha256:61233fe8951e5c85dff81c2458b6528624760166946b5b47ea150a589168411f", size = 4264615, upload-time = "2026-06-11T12:46:45.741Z" },
{ url = "https://files.pythonhosted.org/packages/ff/b0/4af731ff7492c68a96e4c71bfd0f4590acde92b31c6fe4894e6465c10ff6/grpcio-1.81.1-cp314-cp314-win_amd64.whl", hash = "sha256:3768a5ff1b2125e6f552e561b6b2dca0e64982d8949689b4df145cf8b98d7821", size = 5070275, upload-time = "2026-06-11T12:46:48.486Z" },
]
[[package]]
name = "grpcio-health-checking"
version = "1.80.0"
version = "1.81.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "grpcio" },
{ name = "protobuf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d1/a2/aa3cc47f19c03f8e5287b987317059753141a3af8f66b96d5a64b3be10b8/grpcio_health_checking-1.80.0.tar.gz", hash = "sha256:2cc5f08bc8b816b8655ab6f59c71450063ba20766d31e21a493e912e3560c8b1", size = 17117, upload-time = "2026-03-30T08:54:41.899Z" }
sdist = { url = "https://files.pythonhosted.org/packages/65/46/6b6678b5a922765ae7637205bb6d0618a4da8b35f2ce6116f8bcff262370/grpcio_health_checking-1.81.1.tar.gz", hash = "sha256:ecc61480e25058a4a04e11e4ab6900ad7439b32e60a8ce4ece7d9f219221c85d", size = 17107, upload-time = "2026-06-11T12:58:49.632Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5e/d1/d97eb30386feff6ac2a662620e2ed68be352e9a182d62e06213db694906a/grpcio_health_checking-1.80.0-py3-none-any.whl", hash = "sha256:d804d4549cbb71e90ca2c7bf0c501060135dfd220aca8e2c54f96d3e79e210e5", size = 19125, upload-time = "2026-03-30T08:53:44.835Z" },
{ url = "https://files.pythonhosted.org/packages/6e/87/174bbfb5613794862c45b5cdf567fcfb478ad5a3a7b594e11d998656b2f9/grpcio_health_checking-1.81.1-py3-none-any.whl", hash = "sha256:cbc6a4171825ec64389de2f062d296ba129a5c27eefd0dd55fa837909184bdf9", size = 19120, upload-time = "2026-06-11T12:58:38.008Z" },
]
[[package]]
name = "grpcio-tools"
version = "1.80.0"
version = "1.81.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "grpcio" },
{ name = "protobuf" },
{ name = "setuptools" },
]
sdist = { url = "https://files.pythonhosted.org/packages/94/c8/1223f29c84a143ae9a56c084fc96894de0ba84b6e8d60a26241abd81d278/grpcio_tools-1.80.0.tar.gz", hash = "sha256:26052b19c6ce0dcf52d1024496aea3e2bdfa864159f06dc7b97b22d041a94b26", size = 6133212, upload-time = "2026-03-30T08:52:39.077Z" }
sdist = { url = "https://files.pythonhosted.org/packages/83/b3/1c5951352d6777fd7f99a0ccee04617fdfd8a5dbf2918a1f58c8b2b280b8/grpcio_tools-1.81.1.tar.gz", hash = "sha256:a22a3870180927fdd84e2b27d079ef5b7f5f8c6110181b6736afc17a463481f1", size = 6236155, upload-time = "2026-06-11T12:51:21.235Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7a/54/1de67f5080da305a258758a8deb33f85666fa759f56785042a80b114a53f/grpcio_tools-1.80.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:727477b9afa4b53f5ec70cafb41c3965d893835e0d4ea9b542fe3d0d005602bf", size = 2549601, upload-time = "2026-03-30T08:50:09.498Z" },
{ url = "https://files.pythonhosted.org/packages/9a/b4/6d57ea199c5b880d182a2234aafa9a686f9c54c708ea7be75bd19d5aa825/grpcio_tools-1.80.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:85fe8d15f146c62cb76f38d963e256392d287442b9232717d30ae9e3bbda9bc3", size = 5712717, upload-time = "2026-03-30T08:50:15.028Z" },
{ url = "https://files.pythonhosted.org/packages/6a/1a/5505ee2277d368b409c796c78f22ea34a2a517b7d16755247efd663dc7af/grpcio_tools-1.80.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:95f0fffb5ca00519f3b602f938169b4dfa04b165e03258323965a9dfe8cc4d80", size = 2595941, upload-time = "2026-03-30T08:50:17.299Z" },
{ url = "https://files.pythonhosted.org/packages/4e/39/7fc1d16d8b767805079d76365d73e82c88dfaf179034473dbc9fbccedb77/grpcio_tools-1.80.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:7a0106af212748823a6ebd8ffbd9043414216f47cae3835f3187de0a62c415d3", size = 2909304, upload-time = "2026-03-30T08:50:19.485Z" },
{ url = "https://files.pythonhosted.org/packages/97/d8/276ee759755d8f34f2ca5e9d2debd1a59f29f66059fb790bc369f2236c26/grpcio_tools-1.80.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:31fd01a4038b5dfc4ec79504a17061344f670f851833411717fef66920f13cd7", size = 2660269, upload-time = "2026-03-30T08:50:21.266Z" },
{ url = "https://files.pythonhosted.org/packages/51/04/a6bb47942ad52901d777a649324d3203cf19d487f1d446263637f7a5bf12/grpcio_tools-1.80.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:57da9e19607fac4a01c48ead333c0dd15d91ed38794dce1194eda308f73e2038", size = 3109798, upload-time = "2026-03-30T08:50:23.267Z" },
{ url = "https://files.pythonhosted.org/packages/be/50/7ee69b2919916739787d725f205b878e8d1619dd30422b8278e324664669/grpcio_tools-1.80.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:90968f751851abb8b145593609800fa70c837e1c93ba0792c480b1c8d8bc29ef", size = 3658930, upload-time = "2026-03-30T08:50:25.458Z" },
{ url = "https://files.pythonhosted.org/packages/92/61/6d50783092b0e8bbcb04152d5388bf50ecf3ea2f783d95288ff6c3bb00fa/grpcio_tools-1.80.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b69dc5d6376ab43406304d1e2fc61ccf960b287d4325d77c3d45448c37a9d2da", size = 3326562, upload-time = "2026-03-30T08:50:27.809Z" },
{ url = "https://files.pythonhosted.org/packages/ea/58/d272ba549f6b1f0d8504f5fc4cd0a296f2c495a64d6e987fe871c4151557/grpcio_tools-1.80.0-cp310-cp310-win32.whl", hash = "sha256:3e8dcfebe34cb54df095de3d5871a4562a85a29f26d0f8bb41ee2c3dcfb11c3c", size = 997620, upload-time = "2026-03-30T08:50:29.959Z" },
{ url = "https://files.pythonhosted.org/packages/70/5f/9f45a9946a0298711c72ca48b2c1f46a7d0c207a44cd3e4bb59d04556ba3/grpcio_tools-1.80.0-cp310-cp310-win_amd64.whl", hash = "sha256:fc622ed4ca400695f41c9eae3266276c6ba007e4c28164ce53b44e7ccc5e492b", size = 1162466, upload-time = "2026-03-30T08:50:32.242Z" },
{ url = "https://files.pythonhosted.org/packages/8a/d7/225dc91e6cb4f8d4830f16a478a468e9c6f342dcdf8cacc3772cc1d1f607/grpcio_tools-1.80.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:1c43e5c768578fe0c6de3dbfaabe64af642951e1aa05c487cacedda63fa6c6c4", size = 2549937, upload-time = "2026-03-30T08:50:34.651Z" },
{ url = "https://files.pythonhosted.org/packages/97/3d/a3684cb7677f3bea8db434eae02a9ce30135d7a268cd473b1bc8041c4722/grpcio_tools-1.80.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a225348456575f3ac7851d8e23163195e76d2a905ee340cf73f33da62fba08aa", size = 5713099, upload-time = "2026-03-30T08:50:37.158Z" },
{ url = "https://files.pythonhosted.org/packages/b1/81/5665c697173ec346076358bfbfed0f7386825852494593ca14386478dfee/grpcio_tools-1.80.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a9396f02820d3f51c368c2c9dee15c55c77636c91be48a4d5c702e98d6fe0fdc", size = 2595776, upload-time = "2026-03-30T08:50:39.087Z" },
{ url = "https://files.pythonhosted.org/packages/03/4f/fb81384f08a8226fa079972ba88272ac6277581fc72e8ab234d74c7e065b/grpcio_tools-1.80.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:797c08460cae16b402326eac329aec720dccf45c9f9279b95a352792eb53cf0f", size = 2909144, upload-time = "2026-03-30T08:50:40.922Z" },
{ url = "https://files.pythonhosted.org/packages/4d/9c/c957618f1c2a3195ecf5e83b03edcb364c2c1391f74183cb76e5763fa536/grpcio_tools-1.80.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1872a867eb6217de19edb70a4ce4a374ced9d94293533dfd42fa649713f55bf4", size = 2660477, upload-time = "2026-03-30T08:50:42.766Z" },
{ url = "https://files.pythonhosted.org/packages/42/c7/23913da184febfd4eaf04de256a26bc5ff0411a5feb753e2adcff10fa86a/grpcio_tools-1.80.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:db122ba5ee357e3bb14e8944d69bbebcbdae91d5eace29ed4df3edc53cbc6528", size = 3110164, upload-time = "2026-03-30T08:50:44.761Z" },
{ url = "https://files.pythonhosted.org/packages/af/fa/b25ed85ebdb0396910eaa250b1346d75527d22fca586265416bd4330dcd5/grpcio_tools-1.80.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ddefd48c227e6f4d640fe576fac5fb2c4a8898196f513604c8ec7671b3b3d421", size = 3658988, upload-time = "2026-03-30T08:50:47.546Z" },
{ url = "https://files.pythonhosted.org/packages/60/85/2a55147cc9645e2ed777d1afcd2dc68cb34ba6f6c726bd4378ddb001a5ea/grpcio_tools-1.80.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:970ec058fa469dd6dae6ebc687501c5da670d95dead75f62f5b0933dce2c9794", size = 3326662, upload-time = "2026-03-30T08:50:49.59Z" },
{ url = "https://files.pythonhosted.org/packages/68/ed/b05bee2a992e6f9bda81909692ea920d0896cfa05c5c9dd77ba03f2d22fb/grpcio_tools-1.80.0-cp311-cp311-win32.whl", hash = "sha256:526b4402d47a0e9b31cd6087e42b7674784617916cc73c764e0bc35ed41b4ee5", size = 997969, upload-time = "2026-03-30T08:50:51.539Z" },
{ url = "https://files.pythonhosted.org/packages/b6/9a/cb50c8270e2f6285ff2761130ae257ac4e51789ded4b9d9710ce0381814d/grpcio_tools-1.80.0-cp311-cp311-win_amd64.whl", hash = "sha256:ee101ecda7231770f6a5da1024a9a6ed587a7785f8fe23ab8283f4a1acb3ffe6", size = 1162742, upload-time = "2026-03-30T08:50:54.232Z" },
{ url = "https://files.pythonhosted.org/packages/0c/b9/65929df8c9614792db900a8e45d4997fadbd1734c827da3f0eb1f2fe4866/grpcio_tools-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:d19d5a8244311947b96f749c417b32d144641c6953f1164824579e1f0a51d040", size = 2550856, upload-time = "2026-03-30T08:50:57.3Z" },
{ url = "https://files.pythonhosted.org/packages/28/17/af1557544d68d1aeca9d9ea53ed16524022d521fec6ba334ab3530e9c1a6/grpcio_tools-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:fb599a3dc89ed1bb24489a2724b2f6dd4cddbbf0f7bdd69c073477bab0dc7554", size = 5710883, upload-time = "2026-03-30T08:51:00.077Z" },
{ url = "https://files.pythonhosted.org/packages/cc/48/aa9b4f7519ca972bc40d315d5c28f05ca28fa08de13d4e8b69f551b798ab/grpcio_tools-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:623ee31fc2ff7df9a987b4f3d139c30af17ce46a861ae0e25fb8c112daa32dd8", size = 2598004, upload-time = "2026-03-30T08:51:02.102Z" },
{ url = "https://files.pythonhosted.org/packages/b4/b8/b01371c119924b3beca1fe3f047b1bc2cdc66b3d37f0f3acc9d10c567a43/grpcio_tools-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b46570a68378539ee2b75a5a43202561f8d753c832798b1047099e3c551cf5d6", size = 2909568, upload-time = "2026-03-30T08:51:04.159Z" },
{ url = "https://files.pythonhosted.org/packages/4f/7c/1108f7bdb58475a7e701ec89b55eb494538b6e76acd211ba0d4cc5fd28e8/grpcio_tools-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51caf99c28999e7e0f97e9cea190c1405b7681a57bb2e0631205accd92b43fa4", size = 2660938, upload-time = "2026-03-30T08:51:06.126Z" },
{ url = "https://files.pythonhosted.org/packages/67/59/d1c0063d4cd3b85363c7044ff3e5159d6d5df96e2692a9a5312d9c8cb290/grpcio_tools-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cdaa1c9aa8d3a87891a96700cadd29beec214711d6522818d207277f6452567c", size = 3113814, upload-time = "2026-03-30T08:51:08.834Z" },
{ url = "https://files.pythonhosted.org/packages/76/21/18d34a4efe524c903cf66b0cfa5260d81f277b6ae668b647edf795df9ce5/grpcio_tools-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3399b5fd7b59bcffd59c6b9975a969d9f37a3c87f3e3d63c3a09c147907acb0d", size = 3662793, upload-time = "2026-03-30T08:51:11.094Z" },
{ url = "https://files.pythonhosted.org/packages/f3/40/cf2d9295a6bd593244ea703858f8fc2efd315046ca3ef7c6f9ebc5b810fa/grpcio_tools-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9c6abc08d3485b2aac99bb58afcd31dc6cd4316ce36cf263ff09cb6df15f287f", size = 3329149, upload-time = "2026-03-30T08:51:13.066Z" },
{ url = "https://files.pythonhosted.org/packages/0d/1d/fc34b32167966df20d69429b71dfca83c48434b047a5ac4fd6cd91ca4eed/grpcio_tools-1.80.0-cp312-cp312-win32.whl", hash = "sha256:18c51e07652ac7386fcdbd11866f8d55a795de073337c12447b5805575339f74", size = 997519, upload-time = "2026-03-30T08:51:14.87Z" },
{ url = "https://files.pythonhosted.org/packages/91/98/6d6563cdf51085b75f8ec24605c6f2ce84197571878ca8ab4af949c6be2d/grpcio_tools-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:ac6fdd42d5bb18f0d903a067e2825be172deff70cf197164b6f65676cb506c9b", size = 1162407, upload-time = "2026-03-30T08:51:16.793Z" },
{ url = "https://files.pythonhosted.org/packages/44/d9/f7887a4805939e9a85d03744b66fc02575dc1df3c3e8b4d9ec000ee7a33d/grpcio_tools-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e7046837859bbfd10b01786056145480155c16b222c9e209215b68d3be13060e", size = 2550319, upload-time = "2026-03-30T08:51:19.117Z" },
{ url = "https://files.pythonhosted.org/packages/57/5a/c8a05b32bd7203f1b9f4c0151090a2d6179d6c97692d32f2066dc29c67a6/grpcio_tools-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a447f28958a8fe84ff0d9d3d9473868feb27ee4a9c9c805e66f5b670121cec59", size = 5709681, upload-time = "2026-03-30T08:51:21.991Z" },
{ url = "https://files.pythonhosted.org/packages/82/6b/794350ed645c12c310008f97068f6a6fd927150b0d0d08aad1d909e880b1/grpcio_tools-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:75f00450e08fe648ad8a1eeb25bc52219679d54cdd02f04dfdddc747309d83f6", size = 2596820, upload-time = "2026-03-30T08:51:24.323Z" },
{ url = "https://files.pythonhosted.org/packages/f9/b2/b39e7b79f7c878135e0784a53cd7260ee77260c8c7f2c9e46bca8e05d017/grpcio_tools-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3db830eaff1f2c2797328f2fa86c9dcdbd7d81af573a68db81e27afa2182a611", size = 2909193, upload-time = "2026-03-30T08:51:27.025Z" },
{ url = "https://files.pythonhosted.org/packages/10/f3/abe089b058f87f9910c9a458409505cbeb0b3e1c2d993a79721d02ee6a32/grpcio_tools-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7982b5fe42f012686b667dda12916884de95c4b1c65ff64371fb7232a1474b23", size = 2660197, upload-time = "2026-03-30T08:51:29.392Z" },
{ url = "https://files.pythonhosted.org/packages/09/c3/3f7806ad8b731d8a89fe3c6ed496473abd1ef4c9c42c9e9a8836ce96e377/grpcio_tools-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6451b3f4eb52d12c7f32d04bf8e0185f80521f3f088ad04b8d222b3a4819c71e", size = 3113144, upload-time = "2026-03-30T08:51:31.671Z" },
{ url = "https://files.pythonhosted.org/packages/fe/f5/415ef205e0b7e75d2a2005df6120145c4f02fda28d7b3715b55d924fe1a4/grpcio_tools-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:258bc30654a9a2236be4ca8e2ad443e2ac6db7c8cc20454d34cce60265922726", size = 3661897, upload-time = "2026-03-30T08:51:34.849Z" },
{ url = "https://files.pythonhosted.org/packages/e3/d3/2ad54764c2a9547080dd8518f4a4dc7899c7e6e747a1b1de542ce6a12066/grpcio_tools-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:865a2b8e6334c838976ab02a322cbd55c863d2eaf3c1e1a0255883c63996772a", size = 3328786, upload-time = "2026-03-30T08:51:37.265Z" },
{ url = "https://files.pythonhosted.org/packages/eb/63/23ab7db01f9630ab4f3742a2fc9fbff38b0cfc30c976114f913950664a75/grpcio_tools-1.80.0-cp313-cp313-win32.whl", hash = "sha256:f760ac1722f33e774814c37b6aa0444143f612e85088ead7447a0e9cd306a1f1", size = 997087, upload-time = "2026-03-30T08:51:39.137Z" },
{ url = "https://files.pythonhosted.org/packages/9b/af/b1c1c4423fb49cb7c8e9d2c02196b038c44160b7028b425466743c6c81fa/grpcio_tools-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:7843b9ac6ff8ca508424d0dd968bd9a1a4559967e4a290f26be5bd6f04af2234", size = 1162167, upload-time = "2026-03-30T08:51:41.498Z" },
{ url = "https://files.pythonhosted.org/packages/0e/44/7beeee2348f9f412804f5bf80b7d13b81d522bf926a338ae3da46b2213b7/grpcio_tools-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:12f950470449dbeec78317dbc090add7a00eb6ca812af7b0538ab7441e0a42c3", size = 2550303, upload-time = "2026-03-30T08:51:44.373Z" },
{ url = "https://files.pythonhosted.org/packages/2d/aa/f77dd85409a1855f8c6319ffc69d81e8c3ffe122ee3a7136653e1991d8b6/grpcio_tools-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d3f9a376a29c9adf62bb56f7ff5bc81eb4abeaf53d1e7dde5015564832901a51", size = 5709778, upload-time = "2026-03-30T08:51:47.112Z" },
{ url = "https://files.pythonhosted.org/packages/9c/7c/ab7af4883ebdfdc228b853de89fed409703955e8d47285b321a5794856bd/grpcio_tools-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ba1ffbf2cff71533615e2c5a138ed5569611eec9ae7f9c67b8898e127b54ac0", size = 2597928, upload-time = "2026-03-30T08:51:49.494Z" },
{ url = "https://files.pythonhosted.org/packages/22/e8/4381a963d472e3ab6690ba067ed2b1f1abf8518b10f402678bd2dcb79a54/grpcio_tools-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:13f60f8d9397c514c6745a967d22b5c8c698347e88deebca1ff2e1b94555e450", size = 2909333, upload-time = "2026-03-30T08:51:52.124Z" },
{ url = "https://files.pythonhosted.org/packages/94/cb/356b5fdf79dd99455b425fb16302fe60995554ceb721afbf3cf770a19208/grpcio_tools-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:88d77bad5dd3cd5e6f952c4ecdd0ee33e0c02ecfc2e4b0cbee3391ac19e0a431", size = 2660217, upload-time = "2026-03-30T08:51:55.066Z" },
{ url = "https://files.pythonhosted.org/packages/2b/d7/1752018cc2c36b2c5612051379e2e5f59f2dbe612de23e817d2f066a9487/grpcio_tools-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:017945c3e98a4ed1c4e21399781b4137fc08dfc1f802c8ace2e64ef52d32b142", size = 3113896, upload-time = "2026-03-30T08:51:57.3Z" },
{ url = "https://files.pythonhosted.org/packages/cc/17/695bbe454f70df35c03e22b48c5314683b913d3e6ed35ec90d065418c1ab/grpcio_tools-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a33e265d4db803495007a6c623eafb0f6b9bb123ff4a0af89e44567dad809b88", size = 3661950, upload-time = "2026-03-30T08:51:59.867Z" },
{ url = "https://files.pythonhosted.org/packages/9c/d0/533d87629ec823c02c9169ee20228f734c264b209dcdf55268b5a14cde0a/grpcio_tools-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6c129da370c5f85f569be2e545317dda786a60dd51d7deea29b03b0c05f6aac3", size = 3328755, upload-time = "2026-03-30T08:52:02.942Z" },
{ url = "https://files.pythonhosted.org/packages/08/a1/504d7838770c73a9761e8a8ff4869dba1146b44f297ff0ac6641481942d3/grpcio_tools-1.80.0-cp314-cp314-win32.whl", hash = "sha256:25742de5958ae4325249a37e724e7c0e5120f8e302a24a977ebd1737b48a5e97", size = 1019620, upload-time = "2026-03-30T08:52:05.342Z" },
{ url = "https://files.pythonhosted.org/packages/f3/75/8b7cd281c5cdfb4ca2c308f7e9b2799bab2be6e7a9e9212ea5a82e2aecd4/grpcio_tools-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:bbf8eeef78fda1966f732f79c1c802fadd5cfd203d845d2af4d314d18569069c", size = 1194210, upload-time = "2026-03-30T08:52:08.105Z" },
{ url = "https://files.pythonhosted.org/packages/5a/e1/1fcf884902ae7255d8da224cfa638ea88a46d50f62a33d06d35c8960b029/grpcio_tools-1.81.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:9b6ba8a72cfda576508701a7c0bbeebe6f6f9843320d4f12e74efd19ddccd965", size = 2586261, upload-time = "2026-06-11T12:49:21.447Z" },
{ url = "https://files.pythonhosted.org/packages/a2/d7/1815110b2d40ec99dbb0a7e6d7eafd591cd1f1e9bf9d3858cd9cf3ffacbd/grpcio_tools-1.81.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:ac47a9ea1224df8b653072614e6f0207e9fbfe63fdabaa5918a60ca5fc931b88", size = 5817509, upload-time = "2026-06-11T12:49:25.958Z" },
{ url = "https://files.pythonhosted.org/packages/23/e8/af99579842b5a555312fa782f32ce0f99bd35b2b7a1243294b2755468857/grpcio_tools-1.81.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eac4bb645ceff0c147cc720a40ae68f97427eaafb4968e866dd8fcc20d3d4831", size = 2634112, upload-time = "2026-06-11T12:49:27.937Z" },
{ url = "https://files.pythonhosted.org/packages/b5/cd/235ad56ac728c49c17e9218c4daccd5831e6ec7af94236bec0cc66c71c68/grpcio_tools-1.81.1-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:cc410b621dd85193766c12dca2e238696199a27a65d2b31b6f0a4c6c0043ff26", size = 2957950, upload-time = "2026-06-11T12:49:29.619Z" },
{ url = "https://files.pythonhosted.org/packages/77/3e/9103e8b4610597bf89db49eb112091c91bf5d63ddef2a951e11a4be05f2b/grpcio_tools-1.81.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b62d254c214faa3773eac709376ae25cf7abff1a76ba5fc4dbcd7b14fc4e4ae6", size = 2697765, upload-time = "2026-06-11T12:49:31.702Z" },
{ url = "https://files.pythonhosted.org/packages/3c/86/beb2a43fbb93570a2305696083f6736566301d957869f463308ec6839f95/grpcio_tools-1.81.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bd0b68dc76b10b3384b9b6e9f59202b83dcaafd8098eb644759a69316686acf8", size = 3147588, upload-time = "2026-06-11T12:49:33.748Z" },
{ url = "https://files.pythonhosted.org/packages/af/4d/b0182d9948631cd837a372b6625cf59d6e335d4aab0f425d4b7306619074/grpcio_tools-1.81.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a28d231455ab6e3558299f7d831a73c8be8ee6b7ec614ecf39eb50c0ed15767f", size = 3708798, upload-time = "2026-06-11T12:49:35.979Z" },
{ url = "https://files.pythonhosted.org/packages/23/9b/f452a189d399051d85cf82fe2f27a070efaa52512a2c5e3ae6ef1ae99a1f/grpcio_tools-1.81.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:82740248eb6f3b6a38988cb5e64adb7303af9ea5cb4197c8ed08c1fabc767440", size = 3366969, upload-time = "2026-06-11T12:49:37.911Z" },
{ url = "https://files.pythonhosted.org/packages/9b/48/0075cb4f6ae7db280f461de2dbba700b22ae62e351ae13e6e461cd6804de/grpcio_tools-1.81.1-cp310-cp310-win32.whl", hash = "sha256:801d9d8ab5cddf8f8e064225292f0713427011252a07828a6b54e2ed64d534de", size = 1008713, upload-time = "2026-06-11T12:49:39.791Z" },
{ url = "https://files.pythonhosted.org/packages/17/bd/7692bc698259e5645b68720e77e7b176d376f6ae0c9db8b5b750a02f1958/grpcio_tools-1.81.1-cp310-cp310-win_amd64.whl", hash = "sha256:3c8611d6e4e859ac5373422ef27c4b7540cf98c9991c9abc6722613ef72b13aa", size = 1174752, upload-time = "2026-06-11T12:49:41.43Z" },
{ url = "https://files.pythonhosted.org/packages/18/76/14ff87090199a36f914388299a1148d0734a20cea1b0ca8480bae1f373f1/grpcio_tools-1.81.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:8161f398f957a376cae7385ea7c8684f439d460ef702b528912da3bcb31fc515", size = 2586251, upload-time = "2026-06-11T12:49:43.514Z" },
{ url = "https://files.pythonhosted.org/packages/87/a8/d5aa99de9d8b2dd2a8192c1779796eda8b0d0f1dd915422e0a8a61b80391/grpcio_tools-1.81.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:53ef76cc3b0493ff734a5e8c39d5b519e1822236fcccdfe7677c5e1efd767761", size = 5818063, upload-time = "2026-06-11T12:49:45.975Z" },
{ url = "https://files.pythonhosted.org/packages/ba/cb/2e9a6dbc6a514dd3cd264fb3bf9217937453a4d45dbc3ca6ca4ee34ba1a7/grpcio_tools-1.81.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:690e6dcaa8b8a7886ce206ba344e2127211597e1a1ddab73df9f3d80c8f6707e", size = 2634061, upload-time = "2026-06-11T12:49:48.13Z" },
{ url = "https://files.pythonhosted.org/packages/c8/2b/2ccd1a929e6c8ad84a0aa8d66ad9f615b4a8e79d9927373d86aa36b4ba2e/grpcio_tools-1.81.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ad7a997c07bd345e84842e60561e7e2cc090ce6c4e1d2f0407e31b85b40fc49a", size = 2958029, upload-time = "2026-06-11T12:49:50.466Z" },
{ url = "https://files.pythonhosted.org/packages/e7/67/2da8cd312edc348f44f26f82096b25cdb7d2905cd786acc6bf777b169502/grpcio_tools-1.81.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b6bd163ece4535726e5292b845ed80ae9b2cae73ba091c7d6c66033c430e3857", size = 2698031, upload-time = "2026-06-11T12:49:52.292Z" },
{ url = "https://files.pythonhosted.org/packages/d9/ba/ad1680fbdf9317c4f1e54c37c96d1f422370df66ac9adbd175c7cb3531d7/grpcio_tools-1.81.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2baa7e735f35b2a648144c03348a126097b13e101d3c242d5edb6ac91437ccbe", size = 3147541, upload-time = "2026-06-11T12:49:54.43Z" },
{ url = "https://files.pythonhosted.org/packages/57/c1/57cd08eef293d713cb8935295e4f08d8f0013480b2ba3aad1af0271eb7ba/grpcio_tools-1.81.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1d602b410b2b2addc434cace9ce4fe2035974a3078228f98ffa049a5c90acc2f", size = 3708524, upload-time = "2026-06-11T12:49:56.544Z" },
{ url = "https://files.pythonhosted.org/packages/52/31/01ea8ca9c82fe2c79b5b594c3ae427d56699bc106b2d91caca129add8b10/grpcio_tools-1.81.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f8cb64f87c45ccca8234fa47e6b21f09e43801ff11b556deecb461b3b3e9f292", size = 3367022, upload-time = "2026-06-11T12:49:59.608Z" },
{ url = "https://files.pythonhosted.org/packages/7d/35/8140cd175602df3d17215cfb28a7ea55b7a67e2b872be76e1ee4af5c4df9/grpcio_tools-1.81.1-cp311-cp311-win32.whl", hash = "sha256:87b25ca0e27373a4a32a629a4ba976f5764b9887dd50d6fe017d38009a0363e8", size = 1008980, upload-time = "2026-06-11T12:50:01.422Z" },
{ url = "https://files.pythonhosted.org/packages/be/86/1bd29ab3c52457702b96536f1f208ab27695322d855f95c9666dfb713019/grpcio_tools-1.81.1-cp311-cp311-win_amd64.whl", hash = "sha256:204de03b539a4b08772c6553b92bcc112cbc965e0ac22f909f6d133b8ac33a8c", size = 1174840, upload-time = "2026-06-11T12:50:03.408Z" },
{ url = "https://files.pythonhosted.org/packages/3b/8a/824a9ca20bcdce8a568bb8c9f98bfeb7fad62129235e6d2ae7576fd1250a/grpcio_tools-1.81.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:353b1fafcc739c31ed42271052709595b340d34f27c459beeb78a32938305bb5", size = 2585927, upload-time = "2026-06-11T12:50:05.671Z" },
{ url = "https://files.pythonhosted.org/packages/2f/35/e5f9f671378b1b89a896150d3e4fa2c6ec61a5e1e9e5107ce4c140ccc931/grpcio_tools-1.81.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:768f584c2423cbeb6cb6867817a39365b987ff16b8259a3adbc6546b9e303a4e", size = 5815665, upload-time = "2026-06-11T12:50:08.178Z" },
{ url = "https://files.pythonhosted.org/packages/c6/02/631b628e4072e988c669bd8f1b2406ef3c9a4cfcb2625bbf2a308a07b71d/grpcio_tools-1.81.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1680b35a84f4694401819ac4acac42dda6dbc7bb8fc74112fd1a60425a07adf4", size = 2635518, upload-time = "2026-06-11T12:50:10.391Z" },
{ url = "https://files.pythonhosted.org/packages/de/7c/2e3537e3ea3d1c0ddd6766cf6a7c62b487d89fb005713df2781d5f21483a/grpcio_tools-1.81.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f64e665c8ec639278ecf009beb92cbdcc5994f617c1af3d58036e1f70b1423ec", size = 2958252, upload-time = "2026-06-11T12:50:12.677Z" },
{ url = "https://files.pythonhosted.org/packages/35/68/14013cb2942bdac354746b643b4c37dd91906da8dce00f41c616e88bf33d/grpcio_tools-1.81.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba8f1ae82ad199f43448995715445cc623fb20d3882382e4be61f0da8ccb3f0e", size = 2698439, upload-time = "2026-06-11T12:50:15.017Z" },
{ url = "https://files.pythonhosted.org/packages/bd/45/000c14c0338a7ad36054b9f17ea41842deb7841c05c067dd36cc831bc0f4/grpcio_tools-1.81.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b7b6d1e986d5923751bfe2b5cca9c4cb3d5653446e4fa4aacd438033e2dc360a", size = 3152160, upload-time = "2026-06-11T12:50:17.3Z" },
{ url = "https://files.pythonhosted.org/packages/41/97/881930ca3967d2c8a95649bea8ebc991a7cf2331bc96679fd3600450dccc/grpcio_tools-1.81.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7f208c207aca639dcb34648d3826c38d7cf3485118fb2065117e9fc4827406b3", size = 3710468, upload-time = "2026-06-11T12:50:19.479Z" },
{ url = "https://files.pythonhosted.org/packages/c7/b5/67baeba7366162652cdc1dbd962289accde07241bc8f42f6f02b305efcc6/grpcio_tools-1.81.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:724ecb69af63d2f6d4ccea3e6fa0ca110ed9c5824d48c2f887c631bbb03c1c3c", size = 3370797, upload-time = "2026-06-11T12:50:21.501Z" },
{ url = "https://files.pythonhosted.org/packages/f2/5d/34f2dce2125ccb107e32b57f5a9c1257edcc0793b0d2fef1e8b13a6bac3c/grpcio_tools-1.81.1-cp312-cp312-win32.whl", hash = "sha256:895a6782cec86beac71ccebb4b9848259c6f04a3028b8e42fa8d40cfe5146593", size = 1008453, upload-time = "2026-06-11T12:50:23.358Z" },
{ url = "https://files.pythonhosted.org/packages/8a/be/09da8256ec8d2a5ce8a1acc51cbbc4ca52a462d78ed3412778440a56502e/grpcio_tools-1.81.1-cp312-cp312-win_amd64.whl", hash = "sha256:0265fd1386b7458302f79542558345880d484f8fa92ae196c0c0268242c5f23a", size = 1174857, upload-time = "2026-06-11T12:50:25.685Z" },
{ url = "https://files.pythonhosted.org/packages/76/90/5faa8b26e03495e5117f93bef8293cbada4af136362745dad7d1813ef0b0/grpcio_tools-1.81.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:3d604b4fd114b79ebb9f865bf3e04fd3ae93c704e1fad96f7fd03b0865c263b7", size = 2586071, upload-time = "2026-06-11T12:50:28.4Z" },
{ url = "https://files.pythonhosted.org/packages/e8/9a/85dc589fa6ae2439451eaa81a1578de31e29c676980d38bef7549b8a1f45/grpcio_tools-1.81.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:3389e705460efa3f3758141ba5520e6743b131c9576197c944fb9cbe49048126", size = 5813299, upload-time = "2026-06-11T12:50:31.295Z" },
{ url = "https://files.pythonhosted.org/packages/77/fd/c53994e58a837e6eefe48f53eb3492afc04f2b8af255df4adb37d14378f8/grpcio_tools-1.81.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8a17d8ceeb6a855fadf39f5171c80a382d97c4db98d5943eca553497fdebf84b", size = 2634668, upload-time = "2026-06-11T12:50:33.938Z" },
{ url = "https://files.pythonhosted.org/packages/34/32/de988e86688686a2117e7ce6ce9eff4f638c929bb55b0afe60d6fbd2e45c/grpcio_tools-1.81.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:43baf71dc60fd653062da2e95e95c73b35dd130be8f9fa3d544c3af3f808a290", size = 2957930, upload-time = "2026-06-11T12:50:36.726Z" },
{ url = "https://files.pythonhosted.org/packages/72/97/3f18a0ea32b5f809d21961dbd0bc382b589a4c3d501e3d67c345d5456ed3/grpcio_tools-1.81.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:136e90906af0df51ad929713244ba812d0dbb1844b4f467d5d86bdb054698f90", size = 2697760, upload-time = "2026-06-11T12:50:39.108Z" },
{ url = "https://files.pythonhosted.org/packages/49/c0/dbf5cbc877290ff7504a59959a8af4fdcfdaa1e84237948405ccf1aa82a6/grpcio_tools-1.81.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd6c3bf3ea6a61eb58c54368d72ada591f2a270f3a31a32e8536e773337e76d9", size = 3151456, upload-time = "2026-06-11T12:50:41.983Z" },
{ url = "https://files.pythonhosted.org/packages/de/ea/16fe2dc83140a59e5c0a0b9dc2693dd36bfaa6bd835724b4ec66a68eab7b/grpcio_tools-1.81.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c306c307f8f74cddc4056fdbb6f1da55de087a21120efbd02bd915daa5a52fd", size = 3710469, upload-time = "2026-06-11T12:50:44.596Z" },
{ url = "https://files.pythonhosted.org/packages/22/7d/df987d7d81e7ad2f7516d9e9d56ff29c54dbc6d8587e425688dca9a28e49/grpcio_tools-1.81.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bdbdc927be2e0ea13c32564a72ee31d712a716fb6f8c0d53d37a77d8277c272c", size = 3370488, upload-time = "2026-06-11T12:50:47.199Z" },
{ url = "https://files.pythonhosted.org/packages/ba/c5/5a63444d694ea47bf670138208f71830cc1759c402c8818092b28ab2dc5f/grpcio_tools-1.81.1-cp313-cp313-win32.whl", hash = "sha256:9d383724bcd67244b6def9e9164c640ee9380c0b7534ee7545a6fb0022a59afe", size = 1008229, upload-time = "2026-06-11T12:50:49.527Z" },
{ url = "https://files.pythonhosted.org/packages/00/75/3945e26d5c94ae6ed9be5caef73d4d66c47dc8cfdd7b4995efaf942754e0/grpcio_tools-1.81.1-cp313-cp313-win_amd64.whl", hash = "sha256:f3eb15849979ca7bb864ce81a74d68b0f225a7f111ed3fe212bfc08cf9812b10", size = 1174523, upload-time = "2026-06-11T12:50:51.755Z" },
{ url = "https://files.pythonhosted.org/packages/0d/08/e581ad42ae517a61172285047e4d710e2ac75f2f1915f7c91f284254e6d5/grpcio_tools-1.81.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:7d168ea26390717d0462c0d0408331dc98a60fc7f7e6118afac9b73f5a66d87c", size = 2585944, upload-time = "2026-06-11T12:50:54.528Z" },
{ url = "https://files.pythonhosted.org/packages/78/c8/200d90ebad685af7eea5ff7e0360c504dd01ec053fe0f1f9c4abe3ea2d5a/grpcio_tools-1.81.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:43c528655b226375013036692d8db4cd59060c1f41dd62c77f4d17b69f6ce828", size = 5813492, upload-time = "2026-06-11T12:50:57.291Z" },
{ url = "https://files.pythonhosted.org/packages/2d/c0/60da2a1af37aa8eb47308cec24d9f7709a8976fdec3a53fd35b56b358326/grpcio_tools-1.81.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a9c6fcc68c9d5a208967bfe4fd3224d3c3be9a950c3e827e8f4b17e15c2dc555", size = 2634991, upload-time = "2026-06-11T12:50:59.767Z" },
{ url = "https://files.pythonhosted.org/packages/0c/7f/dede28b579ae9bf9079ba1aa913e8088d1dc0cdbe21c85caa22f0790cad2/grpcio_tools-1.81.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:a987c85dcbe1b32066d7acd46266d1a428aecbd629331bf5b853e74c835bf876", size = 2957913, upload-time = "2026-06-11T12:51:02.31Z" },
{ url = "https://files.pythonhosted.org/packages/4c/38/4de2118adb58ec7ffba65ec623b5836db769665c192517cbf187db3f6145/grpcio_tools-1.81.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a882382507bb5ec6d7edc9648053dfd3bc8f9285cde56a6fa9b9a83b4bd07f1c", size = 2697709, upload-time = "2026-06-11T12:51:05.016Z" },
{ url = "https://files.pythonhosted.org/packages/6b/e1/762ced51059e4f694fd337ecae491581d42a4e61dcb0415d8c5c60e6ddcb/grpcio_tools-1.81.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7746e508d4239a02f7e93638be5bc0ebb0120ddb796f7506aaae9d47a4599d97", size = 3151884, upload-time = "2026-06-11T12:51:07.593Z" },
{ url = "https://files.pythonhosted.org/packages/19/d8/9823090dc801e7229944874e7429c3b98e741ac778d8dc373f60240e1c43/grpcio_tools-1.81.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fc3d2a41a7a4467fa03b391394fffada9291fe8feebc8679b526f6bc36942b25", size = 3710404, upload-time = "2026-06-11T12:51:10.172Z" },
{ url = "https://files.pythonhosted.org/packages/64/4e/4eae98d02148cb6f9f452f09942afba407afa6851e6c1fddc5ae9ec0b4ed/grpcio_tools-1.81.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:21bb3ba90e6d8df1ff663d4ee39a4e5b25a64e8ed4902476ca9ded0954d3917a", size = 3370525, upload-time = "2026-06-11T12:51:12.678Z" },
{ url = "https://files.pythonhosted.org/packages/e0/3e/2206e597a128da6a03a6106d2eaf2c3e72c7d80843d4be933e3a3d10d02a/grpcio_tools-1.81.1-cp314-cp314-win32.whl", hash = "sha256:3dca56016d90a710c4d9861bae793dc089c1430a90c79ce672e948ddb65fa539", size = 1030582, upload-time = "2026-06-11T12:51:14.906Z" },
{ url = "https://files.pythonhosted.org/packages/cf/f2/bbeef86c687225b7bbc7c0acdfbd25c8bcaa3f5b1c941db053e5c3d9e859/grpcio_tools-1.81.1-cp314-cp314-win_amd64.whl", hash = "sha256:cb08172b7b629e75cb33866928d319a3196540a725eaab628ba721007140f1af", size = 1207490, upload-time = "2026-06-11T12:51:17.598Z" },
]
[[package]]
@@ -1043,9 +1060,10 @@ wheels = [
[[package]]
name = "langgraph-api"
version = "0.10.0"
version = "0.17.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "cloudpickle" },
{ name = "cryptography" },
{ name = "grpcio" },
@@ -1063,8 +1081,10 @@ dependencies = [
{ name = "langsmith", extra = ["otel"] },
{ name = "opentelemetry-api" },
{ name = "opentelemetry-exporter-otlp-proto-http" },
{ name = "opentelemetry-exporter-prometheus" },
{ name = "opentelemetry-sdk" },
{ name = "orjson" },
{ name = "prometheus-client" },
{ name = "protobuf" },
{ name = "pyjwt" },
{ name = "sse-starlette" },
@@ -1079,9 +1099,9 @@ dependencies = [
{ name = "websockets" },
{ name = "zstandard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7d/17/a23ecdad3de7e48209bc8d534312ba83e332126ec60da9e6487461b02597/langgraph_api-0.10.0.tar.gz", hash = "sha256:f594a160857e2cd7fb2352bba585111177a4a394625ad0cf5898814754fb7bc1", size = 713903, upload-time = "2026-06-11T14:36:50.469Z" }
sdist = { url = "https://files.pythonhosted.org/packages/33/c2/3a5baab0dd010dffdbf92fecd34f0fe25d4fac42281469ed8b494e4b3511/langgraph_api-0.17.1.tar.gz", hash = "sha256:08bf7df9cc2bd7e45fbf2f1cb97267264f149794032010e55dafb50d64cbded7", size = 886040, upload-time = "2026-11-03T21:27:09.883Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/38/6b0cdcaaed9b3105102b452a5a70fb08894a9511458cf00c061c4725347c/langgraph_api-0.10.0-py3-none-any.whl", hash = "sha256:f4b545bf1936c4e90bed1cc07a6852b1be37e1667007ab4736d800cc18e9f9b3", size = 577475, upload-time = "2026-06-11T14:36:48.838Z" },
{ url = "https://files.pythonhosted.org/packages/89/84/a61803f1a2dd895718abbe4dbb076bb19ff04238c36d217697f5a485188e/langgraph_api-0.17.1-py3-none-any.whl", hash = "sha256:e070f5675713adf3322ba993992e618f5ec587ec0aec80441c563dfc404d14f2", size = 663034, upload-time = "2026-11-03T21:27:07.552Z" },
]
[[package]]
@@ -1193,7 +1213,7 @@ wheels = [
[[package]]
name = "langgraph-runtime-inmem"
version = "0.30.0"
version = "0.37.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "blockbuster" },
@@ -1204,9 +1224,9 @@ dependencies = [
{ name = "starlette" },
{ name = "structlog" },
]
sdist = { url = "https://files.pythonhosted.org/packages/98/19/5d330e22e3ee2569742d3577b4ddfbb6b8e383f2edd9724b4a4b73735717/langgraph_runtime_inmem-0.30.0.tar.gz", hash = "sha256:31954e1de6cd43c284b425243d4faeae818f68b3daca4dbe4313e651b6f5e11d", size = 129940, upload-time = "2026-06-11T14:22:46.985Z" }
sdist = { url = "https://files.pythonhosted.org/packages/a3/03/96d55a65eedf319da923a93e99d7a879364e0cd5b1159a15b7a58f2eaf9b/langgraph_runtime_inmem-0.37.1.tar.gz", hash = "sha256:c52a5105d0559994b3c9218a67eee3e746b65eb7b40d731ebb7d87247683395f", size = 142119, upload-time = "2026-11-03T21:20:31.19Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/df/cfb15c70fa4dcab9ea48f8fda9f4ed87d1767eea6e4d2901dc704da297ba/langgraph_runtime_inmem-0.30.0-py3-none-any.whl", hash = "sha256:08a36b83b5e32fe6e30cb25062a1e671a42e2a0206e4043cc95f41198dcbbb12", size = 51762, upload-time = "2026-06-11T14:22:46.012Z" },
{ url = "https://files.pythonhosted.org/packages/8c/bc/df823ed5cdf54cac16a85fb7f0bc4dde9a77a736b0ce1d38e9da97e620af/langgraph_runtime_inmem-0.37.1-py3-none-any.whl", hash = "sha256:b6504d50970e962947bd43a1dd2666b4f1f069d8fa08da6b7364b3f5a23b35a9", size = 62099, upload-time = "2026-11-03T21:20:29.734Z" },
]
[[package]]
@@ -1416,6 +1436,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" },
]
[[package]]
name = "opentelemetry-exporter-prometheus"
version = "0.66b1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-sdk" },
{ name = "prometheus-client" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ed/58/e552853748c3a1478d3f0db31bb4e3bef2e45385f64d658148693183410e/opentelemetry_exporter_prometheus-0.66b1.tar.gz", hash = "sha256:1c702a0cc7a1b8c5e1f3f246aeb4273dbd707179af30dbb66182a75e16a06ed8", size = 16987, upload-time = "2026-10-06T17:33:06.358Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/8a/5e7262d970586a8d69dd159bebc68fbee7457088883b179599a562881a6f/opentelemetry_exporter_prometheus-0.66b1-py3-none-any.whl", hash = "sha256:a938e6af7295d5bacf82da9ca845cab9a9cb1c5f565abe39f54ef8bf511c05d9", size = 13222, upload-time = "2026-10-06T17:32:44.94Z" },
]
[[package]]
name = "opentelemetry-proto"
version = "1.45.1"
@@ -1640,6 +1674,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
[[package]]
name = "prometheus-client"
version = "0.26.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/52/73/f1334c29c2af4cd9dba6c7817e61b611bd0215e2eb5565c6064a4de18802/prometheus_client-0.26.0.tar.gz", hash = "sha256:04a91bcf94e2cf74a44a1a874d651a2e853ed354b6e822f3b7487751465d5c2b", size = 92910, upload-time = "2026-07-24T19:36:41.893Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/eb/a3/b69efbf4143b5b9859b977770bbbabcc2796b702fa69dc40271e45cd5a56/prometheus_client-0.26.0-py3-none-any.whl", hash = "sha256:fa93d06737aa02bacd05794768508bb97d2fbee28cb3bca04eaae92f0ca953d6", size = 64494, upload-time = "2026-07-24T19:36:40.854Z" },
]
[[package]]
name = "protobuf"
version = "6.33.6"
@@ -1905,7 +1948,8 @@ name = "python-discovery"
version = "1.6.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "filelock" },
{ name = "filelock", version = "4.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "filelock", version = "4.2.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0c/57/250bd238b966cece44328235eb85290045d059265fdaf7527a3a958123db/python_discovery-1.6.1.tar.gz", hash = "sha256:cf87d3627dfb4412437fdd5b13eae402607722998d21567993aedbc59b23c15e", size = 84338, upload-time = "2026-09-18T01:31:53.971Z" }
wheels = [
@@ -2328,31 +2372,62 @@ wheels = [
[[package]]
name = "uuid-utils"
version = "0.14.1"
version = "0.19.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7b/d1/38a573f0c631c062cf42fa1f5d021d4dd3c31fb23e4376e4b56b0c9fbbed/uuid_utils-0.14.1.tar.gz", hash = "sha256:9bfc95f64af80ccf129c604fb6b8ca66c6f256451e32bc4570f760e4309c9b69", size = 22195, upload-time = "2026-02-20T22:50:38.833Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f1/0f/cd9b3e495e514441e9af3a42c5c63436aee620f61412cafcce67e259228b/uuid_utils-0.19.0.tar.gz", hash = "sha256:4b2996bb9d12da9b62f6f4c64a066b26926fa75dbe47b4f8e309e24e187abf1d", size = 43343, upload-time = "2026-10-26T10:55:23.821Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/43/b7/add4363039a34506a58457d96d4aa2126061df3a143eb4d042aedd6a2e76/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:93a3b5dc798a54a1feb693f2d1cb4cf08258c32ff05ae4929b5f0a2ca624a4f0", size = 604679, upload-time = "2026-02-20T22:50:27.469Z" },
{ url = "https://files.pythonhosted.org/packages/dd/84/d1d0bef50d9e66d31b2019997c741b42274d53dde2e001b7a83e9511c339/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ccd65a4b8e83af23eae5e56d88034b2fe7264f465d3e830845f10d1591b81741", size = 309346, upload-time = "2026-02-20T22:50:31.857Z" },
{ url = "https://files.pythonhosted.org/packages/ef/ed/b6d6fd52a6636d7c3eddf97d68da50910bf17cd5ac221992506fb56cf12e/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b56b0cacd81583834820588378e432b0696186683b813058b707aedc1e16c4b1", size = 344714, upload-time = "2026-02-20T22:50:42.642Z" },
{ url = "https://files.pythonhosted.org/packages/a8/a7/a19a1719fb626fe0b31882db36056d44fe904dc0cf15b06fdf56b2679cf7/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb3cf14de789097320a3c56bfdfdd51b1225d11d67298afbedee7e84e3837c96", size = 350914, upload-time = "2026-02-20T22:50:36.487Z" },
{ url = "https://files.pythonhosted.org/packages/1d/fc/f6690e667fdc3bb1a73f57951f97497771c56fe23e3d302d7404be394d4f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e0854a90d67f4b0cc6e54773deb8be618f4c9bad98d3326f081423b5d14fae", size = 482609, upload-time = "2026-02-20T22:50:37.511Z" },
{ url = "https://files.pythonhosted.org/packages/54/6e/dcd3fa031320921a12ec7b4672dea3bd1dd90ddffa363a91831ba834d559/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6743ba194de3910b5feb1a62590cd2587e33a73ab6af8a01b642ceb5055862", size = 345699, upload-time = "2026-02-20T22:50:46.87Z" },
{ url = "https://files.pythonhosted.org/packages/04/28/e5220204b58b44ac0047226a9d016a113fde039280cc8732d9e6da43b39f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:043fb58fde6cf1620a6c066382f04f87a8e74feb0f95a585e4ed46f5d44af57b", size = 372205, upload-time = "2026-02-20T22:50:28.438Z" },
{ url = "https://files.pythonhosted.org/packages/c7/d9/3d2eb98af94b8dfffc82b6a33b4dfc87b0a5de2c68a28f6dde0db1f8681b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c915d53f22945e55fe0d3d3b0b87fd965a57f5fd15666fd92d6593a73b1dd297", size = 521836, upload-time = "2026-02-20T22:50:23.057Z" },
{ url = "https://files.pythonhosted.org/packages/a8/15/0eb106cc6fe182f7577bc0ab6e2f0a40be247f35c5e297dbf7bbc460bd02/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0972488e3f9b449e83f006ead5a0e0a33ad4a13e4462e865b7c286ab7d7566a3", size = 625260, upload-time = "2026-02-20T22:50:25.949Z" },
{ url = "https://files.pythonhosted.org/packages/3c/17/f539507091334b109e7496830af2f093d9fc8082411eafd3ece58af1f8ba/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1c238812ae0c8ffe77d8d447a32c6dfd058ea4631246b08b5a71df586ff08531", size = 587824, upload-time = "2026-02-20T22:50:35.225Z" },
{ url = "https://files.pythonhosted.org/packages/2e/c2/d37a7b2e41f153519367d4db01f0526e0d4b06f1a4a87f1c5dfca5d70a8b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:bec8f8ef627af86abf8298e7ec50926627e29b34fa907fcfbedb45aaa72bca43", size = 551407, upload-time = "2026-02-20T22:50:44.915Z" },
{ url = "https://files.pythonhosted.org/packages/65/36/2d24b2cbe78547c6532da33fb8613debd3126eccc33a6374ab788f5e46e9/uuid_utils-0.14.1-cp39-abi3-win32.whl", hash = "sha256:b54d6aa6252d96bac1fdbc80d26ba71bad9f220b2724d692ad2f2310c22ef523", size = 183476, upload-time = "2026-02-20T22:50:32.745Z" },
{ url = "https://files.pythonhosted.org/packages/83/92/2d7e90df8b1a69ec4cff33243ce02b7a62f926ef9e2f0eca5a026889cd73/uuid_utils-0.14.1-cp39-abi3-win_amd64.whl", hash = "sha256:fc27638c2ce267a0ce3e06828aff786f91367f093c80625ee21dad0208e0f5ba", size = 187147, upload-time = "2026-02-20T22:50:45.807Z" },
{ url = "https://files.pythonhosted.org/packages/d9/26/529f4beee17e5248e37e0bc17a2761d34c0fa3b1e5729c88adb2065bae6e/uuid_utils-0.14.1-cp39-abi3-win_arm64.whl", hash = "sha256:b04cb49b42afbc4ff8dbc60cf054930afc479d6f4dd7f1ec3bbe5dbfdde06b7a", size = 188132, upload-time = "2026-02-20T22:50:41.718Z" },
{ url = "https://files.pythonhosted.org/packages/91/f9/6c64bdbf71f58ccde7919e00491812556f446a5291573af92c49a5e9aaef/uuid_utils-0.14.1-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b197cd5424cf89fb019ca7f53641d05bfe34b1879614bed111c9c313b5574cd8", size = 591617, upload-time = "2026-02-20T22:50:24.532Z" },
{ url = "https://files.pythonhosted.org/packages/d0/f0/758c3b0fb0c4871c7704fef26a5bc861de4f8a68e4831669883bebe07b0f/uuid_utils-0.14.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:12c65020ba6cb6abe1d57fcbfc2d0ea0506c67049ee031714057f5caf0f9bc9c", size = 303702, upload-time = "2026-02-20T22:50:40.687Z" },
{ url = "https://files.pythonhosted.org/packages/85/89/d91862b544c695cd58855efe3201f83894ed82fffe34500774238ab8eba7/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b5d2ad28063d422ccc2c28d46471d47b61a58de885d35113a8f18cb547e25bf", size = 337678, upload-time = "2026-02-20T22:50:39.768Z" },
{ url = "https://files.pythonhosted.org/packages/ee/6b/cf342ba8a898f1de024be0243fac67c025cad530c79ea7f89c4ce718891a/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da2234387b45fde40b0fedfee64a0ba591caeea9c48c7698ab6e2d85c7991533", size = 343711, upload-time = "2026-02-20T22:50:43.965Z" },
{ url = "https://files.pythonhosted.org/packages/b3/20/049418d094d396dfa6606b30af925cc68a6670c3b9103b23e6990f84b589/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50fffc2827348c1e48972eed3d1c698959e63f9d030aa5dd82ba451113158a62", size = 476731, upload-time = "2026-02-20T22:50:30.589Z" },
{ url = "https://files.pythonhosted.org/packages/77/a1/0857f64d53a90321e6a46a3d4cc394f50e1366132dcd2ae147f9326ca98b/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1dbe718765f70f5b7f9b7f66b6a937802941b1cc56bcf642ce0274169741e01", size = 338902, upload-time = "2026-02-20T22:50:33.927Z" },
{ url = "https://files.pythonhosted.org/packages/ed/d0/5bf7cbf1ac138c92b9ac21066d18faf4d7e7f651047b700eb192ca4b9fdb/uuid_utils-0.14.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:258186964039a8e36db10810c1ece879d229b01331e09e9030bc5dcabe231bd2", size = 364700, upload-time = "2026-02-20T22:50:21.732Z" },
{ url = "https://files.pythonhosted.org/packages/fa/ce/78f9d79c820b8afc9b0498b0a5f5ef87ea970a19cc382127087f36cd4839/uuid_utils-0.19.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:84929d8c594381c6f1444afa2a25cce9ff7edac99d35e071ebc48275bae83730", size = 574701, upload-time = "2026-10-26T10:54:06.751Z" },
{ url = "https://files.pythonhosted.org/packages/fc/63/fe81dda73d651fb6bf8b0e21c4ea34209fc535ca109634c0d93f14ac21dc/uuid_utils-0.19.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:a416c78167b750186095a0ea950d95b8b4e157e3172fc6ccdc317b6ca2896580", size = 294226, upload-time = "2026-10-26T10:54:08.422Z" },
{ url = "https://files.pythonhosted.org/packages/81/6c/118c7e11fb579b5631212bfbbb31542a1778936dfb7c55591aa9b52c7c99/uuid_utils-0.19.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82573c0e5090732c0ae2e18626e06cb35fe7cfcf6fe9534d4c1638af41d25d36", size = 327869, upload-time = "2026-10-26T10:54:10.016Z" },
{ url = "https://files.pythonhosted.org/packages/a3/76/80e9bad735cbf92f127183ee5c44ee0d83604e56197bddcaaaed2c66f7e6/uuid_utils-0.19.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c78d527fddf5495e80032caeca2f140731ef394298ef4cbecaaf51aa2c257e1e", size = 334737, upload-time = "2026-10-26T10:54:11.434Z" },
{ url = "https://files.pythonhosted.org/packages/9e/7b/c8eb8e45569e8681f20e381859248d18635d823b8aedc70de8a0bd5faca6/uuid_utils-0.19.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c33b5c62cf82d509945c770201b1656448c2560e12931d1a8d698a683e9125d", size = 440438, upload-time = "2026-10-26T10:54:12.864Z" },
{ url = "https://files.pythonhosted.org/packages/be/45/8238eac588b668e5c500cb187fd74a9f6d984c09f3f3f2fa4161d3c07eb7/uuid_utils-0.19.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8060530a8fa27f11947489c79bc36e6ebb465b2d714945364dc397283309417", size = 330342, upload-time = "2026-10-26T10:54:14.309Z" },
{ url = "https://files.pythonhosted.org/packages/0e/45/8215129786e67842d479480a44066cac8a7086f345ac9707f629ed777cfe/uuid_utils-0.19.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:96429c78eab28ac71b2121fb47c66783bfe5d06632b5bd5200004707fdc0faad", size = 354275, upload-time = "2026-10-26T10:54:15.753Z" },
{ url = "https://files.pythonhosted.org/packages/7f/bc/a673bfd116eb26419dd82bffa9bc2bc1e915af02bf68ac8c09598dc678ec/uuid_utils-0.19.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80e0b37b3ddf60c50c51b869e1b02df61415941aa68bc903f61562fd8df04b18", size = 504446, upload-time = "2026-10-26T10:54:17.45Z" },
{ url = "https://files.pythonhosted.org/packages/af/ff/61867210f202c603d4a5ca1c5b3c4c76e8d8724eeebd7cf1307d18b63a9a/uuid_utils-0.19.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f629f2a4f95e46d99ee673536ab9706899696cdc6686e93314ea756d3b394ce4", size = 609016, upload-time = "2026-10-26T10:54:18.914Z" },
{ url = "https://files.pythonhosted.org/packages/42/81/6fb8a2ee8010a6a0c5cb29f5efed2604717613cc9888f9e5c999dc36b84b/uuid_utils-0.19.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:ec330297c09e40b82d613326b57bb381d68535e8a341fa2e501b46791cbdfeec", size = 570293, upload-time = "2026-10-26T10:54:20.328Z" },
{ url = "https://files.pythonhosted.org/packages/0f/2f/e1c749b65c584ebd8bc5379cf18e26c67cdff8f2bd9f8f77111a47083408/uuid_utils-0.19.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:313de97d59dbbb09f4598cece2c799cc233d8673bb075ad7eff442cd485623c2", size = 535321, upload-time = "2026-10-26T10:54:21.735Z" },
{ url = "https://files.pythonhosted.org/packages/87/ba/f423f68fd784124e68ab1b1afe27ab4591ffd5363b39eccfcbaf8f3b0ff3/uuid_utils-0.19.0-cp310-abi3-pyemscripten_2025_0_wasm32.whl", hash = "sha256:5c47c7fc35e91a801996477f0d746d506a2e5b47a13accbf41dc29844786c40c", size = 103141, upload-time = "2026-10-26T10:54:23.192Z" },
{ url = "https://files.pythonhosted.org/packages/f1/9a/7a0a0dad166db9f4437f3d259e47ce74d552ccc50d8aa63d05b2510e3096/uuid_utils-0.19.0-cp310-abi3-pyemscripten_2026_0_wasm32.whl", hash = "sha256:23868cdc4dfa71021fec6cf43fbb83797345362ac1c866b50dbf17f479587413", size = 103069, upload-time = "2026-10-26T10:54:24.709Z" },
{ url = "https://files.pythonhosted.org/packages/e1/3c/c5fef96cea027de66a035b4173951fc29c2e83ed49a5d5b3fa7b632f95b5/uuid_utils-0.19.0-cp310-abi3-win32.whl", hash = "sha256:0cff166cf6348e9a705740e6575ce7bc727554a71a6add60b9ceceaaf169002a", size = 175479, upload-time = "2026-10-26T10:54:25.949Z" },
{ url = "https://files.pythonhosted.org/packages/3f/16/edfb8edc8a4624cfaf1387a57bf37cd53e5d7f213ebeb8dd6fbd2f6b2c64/uuid_utils-0.19.0-cp310-abi3-win_amd64.whl", hash = "sha256:b763ae8f23334ccd95d0b81fb19ee418f38f9e80ebfb521f313a9745280bc07c", size = 182891, upload-time = "2026-10-26T10:54:27.527Z" },
{ url = "https://files.pythonhosted.org/packages/63/17/258e5dd380bc594731b2b705082360fb4cef1e5cbaab46ef307439a6f415/uuid_utils-0.19.0-cp310-abi3-win_arm64.whl", hash = "sha256:b244ea25bfe1b34e40fabf8f079d59a9f153b22bd4095842065bb0a9dfaa477b", size = 180849, upload-time = "2026-10-26T10:54:28.86Z" },
{ url = "https://files.pythonhosted.org/packages/cb/64/b5554f686d5395db970bc07944b9c59c26ab0e192a27d7c8b555e5fc6332/uuid_utils-0.19.0-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:9adf8d69574f0018a25aed0cc194bffacc05fb91b922fdd8e794109c45ad80ed", size = 565592, upload-time = "2026-10-26T10:54:30.242Z" },
{ url = "https://files.pythonhosted.org/packages/4a/a3/d8d0b7bf4892c41dbbb0e81a356d20aad781cc28d3029669e167eb72d9a1/uuid_utils-0.19.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ac37f30acc7fe01266c73f19a686d0ea1fbd22495d81205cc3b572e2fb2293a6", size = 292083, upload-time = "2026-10-26T10:54:32.053Z" },
{ url = "https://files.pythonhosted.org/packages/fa/c1/770ccb569b4c15ee81c36bc40e74f03c5e0a2ee71ecc534d3fe7cfa5696d/uuid_utils-0.19.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afc1dc05c83163ec58304350ee61d20f1f3285d0e8fb873e4d85a97988c284ad", size = 323275, upload-time = "2026-10-26T10:54:33.896Z" },
{ url = "https://files.pythonhosted.org/packages/c7/12/798e09469358bf59848534291aa62d16cc6fbd66767f469c5d8278d0bc4d/uuid_utils-0.19.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16e68b1a4b81301fa7ba52d500947094226d8b0c1521695746ec57ff0779ff6f", size = 328681, upload-time = "2026-10-26T10:54:35.506Z" },
{ url = "https://files.pythonhosted.org/packages/42/16/71df642a7969381747de36d4e365e59683cdaa2c425da0530e9383e4de86/uuid_utils-0.19.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:833edd2b38ef08a324e9f3aed4269d2ab955d4cb66417422eeb3af28a6237427", size = 434504, upload-time = "2026-10-26T10:54:36.96Z" },
{ url = "https://files.pythonhosted.org/packages/c1/18/35c86d901bbd6d3f088217f37a6ff01aae4ce8f8cdb3f9ddb5220887a122/uuid_utils-0.19.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21356ba99b82c813c1945054a09ff7ebc1bf55b858b41a6ae084e6ed2c8bb9a9", size = 325810, upload-time = "2026-10-26T10:54:38.492Z" },
{ url = "https://files.pythonhosted.org/packages/fc/51/25427eee1031946d5abe0d8985123f6fd4a1931c8a5a8660b1c76368ca12/uuid_utils-0.19.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2477161656105e787a5f986adb63191b61f129c9d7399b2bce406f87a1407b4d", size = 346300, upload-time = "2026-10-26T10:54:39.864Z" },
{ url = "https://files.pythonhosted.org/packages/26/4b/edbb0ebdc948549a0250363f441883c31fef42fb4e4bb607b9a083efaeba/uuid_utils-0.19.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ddf61d8443993d683c8d6fcd05cbb711c2655042952850fc0cf25af969e3a213", size = 501365, upload-time = "2026-10-26T10:54:41.268Z" },
{ url = "https://files.pythonhosted.org/packages/68/e4/f5a788fae45e315d8d04d5889a0b0809543e27c8d08b9fe10b09d0766b9b/uuid_utils-0.19.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cb9ead0aeae67ad2de3f660f2e978ece77e14ad3f2a7082b768e2c75866664a2", size = 604290, upload-time = "2026-10-26T10:54:42.686Z" },
{ url = "https://files.pythonhosted.org/packages/a7/90/3800a0b2083d6c156a4d686e75f26c055a48fcf315b9310ba211b7e3659c/uuid_utils-0.19.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4e5155902b7779ae7b692ac69e6031f41481d62fa85a530d3725c270799940a0", size = 563566, upload-time = "2026-10-26T10:54:44.068Z" },
{ url = "https://files.pythonhosted.org/packages/7f/37/1b58a17a041bd560a1d652afccd57ecd5d0e0c4d6280deded663b783de43/uuid_utils-0.19.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd3bcc556e249bb70b1c7fd78e659a1ebfb5572e7ff2091d096aecbac62edb1c", size = 531143, upload-time = "2026-10-26T10:54:45.599Z" },
{ url = "https://files.pythonhosted.org/packages/66/ac/f6286bcfad2f321be2acd04961934a58e4a6c15ba78070a13d193cbbb9f0/uuid_utils-0.19.0-cp314-cp314t-win32.whl", hash = "sha256:9b9064d96c6fe5e43ff4486722c5c891083a7fa91ee36311286cfd833b4b5562", size = 172229, upload-time = "2026-10-26T10:54:46.925Z" },
{ url = "https://files.pythonhosted.org/packages/87/45/0df876abf12875ef82bcd8f034c0c7adc1507dd721a5942232120b6267e8/uuid_utils-0.19.0-cp314-cp314t-win_amd64.whl", hash = "sha256:eeaf6b77ed2ec9815988e10d043d1533a1ae8c6535e7fb5479fd7e2e6ad25dff", size = 178436, upload-time = "2026-10-26T10:54:48.249Z" },
{ url = "https://files.pythonhosted.org/packages/d2/ae/c9b4aaa8ee95581b444a7be681dc0ceb57a465b50b28b2f2517f5fec1c51/uuid_utils-0.19.0-cp314-cp314t-win_arm64.whl", hash = "sha256:ebb011b9010ddbd8acd4650896e87db2715f2cfc70fb194c03ebcbf4150d3844", size = 175725, upload-time = "2026-10-26T10:54:49.562Z" },
{ url = "https://files.pythonhosted.org/packages/41/6e/3a3fdaa9689b687f105ba885e61f6b2e8336ec4d209fd51d41923ad83479/uuid_utils-0.19.0-cp315-cp315t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8c76f01a815a0ca8b698202f2f5e40098a4b3cd56305e1fc9ed3d959ac718ff0", size = 565325, upload-time = "2026-10-26T10:54:50.924Z" },
{ url = "https://files.pythonhosted.org/packages/5e/1a/23dacf86dfe52f4606694501a6072379156025a3c6988e16f477f627249c/uuid_utils-0.19.0-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:f3828e1fc0831a9d539e702992e9fcfeb400e80f737467b8e557921cdc8ae067", size = 291930, upload-time = "2026-10-26T10:54:52.529Z" },
{ url = "https://files.pythonhosted.org/packages/4e/f2/adf223ded569b0d75e8ac10db14be8fc253da900a63e678436bc3ff3b72a/uuid_utils-0.19.0-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25ddec2775c2f500a36029ab93af09f39d5c417786df02a464464e030152014", size = 322729, upload-time = "2026-10-26T10:54:54.01Z" },
{ url = "https://files.pythonhosted.org/packages/88/df/0f78e7f22c778d2e48ef092a3a1c44d5b8b487b7e178e1044e7a39195b17/uuid_utils-0.19.0-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1fc412a5ef2f1e6f96d132a6656af59bae2dba9fd04f8df2a2f6eb913fe6180a", size = 328838, upload-time = "2026-10-26T10:54:55.473Z" },
{ url = "https://files.pythonhosted.org/packages/b3/96/9a9d6f7bef37b3545203eaf643e7f4d574f4ea3a6364af8865c071847df5/uuid_utils-0.19.0-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94cbef978d789b18014c477f51c8311800803cd2a0c7e1de6d2af85377210053", size = 433992, upload-time = "2026-10-26T10:54:56.911Z" },
{ url = "https://files.pythonhosted.org/packages/07/92/62f67dc40a036ab890ac3f55eaab610999c713654bb3d0b56d5676ce3eaa/uuid_utils-0.19.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4b012d2a2c5516e11ded4fb70cdd730f36e2d40b45018b9a40c6605dc5c0666", size = 326138, upload-time = "2026-10-26T10:54:58.533Z" },
{ url = "https://files.pythonhosted.org/packages/c8/b7/740525de1aa1a150eb7a80a667c997d54284f407b7f3b38ce8f90f948813/uuid_utils-0.19.0-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4062c0dfd6697ce7c91a504d8ff6eb8dcaf4e790fb6adec69c4c83fd89870d4a", size = 346714, upload-time = "2026-10-26T10:55:00.01Z" },
{ url = "https://files.pythonhosted.org/packages/3c/1a/c9d2be770a2061da608164d022becda3fc7ddabb34ec7a1950d29ae51ca0/uuid_utils-0.19.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:361cfd0fba0a49f1c8b6df741a35f7d948f0fae3d3914f8622ba261dc4656a5a", size = 500974, upload-time = "2026-10-26T10:55:01.392Z" },
{ url = "https://files.pythonhosted.org/packages/36/2e/ee7522207a7f9477c58757f040f05c4eedb68f362623d4e40da4983bdf5b/uuid_utils-0.19.0-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:a15c8943e02ac59992eaad1ce69793720dc711ce66188d386be1cbf8f5b5a2e4", size = 604607, upload-time = "2026-10-26T10:55:03.141Z" },
{ url = "https://files.pythonhosted.org/packages/07/b8/c9a1f21627238171a2021dd777177a211df20b12c0c5e45b828ee9bc3c84/uuid_utils-0.19.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:4bd00a485e973cb3bcefe06f13a663cf4ee0c54caeacb2a656a5f901d1505a03", size = 563749, upload-time = "2026-10-26T10:55:04.809Z" },
{ url = "https://files.pythonhosted.org/packages/e5/9e/ed7d14d4d7bdb94159909e722337a557c855e8990a7fdff55ef6b016fb38/uuid_utils-0.19.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:9fe68c4b2e017fb13c5e460b76b76c5cfa717ddd653cea184c2664bc2a852011", size = 530994, upload-time = "2026-10-26T10:55:06.511Z" },
{ url = "https://files.pythonhosted.org/packages/07/92/5b84a87dd4127a14337d42eb4a24a764583e4decc2675a9949d4568b16e1/uuid_utils-0.19.0-cp315-cp315t-win32.whl", hash = "sha256:5397581f40e268535cd48230e4f2f372dc5f3e70ce79e0aff9cb5ceb838163c8", size = 172512, upload-time = "2026-10-26T10:55:08.261Z" },
{ url = "https://files.pythonhosted.org/packages/d6/43/a11a33e39f64c50f17483842652933b8ef769ce7cac3399ed7d2a2b928c2/uuid_utils-0.19.0-cp315-cp315t-win_amd64.whl", hash = "sha256:225752e3c36b7c861eb1bbceb5c9197c134c806b210ed53dcacf6339a3d599e6", size = 178626, upload-time = "2026-10-26T10:55:09.658Z" },
{ url = "https://files.pythonhosted.org/packages/ab/78/4f9dc891db194ce8107c9704d7e2e51da558af457e64134da1b1a7c5db1f/uuid_utils-0.19.0-cp315-cp315t-win_arm64.whl", hash = "sha256:bfcda2352c1500230227da5475139ab177bb2516309254cd74ade19a28cb842d", size = 175582, upload-time = "2026-10-26T10:55:11.001Z" },
{ url = "https://files.pythonhosted.org/packages/61/60/751fb8688322e107e4b4b66747f084ac0e3de599eec6f800b1661b1bbf7b/uuid_utils-0.19.0-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:492e621a6aa0d8b4a47cad5e71b9fbbebf5e7af6d0f568daea65d38353f713c1", size = 569090, upload-time = "2026-10-26T10:55:12.449Z" },
{ url = "https://files.pythonhosted.org/packages/7a/0c/2971bf13e98e2cab194fb43a81c3ffc47d2eaf84d4669d1d5b96ed89d98f/uuid_utils-0.19.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:68ab7e44d8be02c5d34e2501c465e32747b384c04cf7476de80c01729466a9dc", size = 290711, upload-time = "2026-10-26T10:55:13.836Z" },
{ url = "https://files.pythonhosted.org/packages/bf/18/a446ae37e9489a4dd905c738fcd98a4a49cd3c57ba36b300fc50372162b8/uuid_utils-0.19.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0edba30609b6f1d6bc5eb9be5cc90759bf5d0c77f132d2b63fc7c62038b9304e", size = 325610, upload-time = "2026-10-26T10:55:15.237Z" },
{ url = "https://files.pythonhosted.org/packages/41/ee/32ae97908244fdbed279b4c7215fe186891a9aa1fe9d25f6760b4fe415bc/uuid_utils-0.19.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6e137ad199860077c8cf732b71fc8350dbf4bc5e427c01fb82b27dbddd45e370", size = 333087, upload-time = "2026-10-26T10:55:16.728Z" },
{ url = "https://files.pythonhosted.org/packages/25/f5/dc1ad3783eb0409a912d7c87f0f00143b450cb60cecbb9e1648aabc06f30/uuid_utils-0.19.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42f53089ba59a2d1777be98db285aa4948169d434421ee1474ce4615215326ea", size = 438894, upload-time = "2026-10-26T10:55:18.202Z" },
{ url = "https://files.pythonhosted.org/packages/5d/4c/07bbee76758ee4e425455477120899463eb8e05e5c533eb61c6314bf188c/uuid_utils-0.19.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10d689c0f2715502b7d42426947e3bb5c76ac5ba91ae9bd23afc34283189b82", size = 328432, upload-time = "2026-10-26T10:55:19.512Z" },
{ url = "https://files.pythonhosted.org/packages/14/d6/1e70bbe1c3828fec80b060137f11db69d16b4f40f8ae32526d98b9e604ec/uuid_utils-0.19.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0da57ba80366ae75307b686376bf437bdc76b3710d52e6ffe012c99a5c800927", size = 351628, upload-time = "2026-10-26T10:55:20.922Z" },
{ url = "https://files.pythonhosted.org/packages/79/df/75f58b9ee63449e8f81b1698e9e3cb76efada622e6b5590b329d26242a6b/uuid_utils-0.19.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bb0c63bfa8d9af4228353b5c1b06917acf76067674d4d741c31d2117a3db09c5", size = 181546, upload-time = "2026-10-26T10:55:22.522Z" },
]
[[package]]
@@ -2444,7 +2519,8 @@ version = "21.14.13"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "distlib" },
{ name = "filelock" },
{ name = "filelock", version = "4.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "filelock", version = "4.2.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "packaging" },
{ name = "platformdirs" },
{ name = "python-discovery" },
@@ -74,6 +74,8 @@ CONFIG_KEY_RUNTIME = sys.intern("__pregel_runtime")
# holds a `Runtime` instance with context, store, stream writer, etc.
CONFIG_KEY_RESUME_MAP = sys.intern("__pregel_resume_map")
# holds a mapping of task ns -> resume value for resuming tasks
CONFIG_KEY_STRICT_RESUME = sys.intern("__pregel_strict_resume")
# when True, the root graph refuses a resume that can't be matched to its pending interrupts
CONFIG_KEY_STREAM_MESSAGES_V2 = sys.intern("__pregel_stream_messages_v2")
# when True, attach StreamMessagesHandlerV2 so content-block (v2) events
# flow through stream_mode="messages"; set by StreamingHandler only.
@@ -134,6 +136,7 @@ RESERVED = {
CONFIG_KEY_CHECKPOINT_NS,
CONFIG_KEY_TIMED_ATTEMPT_OBSERVER,
CONFIG_KEY_RESUME_MAP,
CONFIG_KEY_STRICT_RESUME,
CONFIG_KEY_STREAM_MESSAGES_V2,
# other constants
PUSH,
+41
View File
@@ -28,6 +28,7 @@ __all__ = (
"ParentCommand",
"EmptyInputError",
"TaskNotFound",
"UnknownInterruptError",
"is_invalid_resume",
)
@@ -146,6 +147,46 @@ class TaskNotFound(Exception):
pass
class UnknownInterruptError(Exception):
"""Raised in strict resume mode when a resume doesn't match the pending interrupts.
`reason` says how it doesn't match:
- `"not_pending"`: the resume names interrupts that aren't pending, listed in
`interrupt_ids`.
- `"empty_map"`: the resume is an empty map.
- `"ambiguous"`: the resume is a single value while several interrupts are
pending.
`pending_ids` lists the interrupts still waiting for an answer. The error is
raised before anything is written, so they can still be answered.
A single value with no interrupt pending is accepted, because it resumes a
graph paused at a static breakpoint (`interrupt_before` or `interrupt_after`).
Each `interrupt()` call in a task has its own id, so an answer retried after
the task asked its next question is refused instead of answering that
question.
"""
reason: Literal["not_pending", "empty_map", "ambiguous"]
interrupt_ids: tuple[str, ...]
pending_ids: tuple[str, ...]
def __init__(
self,
message: str,
*,
reason: Literal["not_pending", "empty_map", "ambiguous"],
interrupt_ids: Sequence[str] = (),
pending_ids: Sequence[str] = (),
) -> None:
super().__init__(message)
self.reason = reason
self.interrupt_ids = tuple(interrupt_ids)
self.pending_ids = tuple(pending_ids)
@dataclass(frozen=True, slots=True)
class NodeError:
"""Failure context passed to a node-level error handler.
+15 -9
View File
@@ -85,6 +85,7 @@ from langgraph.types import (
RetryPolicy,
Send,
TimeoutPolicy,
_interrupt_id,
)
GetNextVersion = Callable[[V | None, None], V]
@@ -626,7 +627,7 @@ def prepare_single_task(
config[CONF].get(CONFIG_KEY_SCRATCHPAD),
pending_writes,
task_id,
xxh3_128_hexdigest(task_checkpoint_ns.encode()),
task_checkpoint_ns,
config[CONF].get(CONFIG_KEY_RESUME_MAP),
step,
stop,
@@ -873,7 +874,7 @@ def prepare_push_task_functional(
configurable.get(CONFIG_KEY_SCRATCHPAD),
pending_writes,
task_id,
xxh3_128_hexdigest(task_checkpoint_ns.encode()),
task_checkpoint_ns,
configurable.get(CONFIG_KEY_RESUME_MAP),
step,
stop,
@@ -1035,7 +1036,7 @@ def prepare_push_task_send(
config[CONF].get(CONFIG_KEY_SCRATCHPAD),
pending_writes,
task_id,
xxh3_128_hexdigest(task_checkpoint_ns.encode()),
task_checkpoint_ns,
config[CONF].get(CONFIG_KEY_RESUME_MAP),
step,
stop,
@@ -1184,7 +1185,7 @@ def prepare_node_error_handler_task(
config[CONF].get(CONFIG_KEY_SCRATCHPAD),
pending_writes,
task_id,
xxh3_128_hexdigest(task_checkpoint_ns.encode()),
task_checkpoint_ns,
config[CONF].get(CONFIG_KEY_RESUME_MAP),
step,
stop,
@@ -1280,7 +1281,7 @@ def _scratchpad(
parent_scratchpad: PregelScratchpad | None,
pending_writes: list[PendingWrite],
task_id: str,
namespace_hash: str,
task_checkpoint_ns: str,
resume_map: dict[str, Any] | None,
step: int,
stop: int,
@@ -1307,10 +1308,15 @@ def _scratchpad(
task_resume_write = []
del w
# find namespace and task-specific resume value
if resume_map and namespace_hash in resume_map:
mapped_resume_write = resume_map[namespace_hash]
task_resume_write.append(mapped_resume_write)
# find the resume value for the task's next interrupt
if resume_map:
next_id = _interrupt_id(task_checkpoint_ns, len(task_resume_write))
first_id = _interrupt_id(task_checkpoint_ns, 0)
if next_id in resume_map:
task_resume_write.append(resume_map[next_id])
# Older versions gave all of a task's interrupts the first id.
elif first_id in resume_map:
task_resume_write.append(resume_map[first_id])
else:
null_resume_write = None
+33 -3
View File
@@ -48,6 +48,7 @@ from langgraph._internal._constants import (
CONFIG_KEY_RUNTIME,
CONFIG_KEY_SCRATCHPAD,
CONFIG_KEY_STREAM,
CONFIG_KEY_STRICT_RESUME,
CONFIG_KEY_TASK_ID,
CONFIG_KEY_THREAD_ID,
ERROR,
@@ -77,6 +78,7 @@ from langgraph.constants import TAG_HIDDEN
from langgraph.errors import (
EmptyInputError,
GraphInterrupt,
UnknownInterruptError,
)
from langgraph.managed.base import (
ManagedValueMapping,
@@ -1024,17 +1026,45 @@ class PregelLoop:
"Cannot use Command(resume=...) without checkpointer"
)
strict = (
self.config[CONF].get(CONFIG_KEY_STRICT_RESUME, False)
# Nested calls inherit the parent's configurable.
and not self.is_nested
# A finished task can rerun and interrupt again on replay, while
# its earlier output still makes its interrupt look answered.
and not self._addressed_checkpoint_superseded
)
if resume_is_map := (
isinstance(resume, dict)
and all(is_xxh3_128_hexdigest(k) for k in resume)
):
if strict:
pending = self._pending_interrupts()
if not resume:
raise UnknownInterruptError(
"The resume map names no interrupt",
reason="empty_map",
pending_ids=sorted(pending),
)
if unknown := sorted(resume.keys() - pending):
raise UnknownInterruptError(
f"Unknown or already-consumed interrupt: {unknown[0]}",
reason="not_pending",
interrupt_ids=unknown,
pending_ids=sorted(pending),
)
self.config[CONF][CONFIG_KEY_RESUME_MAP] = resume
else:
if len(self._pending_interrupts()) > 1:
raise RuntimeError(
if len(pending := self._pending_interrupts()) > 1:
message = (
"When there are multiple pending interrupts, you must specify the interrupt id when resuming. "
"Docs: https://docs.langchain.com/oss/python/langgraph/add-human-in-the-loop#resume-multiple-interrupts-with-one-invocation."
"Docs: https://docs.langchain.com/oss/python/langgraph/interrupts#handling-multiple-interrupts."
)
if strict:
raise UnknownInterruptError(
message, reason="ambiguous", pending_ids=sorted(pending)
)
raise RuntimeError(message)
writes: defaultdict[str, list[tuple[str, Any]]] = defaultdict(list)
# group writes by task ID
+21
View File
@@ -28,6 +28,7 @@ from langgraph._internal._constants import (
CONFIG_KEY_CHECKPOINT_NS,
CONFIG_KEY_RESUMING,
CONFIG_KEY_RUNTIME,
CONFIG_KEY_SCRATCHPAD,
CONFIG_KEY_SEND,
CONFIG_KEY_STREAM,
CONFIG_KEY_TASK_ID,
@@ -44,6 +45,7 @@ from langgraph.errors import (
NodeTimeoutError,
ParentCommand,
)
from langgraph.pregel._algo import LazyAtomicCounter
from langgraph.pregel.protocol import StreamProtocol
from langgraph.runtime import ExecutionInfo, Runtime
from langgraph.types import Command, PregelExecutableTask, RetryPolicy, TimeoutPolicy
@@ -571,6 +573,23 @@ def _checkpoint_ns_for_parent_command(ns: str) -> str:
return NS_SEP.join(parts)
def _reset_interrupt_counter(config: RunnableConfig) -> RunnableConfig:
# A retry reruns the node from its first line, so its interrupt() calls count
# from the first again to reuse the answers its task already has, rather than
# asking again under an id the next resume can't match.
scratchpad = config[CONF].get(CONFIG_KEY_SCRATCHPAD)
if scratchpad is None:
return config
return patch_configurable(
config,
{
CONFIG_KEY_SCRATCHPAD: replace(
scratchpad, interrupt_counter=LazyAtomicCounter()
)
},
)
def run_with_retry(
task: PregelExecutableTask,
retry_policy: Sequence[RetryPolicy] | None,
@@ -681,6 +700,7 @@ def run_with_retry(
)
# signal subgraphs to resume (if available)
config = patch_configurable(config, {CONFIG_KEY_RESUMING: True})
config = _reset_interrupt_counter(config)
async def arun_with_retry(
@@ -829,6 +849,7 @@ async def arun_with_retry(
)
# signal subgraphs to resume (if available)
config = patch_configurable(config, {CONFIG_KEY_RESUMING: True})
config = _reset_interrupt_counter(config)
def _return_value(task: PregelExecutableTask) -> Any:
+35 -12
View File
@@ -884,11 +884,23 @@ class Pregel(
)
def _subgraph_for_namespace(
self, config: RunnableConfig, checkpointer: BaseCheckpointSaver
self,
config: RunnableConfig,
checkpointer: BaseCheckpointSaver,
*,
for_update: bool = False,
) -> tuple[PregelProtocol, RunnableConfig] | None:
"""The subgraph a state method's namespaced config addresses, and the
config to call it with, lending it `checkpointer`. `None` when the
config is for this graph."""
config is for this graph, or names no subgraph this graph declares or
detects (such as one a tool invokes), so a read uses this graph's
channels.
Raises:
ValueError: If `for_update` and the namespace names no such
subgraph: this graph's nodes and channels would write the wrong
state into it.
"""
checkpoint_ns = config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "")
if not checkpoint_ns or CONFIG_KEY_CHECKPOINTER in config[CONF]:
return None
@@ -897,7 +909,9 @@ class Pregel(
return pregel, patch_configurable(
config, {CONFIG_KEY_CHECKPOINTER: checkpointer}
)
raise ValueError(f"Subgraph {recast} not found")
if for_update:
raise ValueError(f"Subgraph {recast} not found")
return None
def _apply_checkpointer_allowlist(
self, checkpointer: BaseCheckpointSaver | None
@@ -1143,7 +1157,9 @@ class Pregel(
"""Get the subgraphs of the graph.
Args:
namespace: The namespace to filter the subgraphs by.
namespace: Yield only the subgraph at this exact namespace, such
as `parent|child` (without task IDs). A nested namespace needs
`recurse=True`.
recurse: Whether to recurse into the subgraphs.
If `False`, only the immediate subgraphs will be returned.
@@ -1151,9 +1167,9 @@ class Pregel(
An iterator of the `(namespace, subgraph)` pairs.
"""
for name, node in self.nodes.items():
# filter by prefix
# filter by the namespace's first segment, matched whole
if namespace is not None:
if not namespace.startswith(name):
if namespace != name and not namespace.startswith(name + NS_SEP):
continue
# find the subgraph, if any
@@ -1167,12 +1183,13 @@ class Pregel(
if namespace is None:
yield name, graph
if recurse and isinstance(graph, Pregel):
if namespace is not None:
namespace = namespace[len(name) + 1 :]
child_namespace = (
None if namespace is None else namespace[len(name) + 1 :]
)
yield from (
(f"{name}{NS_SEP}{n}", s)
for n, s in graph.get_subgraphs(
namespace=namespace, recurse=recurse
namespace=child_namespace, recurse=recurse
)
)
@@ -1182,7 +1199,9 @@ class Pregel(
"""Get the subgraphs of the graph.
Args:
namespace: The namespace to filter the subgraphs by.
namespace: Yield only the subgraph at this exact namespace, such
as `parent|child` (without task IDs). A nested namespace needs
`recurse=True`.
recurse: Whether to recurse into the subgraphs.
If `False`, only the immediate subgraphs will be returned.
@@ -1641,7 +1660,9 @@ class Pregel(
if any(len(u) == 0 for u in supersteps):
raise ValueError("No updates provided")
if subgraph := self._subgraph_for_namespace(config, checkpointer):
if subgraph := self._subgraph_for_namespace(
config, checkpointer, for_update=True
):
pregel, subgraph_config = subgraph
return pregel.bulk_update_state(subgraph_config, supersteps)
@@ -2142,7 +2163,9 @@ class Pregel(
if any(len(u) == 0 for u in supersteps):
raise ValueError("No updates provided")
if subgraph := self._subgraph_for_namespace(config, checkpointer):
if subgraph := self._subgraph_for_namespace(
config, checkpointer, for_update=True
):
pregel, subgraph_config = subgraph
return await pregel.abulk_update_state(subgraph_config, supersteps)
@@ -50,6 +50,7 @@ from langgraph._internal._constants import (
CONFIG_KEY_CHECKPOINT_MAP,
CONFIG_KEY_CHECKPOINT_NS,
CONFIG_KEY_STREAM,
CONFIG_KEY_STRICT_RESUME,
CONFIG_KEY_TASK_ID,
INTERRUPT,
NS_SEP,
@@ -81,6 +82,7 @@ _CONF_DROPLIST = frozenset(
CONFIG_KEY_CHECKPOINT_ID,
CONFIG_KEY_CHECKPOINT_NS,
CONFIG_KEY_TASK_ID,
CONFIG_KEY_STRICT_RESUME,
),
)
+15 -3
View File
@@ -33,6 +33,7 @@ from xxhash import xxh3_128_hexdigest
from langgraph._internal._cache import default_cache_key
from langgraph._internal._constants import INTERRUPT as _INTERRUPT_KEY
from langgraph._internal._constants import NS_SEP
from langgraph._internal._fields import get_cached_annotated_keys, get_update_as_tuples
from langgraph._internal._retry import default_retry_on
from langgraph._internal._typing import MISSING, DeprecatedKwargs
@@ -579,6 +580,15 @@ def omit_payload(_value: Any) -> dict[str, Any]:
_DEFAULT_INTERRUPT_ID = "placeholder-id"
def _interrupt_id(ns: str, answered: int) -> str:
# Keyed by how many answers the task already has, the slot this interrupt's
# answer fills; call order isn't stable across parallel calls in one task.
# With none it's the bare namespace hash older versions gave every
# interrupt, so saved ids still match. The suffix can't form another task's
# namespace, whose segments all contain ":".
return xxh3_128_hexdigest((f"{ns}{NS_SEP}{answered}" if answered else ns).encode())
@final
@dataclass(init=False, slots=True)
class Interrupt(Generic[ResponseT]):
@@ -641,7 +651,7 @@ class Interrupt(Generic[ResponseT]):
) -> Interrupt[ResponseT]:
return cls(
value=value,
id=xxh3_128_hexdigest(ns.encode()),
id=_interrupt_id(ns, 0),
response_schema=response_schema,
)
@@ -1037,9 +1047,11 @@ def interrupt(
# no resume value found
raise GraphInterrupt(
(
Interrupt.from_ns(
Interrupt(
value=value,
ns=conf[CONFIG_KEY_CHECKPOINT_NS],
id=_interrupt_id(
conf[CONFIG_KEY_CHECKPOINT_NS], len(scratchpad.resume)
),
response_schema=adapter.json_schema() if adapter else response_schema,
),
)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph"
version = "1.2.19"
version = "1.2.20"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
requires-python = ">=3.10"
+2 -1
View File
@@ -16,7 +16,7 @@ from langgraph_sdk.schema import StreamPart
from pydantic import BaseModel
from typing_extensions import TypedDict
from langgraph._internal._constants import CONFIG_KEY_STREAM
from langgraph._internal._constants import CONFIG_KEY_STREAM, CONFIG_KEY_STRICT_RESUME
from langgraph.errors import GraphInterrupt
from langgraph.graph import END, START, MessagesState, StateGraph, add_messages
from langgraph.pregel import Pregel
@@ -1360,6 +1360,7 @@ def test_sanitize_config():
"checkpoint_map": {"key": "value"}, # Should be dropped
"checkpoint_id": "123", # Should be dropped
"checkpoint_ns": "ns", # Should be dropped
CONFIG_KEY_STRICT_RESUME: True, # Should be dropped
}
}
sanitized = remote._sanitize_config(config_with_drops)
+423
View File
@@ -0,0 +1,423 @@
import operator
import sys
import uuid
from typing import Annotated
import pytest
from langchain_core.runnables import RunnableConfig
from langchain_core.runnables.config import get_executor_for_config
from langgraph.checkpoint.base import BaseCheckpointSaver
from typing_extensions import TypedDict
from xxhash import xxh3_128_hexdigest
from langgraph._internal._constants import CONFIG_KEY_STRICT_RESUME, INTERRUPT
from langgraph.errors import UnknownInterruptError
from langgraph.graph import START, StateGraph
from langgraph.graph.state import CompiledStateGraph
from langgraph.types import Command, Durability, Interrupt, RetryPolicy, interrupt
pytestmark = pytest.mark.anyio
NEEDS_CONTEXTVARS = pytest.mark.skipif(
sys.version_info < (3, 11),
reason="Python 3.11+ is required for async contextvars support",
)
UNKNOWN_ID = "0" * 32
class State(TypedDict, total=False):
log: Annotated[list[str], operator.add]
def _config(*, strict: bool = True) -> RunnableConfig:
return {
"configurable": {
"thread_id": str(uuid.uuid4()),
CONFIG_KEY_STRICT_RESUME: strict,
}
}
def _parallel(checkpointer: BaseCheckpointSaver) -> CompiledStateGraph:
def a(state: State):
return {"log": [f"a:{interrupt('a?')}"]}
def b(state: State):
return {"log": [f"b:{interrupt('b?')}"]}
return (
StateGraph(State)
.add_node("a", a)
.add_node("b", b)
.add_edge(START, "a")
.add_edge(START, "b")
.compile(checkpointer=checkpointer)
)
def _ask_twice(checkpointer: BaseCheckpointSaver) -> CompiledStateGraph:
def ask_twice(state: State):
first = interrupt("first?")
second = interrupt("second?")
return {"log": [f"{first},{second}"]}
return (
StateGraph(State)
.add_node("ask", ask_twice)
.add_edge(START, "ask")
.compile(checkpointer=checkpointer)
)
def _ask_then_fail_once(checkpointer: BaseCheckpointSaver) -> CompiledStateGraph:
failures = [ConnectionError("flaky api")]
def ask(state: State):
answer = interrupt("delete a.txt?")
if failures:
raise failures.pop()
return {"log": [answer]}
return (
StateGraph(State)
.add_node(
"ask", ask, retry_policy=RetryPolicy(initial_interval=0, jitter=False)
)
.add_edge(START, "ask")
.compile(checkpointer=checkpointer)
)
def _pending_ids(graph: CompiledStateGraph, config: RunnableConfig) -> dict[str, str]:
return {i.value: i.id for i in graph.get_state(config).interrupts}
def test_unknown_id_is_refused_before_anything_is_written(
sync_checkpointer: BaseCheckpointSaver, durability: Durability
) -> None:
graph = _parallel(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config, durability=durability)
before = graph.get_state(config)
checkpoints = len(list(sync_checkpointer.list(config)))
ids = _pending_ids(graph, config)
with pytest.raises(
UnknownInterruptError,
match=f"Unknown or already-consumed interrupt: {UNKNOWN_ID}",
) as refused:
graph.invoke(Command(resume={UNKNOWN_ID: "x"}), config, durability=durability)
assert (
refused.value.reason,
refused.value.interrupt_ids,
refused.value.pending_ids,
) == ("not_pending", (UNKNOWN_ID,), tuple(sorted(ids.values())))
after = graph.get_state(config)
assert len(list(sync_checkpointer.list(config))) == checkpoints
assert (after.values, after.next, after.interrupts) == (
before.values,
before.next,
before.interrupts,
)
result = graph.invoke(
Command(resume={ids["a?"]: "yes", ids["b?"]: "no"}),
config,
durability=durability,
)
assert sorted(result["log"]) == ["a:yes", "b:no"]
def test_an_answered_id_is_refused(sync_checkpointer: BaseCheckpointSaver) -> None:
graph = _parallel(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
ids = _pending_ids(graph, config)
graph.invoke(Command(resume={ids["a?"]: "yes"}), config)
with pytest.raises(UnknownInterruptError, match=ids["a?"]) as refused:
graph.invoke(Command(resume={ids["a?"]: "again"}), config)
assert (
refused.value.reason,
refused.value.interrupt_ids,
refused.value.pending_ids,
) == ("not_pending", (ids["a?"],), (ids["b?"],))
result = graph.invoke(Command(resume={ids["b?"]: "no"}), config)
assert sorted(result["log"]) == ["a:yes", "b:no"]
def test_an_empty_map_is_refused(sync_checkpointer: BaseCheckpointSaver) -> None:
graph = _parallel(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
with pytest.raises(UnknownInterruptError, match="names no interrupt") as refused:
graph.invoke(Command(resume={}), config)
assert (
refused.value.reason,
refused.value.interrupt_ids,
refused.value.pending_ids,
) == ("empty_map", (), tuple(sorted(_pending_ids(graph, config).values())))
def test_one_value_is_refused_while_several_are_pending(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _parallel(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
with pytest.raises(
UnknownInterruptError, match="multiple pending interrupts"
) as refused:
graph.invoke(Command(resume="yes"), config)
ids = _pending_ids(graph, config)
assert (refused.value.reason, refused.value.pending_ids) == (
"ambiguous",
tuple(sorted(ids.values())),
)
graph.invoke(Command(resume={ids["a?"]: "yes"}), config)
result = graph.invoke(Command(resume="no"), config)
assert sorted(result["log"]) == ["a:yes", "b:no"]
def test_one_value_resumes_a_static_breakpoint(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = (
StateGraph(State)
.add_node("a", lambda state: {"log": ["a"]})
.add_edge(START, "a")
.compile(checkpointer=sync_checkpointer, interrupt_before=["a"])
)
config = _config()
graph.invoke({"log": []}, config)
with pytest.raises(UnknownInterruptError, match=UNKNOWN_ID):
graph.invoke(Command(resume={UNKNOWN_ID: "x"}), config)
assert graph.invoke(Command(resume="go"), config) == {"log": ["a"]}
def test_without_strict_mode_nothing_changes(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _parallel(sync_checkpointer)
config = _config(strict=False)
graph.invoke({"log": []}, config)
graph.invoke(Command(resume={UNKNOWN_ID: "x"}), config)
assert set(_pending_ids(graph, config)) == {"a?", "b?"}
with pytest.raises(RuntimeError, match="multiple pending interrupts"):
graph.invoke(Command(resume="yes"), config)
def test_a_subgraph_interrupt_is_answered_by_its_id(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
def ask(state: State):
return {"log": [f"asked:{interrupt('sub?')}"]}
subgraph = StateGraph(State).add_node("ask", ask).add_edge(START, "ask").compile()
graph = (
StateGraph(State)
.add_node("sub", subgraph)
.add_edge(START, "sub")
.compile(checkpointer=sync_checkpointer)
)
config = _config()
graph.invoke({"log": []}, config)
result = graph.invoke(
Command(resume={_pending_ids(graph, config)["sub?"]: "yes"}), config
)
assert result["log"] == ["asked:yes"]
def test_a_second_interrupt_in_one_node_gets_its_own_id(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _ask_twice(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
state = graph.get_state(config)
(card,) = state.interrupts
assert card.id == xxh3_128_hexdigest(f"ask:{state.tasks[0].id}".encode())
graph.invoke(Command(resume={card.id: "1"}), config)
(second,) = graph.get_state(config).interrupts
assert second.id != card.id
result = graph.invoke(Command(resume={second.id: "2"}), config)
assert result["log"] == ["1,2"]
def test_a_retried_answer_in_one_node_is_refused(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _ask_twice(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
(card,) = graph.get_state(config).interrupts
graph.invoke(Command(resume={card.id: "yes"}), config)
(second,) = graph.get_state(config).interrupts
with pytest.raises(UnknownInterruptError) as refused:
graph.invoke(Command(resume={card.id: "yes"}), config)
assert (
refused.value.reason,
refused.value.interrupt_ids,
refused.value.pending_ids,
) == ("not_pending", (card.id,), (second.id,))
result = graph.invoke(Command(resume={second.id: "no"}), config)
assert result["log"] == ["yes,no"]
def test_parallel_interrupts_in_one_node_each_take_an_answer(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
def ask(name: str) -> str:
return interrupt(f"{name}?")
def ask_in_parallel(state: State, config: RunnableConfig):
with get_executor_for_config(config) as pool:
b = pool.submit(ask, "b")
b.exception()
a = pool.submit(ask, "a")
return {"log": [f"{a.result()},{b.result()}"]}
graph = (
StateGraph(State)
.add_node("ask", ask_in_parallel)
.add_edge(START, "ask")
.compile(checkpointer=sync_checkpointer)
)
config = _config()
graph.invoke({"log": []}, config)
for answer in ("1", "2"):
(card,) = graph.get_state(config).interrupts
graph.invoke(Command(resume={card.id: answer}), config)
assert graph.get_state(config).next == ()
def test_a_later_interrupt_saved_with_the_first_id_is_still_answered(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _ask_twice(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
(card,) = graph.get_state(config).interrupts
graph.invoke(Command(resume={card.id: "1"}), config)
state = graph.get_state(config)
sync_checkpointer.put_writes(
state.config,
[(INTERRUPT, (Interrupt(value="second?", id=card.id),))],
state.tasks[0].id,
)
result = graph.invoke(Command(resume={card.id: "2"}), config)
assert result["log"] == ["1,2"]
def test_a_retried_node_reuses_its_answer(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _ask_then_fail_once(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
(card,) = graph.get_state(config).interrupts
result = graph.invoke(Command(resume={card.id: "yes"}), config)
assert result["log"] == ["yes"]
def test_a_card_whose_resumed_task_raised_is_not_refused(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
failures = ["once"]
def ask_then_fail_once(state: State):
answer = interrupt("approve?")
if failures:
failures.pop()
raise ValueError("failed after the answer")
return {"log": [f"answered:{answer}"]}
graph = (
StateGraph(State)
.add_node("ask", ask_then_fail_once)
.add_edge(START, "ask")
.compile(checkpointer=sync_checkpointer)
)
config = _config()
graph.invoke({"log": []}, config)
(card,) = graph.get_state(config).interrupts
with pytest.raises(ValueError, match="failed after the answer"):
graph.invoke(Command(resume={card.id: "first"}), config)
graph.invoke(Command(resume={card.id: "second"}), config)
assert graph.get_state(config).next == ()
def test_a_pinned_head_checkpoint_is_checked(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _parallel(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
head = graph.get_state(config).config
pinned = {"configurable": {**config["configurable"], **head["configurable"]}}
with pytest.raises(UnknownInterruptError):
graph.invoke(Command(resume={UNKNOWN_ID: "x"}), pinned)
ids = _pending_ids(graph, config)
result = graph.invoke(Command(resume={ids["a?"]: "yes", ids["b?"]: "no"}), pinned)
assert sorted(result["log"]) == ["a:yes", "b:no"]
def test_a_superseded_checkpoint_is_not_checked(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _parallel(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
paused = graph.get_state(config).config
ids = _pending_ids(graph, config)
graph.invoke(Command(resume={ids["a?"]: "yes", ids["b?"]: "no"}), config)
pinned = {"configurable": {**config["configurable"], **paused["configurable"]}}
graph.invoke(Command(resume={ids["a?"]: "again", ids["b?"]: "again"}), pinned)
@NEEDS_CONTEXTVARS
async def test_unknown_id_is_refused_async(
async_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _parallel(async_checkpointer)
config = _config()
await graph.ainvoke({"log": []}, config)
with pytest.raises(UnknownInterruptError, match=UNKNOWN_ID):
await graph.ainvoke(Command(resume={UNKNOWN_ID: "x"}), config)
state = await graph.aget_state(config)
assert {i.value for i in state.interrupts} == {"a?", "b?"}
@NEEDS_CONTEXTVARS
async def test_a_retried_node_reuses_its_answer_async(
async_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _ask_then_fail_once(async_checkpointer)
config = _config()
await graph.ainvoke({"log": []}, config)
(card,) = (await graph.aget_state(config)).interrupts
result = await graph.ainvoke(Command(resume={card.id: "yes"}), config)
assert result["log"] == ["yes"]
@@ -6,16 +6,24 @@ Covers three checkpointer settings for subgraph state:
interrupt support, but state resets each invocation. This is the common case
when an agent is invoked from inside a tool used by another agent.
- checkpointer=True: "stateful" — state accumulates across invocations on the same thread id
Also covers reading a subgraph's namespace from the parent graph, including
one where no subgraph is declared, such as a subagent a tool invokes, and
finding the subgraph a namespace names.
"""
from typing import Annotated, Any
from uuid import uuid4
from langchain_core.messages import AIMessage, HumanMessage
import pytest
from langchain_core.messages import AIMessage, AnyMessage, HumanMessage
from langchain_core.runnables import RunnableConfig
from langgraph.checkpoint.base import BaseCheckpointSaver
from typing_extensions import TypedDict
from langgraph.channels.delta import DeltaChannel
from langgraph.graph import START, StateGraph
from langgraph.graph.message import MessagesState
from langgraph.graph.message import MessagesState, _messages_delta_reducer
from langgraph.types import Command, Interrupt, interrupt
from tests.any_str import AnyStr
@@ -685,3 +693,301 @@ def test_child_with_own_thread_id_keeps_namespace(
parent.invoke({"result": ""}, config=parent_config)
state2 = child.get_state(child_config)
assert state2.values.get("count") == 2
# -- reading a subgraph's namespace from the parent graph --
class NotesState(MessagesState):
notes: str # a key only the subgraphs have
class DeltaMessagesState(TypedDict):
# Stores `messages` as deltas between snapshots, as deepagents' parent does.
messages: Annotated[list[AnyMessage], DeltaChannel(_messages_delta_reducer)]
class DeltaNotesState(DeltaMessagesState):
notes: str
def _notes_graph(label: str, state_schema: type = NotesState) -> Any:
# Typed `dict` so it fits either schema (see `_run_tool_subgraph`).
def work(state: dict) -> dict:
return {"messages": [AIMessage(content=f"{label} done")], "notes": label}
return (
StateGraph(state_schema)
.add_node("work", work)
.add_edge(START, "work")
.compile()
)
def _namespace_configs(parent: Any) -> dict[str, RunnableConfig]:
"""Run `parent` on a new thread. Returns a config for each subgraph
namespace it wrote, keyed by the node the namespace starts with."""
thread_id = str(uuid4())
return {
ns[0].split(":")[0]: {
"configurable": {"thread_id": thread_id, "checkpoint_ns": ns[0]}
}
for ns, _ in parent.stream(
{"messages": [HumanMessage(content="go")]},
{"configurable": {"thread_id": thread_id}},
subgraphs=True,
)
if ns
}
def _run_tool_subgraph(
checkpointer: BaseCheckpointSaver,
*,
parent_schema: type = MessagesState,
subagent_schema: type = NotesState,
) -> tuple[Any, RunnableConfig]:
"""Run a parent whose `tools` node invokes a subgraph it doesn't declare,
as deepagents runs subagents. Returns the parent and a config for the
subgraph's `tools:<task_id>` namespace."""
# Subgraph detection doesn't follow a subscript, so `tools` declares none.
subagents = {"researcher": _notes_graph("researcher", subagent_schema)}
# Typed `dict`: a schema here would add its `messages` channel to the
# parent's, and a plain one clashes with a `DeltaChannel` one.
def tools(state: dict) -> dict:
subagents["researcher"].invoke(
{"messages": [HumanMessage(content="research apples")]}
)
return {"messages": [AIMessage(content="tool done")]}
parent = (
StateGraph(parent_schema)
.add_node("tools", tools)
.add_edge(START, "tools")
.compile(checkpointer=checkpointer)
)
return parent, _namespace_configs(parent)["tools"]
def test_undeclared_subgraph_get_state(sync_checkpointer: BaseCheckpointSaver) -> None:
"""A namespace with no declared subgraph is read with the parent graph."""
parent, ns_config = _run_tool_subgraph(sync_checkpointer)
state = parent.get_state(ns_config)
assert [m.text for m in state.values["messages"]] == [
"research apples",
"researcher done",
]
assert state.config["configurable"]["checkpoint_ns"] == AnyStr("tools:")
def test_undeclared_subgraph_get_state_history(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
"""History of a namespace with no declared subgraph is read with the parent
graph, and only holds that namespace's checkpoints."""
parent, ns_config = _run_tool_subgraph(sync_checkpointer)
history = list(parent.get_state_history(ns_config))
assert [[m.text for m in s.values.get("messages", [])] for s in history] == [
["research apples", "researcher done"],
["research apples"],
[],
]
assert {s.config["configurable"]["checkpoint_ns"] for s in history} == {
ns_config["configurable"]["checkpoint_ns"]
}
def test_undeclared_subgraph_update_state_raises(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
"""Unlike a read, an update can't fall back to the parent graph: its nodes
and channels would write the wrong state into the subgraph's namespace."""
parent, ns_config = _run_tool_subgraph(sync_checkpointer)
with pytest.raises(ValueError, match="Subgraph tools not found"):
parent.update_state(ns_config, {"messages": [AIMessage(content="edit")]})
assert len(list(parent.get_state_history(ns_config))) == 3
@pytest.mark.parametrize(
("parent_schema", "subagent_schema"),
[(DeltaMessagesState, NotesState), (MessagesState, DeltaNotesState)],
ids=["delta_parent", "delta_subagent"],
)
def test_undeclared_subgraph_read_across_messages_channel_types(
sync_checkpointer: BaseCheckpointSaver,
parent_schema: type,
subagent_schema: type,
) -> None:
"""deepagents' parent stores `messages` in a `DeltaChannel`, and its
subagents in a plain `add_messages` channel. Reading a subagent's namespace
with the parent's channel restores its messages, either way round."""
parent, ns_config = _run_tool_subgraph(
sync_checkpointer,
parent_schema=parent_schema,
subagent_schema=subagent_schema,
)
state = parent.get_state(ns_config)
history = list(parent.get_state_history(ns_config))
assert [m.text for m in state.values["messages"]] == [
"research apples",
"researcher done",
]
assert [[m.text for m in s.values.get("messages", [])] for s in history] == [
["research apples", "researcher done"],
["research apples"],
[],
]
@pytest.mark.parametrize("review_declared", [False, True])
def test_prefix_named_namespaces_resolve_to_their_own_graph(
sync_checkpointer: BaseCheckpointSaver, review_declared: bool
) -> None:
"""`research_review` starts with `research`, whose subgraph has a `review`
node with its own subgraph. Each namespace still reads with its own graph:
a declared subgraph's channels, or the parent's when none is declared."""
research = (
StateGraph(NotesState)
.add_node("review", _notes_graph("research"))
.add_edge(START, "review")
.compile()
)
class ReviewState(NotesState):
verdict: str # a key `research|review`'s graph doesn't have
def review(state: ReviewState) -> dict:
return {
"messages": [AIMessage(content="review done")],
"notes": "review",
"verdict": "approved",
}
# Subgraph detection doesn't follow a subscript, so `call_review` declares
# none.
subgraphs = {
"review": StateGraph(ReviewState)
.add_node("work", review)
.add_edge(START, "work")
.compile()
}
def call_review(state: MessagesState) -> dict:
subgraphs["review"].invoke({"messages": [HumanMessage(content="check it")]})
return {}
parent = (
StateGraph(MessagesState)
.add_node("research", research)
.add_node(
"research_review",
subgraphs["review"] if review_declared else call_review,
)
.add_edge(START, "research")
.add_edge("research", "research_review")
.compile(checkpointer=sync_checkpointer)
)
configs = _namespace_configs(parent)
def values(name: str) -> dict:
return {
k: [m.text for m in v] if k == "messages" else v
for k, v in parent.get_state(configs[name]).values.items()
}
assert values("research") == {
"messages": ["go", "research done"],
"notes": "research",
}
if review_declared:
assert values("research_review") == {
"messages": ["go", "research done", "review done"],
"notes": "review",
"verdict": "approved",
}
else:
# The parent's channels have no `notes` or `verdict`.
assert values("research_review") == {"messages": ["check it", "review done"]}
def _prefix_named_subgraphs() -> tuple[Any, dict[str, Any]]:
"""A parent whose subgraph namespaces start with one another's names:
`research` has a `review` subgraph, and the parent declares
`research_review` and `review` too. Returns the parent and each subgraph,
keyed by its namespace."""
graphs = {
"research|review": _notes_graph("research's review"),
"research_review": _notes_graph("research_review"),
"review": _notes_graph("review"),
}
graphs["research"] = (
StateGraph(NotesState)
.add_node("review", graphs["research|review"])
.add_edge(START, "review")
.compile()
)
parent = (
StateGraph(NotesState)
.add_node("research", graphs["research"])
.add_node("research_review", graphs["research_review"])
.add_node("review", graphs["review"])
.add_edge(START, "research")
.add_edge("research", "research_review")
.add_edge("research_review", "review")
.compile()
)
return parent, graphs
@pytest.mark.parametrize(
"namespace", ["research", "research|review", "research_review", "review"]
)
def test_get_subgraphs_matches_whole_namespace_segments(namespace: str) -> None:
"""`get_subgraphs(namespace=...)` yields only the subgraph at that
namespace. `research_review` isn't `research` then `review`, and
`research|review` doesn't also find the parent's own `review`."""
parent, graphs = _prefix_named_subgraphs()
assert list(parent.get_subgraphs(namespace=namespace, recurse=True)) == [
(namespace, graphs[namespace])
]
def test_nested_namespace_update_raises_despite_top_level_namesake(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
"""`research|review` names a `review` inside `research`'s subgraph, which
declares no subgraph there. An update to it raises rather than writing
through the parent's own `review`."""
research = (
StateGraph(NotesState)
.add_node("review", lambda state: {})
.add_edge(START, "review")
.compile()
)
parent = (
StateGraph(NotesState)
.add_node("research", research)
.add_node("review", _notes_graph("review"))
.add_edge(START, "research")
.add_edge("research", "review")
.compile(checkpointer=sync_checkpointer)
)
config: RunnableConfig = {
"configurable": {
"thread_id": str(uuid4()),
"checkpoint_ns": "research:a|review:b",
}
}
with pytest.raises(ValueError, match=r"Subgraph research\|review not found"):
parent.update_state(config, {"notes": "edit"})
@@ -6,18 +6,25 @@ Covers three checkpointer settings for subgraph state:
interrupt support, but state resets each invocation. This is the common case
when an agent is invoked from inside a tool used by another agent.
- checkpointer=True: "stateful" — state accumulates across invocations on the same thread id
Also covers reading a subgraph's namespace from the parent graph, including
one where no subgraph is declared, such as a subagent a tool invokes, and
finding the subgraph a namespace names.
"""
import sys
from typing import Annotated, Any
from uuid import uuid4
import pytest
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.messages import AIMessage, AnyMessage, HumanMessage
from langchain_core.runnables import RunnableConfig
from langgraph.checkpoint.base import BaseCheckpointSaver
from typing_extensions import TypedDict
from langgraph.channels.delta import DeltaChannel
from langgraph.graph import START, StateGraph
from langgraph.graph.message import MessagesState
from langgraph.graph.message import MessagesState, _messages_delta_reducer
from langgraph.types import Command, Interrupt, interrupt
from tests.any_str import AnyStr
@@ -707,3 +714,315 @@ async def test_child_with_own_thread_id_keeps_namespace_async(
await parent.ainvoke({"result": ""}, config=parent_config)
state2 = await child.aget_state(child_config)
assert state2.values.get("count") == 2
# -- reading a subgraph's namespace from the parent graph --
class NotesState(MessagesState):
notes: str # a key only the subgraphs have
class DeltaMessagesState(TypedDict):
# Stores `messages` as deltas between snapshots, as deepagents' parent does.
messages: Annotated[list[AnyMessage], DeltaChannel(_messages_delta_reducer)]
class DeltaNotesState(DeltaMessagesState):
notes: str
def _notes_graph(label: str, state_schema: type = NotesState) -> Any:
# Typed `dict` so it fits either schema (see `_run_tool_subgraph`).
def work(state: dict) -> dict:
return {"messages": [AIMessage(content=f"{label} done")], "notes": label}
return (
StateGraph(state_schema)
.add_node("work", work)
.add_edge(START, "work")
.compile()
)
async def _namespace_configs(parent: Any) -> dict[str, RunnableConfig]:
"""Run `parent` on a new thread. Returns a config for each subgraph
namespace it wrote, keyed by the node the namespace starts with."""
thread_id = str(uuid4())
return {
ns[0].split(":")[0]: {
"configurable": {"thread_id": thread_id, "checkpoint_ns": ns[0]}
}
async for ns, _ in parent.astream(
{"messages": [HumanMessage(content="go")]},
{"configurable": {"thread_id": thread_id}},
subgraphs=True,
)
if ns
}
async def _run_tool_subgraph(
checkpointer: BaseCheckpointSaver,
*,
parent_schema: type = MessagesState,
subagent_schema: type = NotesState,
) -> tuple[Any, RunnableConfig]:
"""Run a parent whose `tools` node invokes a subgraph it doesn't declare,
as deepagents runs subagents. Returns the parent and a config for the
subgraph's `tools:<task_id>` namespace."""
# Subgraph detection doesn't follow a subscript, so `tools` declares none.
subagents = {"researcher": _notes_graph("researcher", subagent_schema)}
# Typed `dict`: a schema here would add its `messages` channel to the
# parent's, and a plain one clashes with a `DeltaChannel` one.
async def tools(state: dict) -> dict:
await subagents["researcher"].ainvoke(
{"messages": [HumanMessage(content="research apples")]}
)
return {"messages": [AIMessage(content="tool done")]}
parent = (
StateGraph(parent_schema)
.add_node("tools", tools)
.add_edge(START, "tools")
.compile(checkpointer=checkpointer)
)
return parent, (await _namespace_configs(parent))["tools"]
@NEEDS_CONTEXTVARS
async def test_undeclared_subgraph_get_state_async(
async_checkpointer: BaseCheckpointSaver,
) -> None:
"""A namespace with no declared subgraph is read with the parent graph."""
parent, ns_config = await _run_tool_subgraph(async_checkpointer)
state = await parent.aget_state(ns_config)
assert [m.text for m in state.values["messages"]] == [
"research apples",
"researcher done",
]
assert state.config["configurable"]["checkpoint_ns"] == AnyStr("tools:")
@NEEDS_CONTEXTVARS
async def test_undeclared_subgraph_get_state_history_async(
async_checkpointer: BaseCheckpointSaver,
) -> None:
"""History of a namespace with no declared subgraph is read with the parent
graph, and only holds that namespace's checkpoints."""
parent, ns_config = await _run_tool_subgraph(async_checkpointer)
history = [s async for s in parent.aget_state_history(ns_config)]
assert [[m.text for m in s.values.get("messages", [])] for s in history] == [
["research apples", "researcher done"],
["research apples"],
[],
]
assert {s.config["configurable"]["checkpoint_ns"] for s in history} == {
ns_config["configurable"]["checkpoint_ns"]
}
@NEEDS_CONTEXTVARS
async def test_undeclared_subgraph_update_state_raises_async(
async_checkpointer: BaseCheckpointSaver,
) -> None:
"""Unlike a read, an update can't fall back to the parent graph: its nodes
and channels would write the wrong state into the subgraph's namespace."""
parent, ns_config = await _run_tool_subgraph(async_checkpointer)
with pytest.raises(ValueError, match="Subgraph tools not found"):
await parent.aupdate_state(ns_config, {"messages": [AIMessage(content="edit")]})
assert len([s async for s in parent.aget_state_history(ns_config)]) == 3
@NEEDS_CONTEXTVARS
@pytest.mark.parametrize(
("parent_schema", "subagent_schema"),
[(DeltaMessagesState, NotesState), (MessagesState, DeltaNotesState)],
ids=["delta_parent", "delta_subagent"],
)
async def test_undeclared_subgraph_read_across_messages_channel_types_async(
async_checkpointer: BaseCheckpointSaver,
parent_schema: type,
subagent_schema: type,
) -> None:
"""deepagents' parent stores `messages` in a `DeltaChannel`, and its
subagents in a plain `add_messages` channel. Reading a subagent's namespace
with the parent's channel restores its messages, either way round."""
parent, ns_config = await _run_tool_subgraph(
async_checkpointer,
parent_schema=parent_schema,
subagent_schema=subagent_schema,
)
state = await parent.aget_state(ns_config)
history = [s async for s in parent.aget_state_history(ns_config)]
assert [m.text for m in state.values["messages"]] == [
"research apples",
"researcher done",
]
assert [[m.text for m in s.values.get("messages", [])] for s in history] == [
["research apples", "researcher done"],
["research apples"],
[],
]
@NEEDS_CONTEXTVARS
@pytest.mark.parametrize("review_declared", [False, True])
async def test_prefix_named_namespaces_resolve_to_their_own_graph_async(
async_checkpointer: BaseCheckpointSaver, review_declared: bool
) -> None:
"""`research_review` starts with `research`, whose subgraph has a `review`
node with its own subgraph. Each namespace still reads with its own graph:
a declared subgraph's channels, or the parent's when none is declared."""
research = (
StateGraph(NotesState)
.add_node("review", _notes_graph("research"))
.add_edge(START, "review")
.compile()
)
class ReviewState(NotesState):
verdict: str # a key `research|review`'s graph doesn't have
def review(state: ReviewState) -> dict:
return {
"messages": [AIMessage(content="review done")],
"notes": "review",
"verdict": "approved",
}
# Subgraph detection doesn't follow a subscript, so `call_review` declares
# none.
subgraphs = {
"review": StateGraph(ReviewState)
.add_node("work", review)
.add_edge(START, "work")
.compile()
}
async def call_review(state: MessagesState) -> dict:
await subgraphs["review"].ainvoke(
{"messages": [HumanMessage(content="check it")]}
)
return {}
parent = (
StateGraph(MessagesState)
.add_node("research", research)
.add_node(
"research_review",
subgraphs["review"] if review_declared else call_review,
)
.add_edge(START, "research")
.add_edge("research", "research_review")
.compile(checkpointer=async_checkpointer)
)
configs = await _namespace_configs(parent)
async def values(name: str) -> dict:
state = await parent.aget_state(configs[name])
return {
k: [m.text for m in v] if k == "messages" else v
for k, v in state.values.items()
}
assert await values("research") == {
"messages": ["go", "research done"],
"notes": "research",
}
if review_declared:
assert await values("research_review") == {
"messages": ["go", "research done", "review done"],
"notes": "review",
"verdict": "approved",
}
else:
# The parent's channels have no `notes` or `verdict`.
assert await values("research_review") == {
"messages": ["check it", "review done"]
}
def _prefix_named_subgraphs() -> tuple[Any, dict[str, Any]]:
"""A parent whose subgraph namespaces start with one another's names:
`research` has a `review` subgraph, and the parent declares
`research_review` and `review` too. Returns the parent and each subgraph,
keyed by its namespace."""
graphs = {
"research|review": _notes_graph("research's review"),
"research_review": _notes_graph("research_review"),
"review": _notes_graph("review"),
}
graphs["research"] = (
StateGraph(NotesState)
.add_node("review", graphs["research|review"])
.add_edge(START, "review")
.compile()
)
parent = (
StateGraph(NotesState)
.add_node("research", graphs["research"])
.add_node("research_review", graphs["research_review"])
.add_node("review", graphs["review"])
.add_edge(START, "research")
.add_edge("research", "research_review")
.add_edge("research_review", "review")
.compile()
)
return parent, graphs
@pytest.mark.parametrize(
"namespace", ["research", "research|review", "research_review", "review"]
)
async def test_get_subgraphs_matches_whole_namespace_segments_async(
namespace: str,
) -> None:
"""`aget_subgraphs(namespace=...)` yields only the subgraph at that
namespace. `research_review` isn't `research` then `review`, and
`research|review` doesn't also find the parent's own `review`."""
parent, graphs = _prefix_named_subgraphs()
assert [
s async for s in parent.aget_subgraphs(namespace=namespace, recurse=True)
] == [(namespace, graphs[namespace])]
async def test_nested_namespace_update_raises_despite_top_level_namesake_async(
async_checkpointer: BaseCheckpointSaver,
) -> None:
"""`research|review` names a `review` inside `research`'s subgraph, which
declares no subgraph there. An update to it raises rather than writing
through the parent's own `review`."""
research = (
StateGraph(NotesState)
.add_node("review", lambda state: {})
.add_edge(START, "review")
.compile()
)
parent = (
StateGraph(NotesState)
.add_node("research", research)
.add_node("review", _notes_graph("review"))
.add_edge(START, "research")
.add_edge("research", "review")
.compile(checkpointer=async_checkpointer)
)
config: RunnableConfig = {
"configurable": {
"thread_id": str(uuid4()),
"checkpoint_ns": "research:a|review:b",
}
}
with pytest.raises(ValueError, match=r"Subgraph research\|review not found"):
await parent.aupdate_state(config, {"notes": "edit"})
+204 -117
View File
@@ -706,139 +706,139 @@ wheels = [
[[package]]
name = "grpcio"
version = "1.80.0"
version = "1.81.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/b5/1ff353970a87eda4c98251e34d2dfd214abd4982dc89119c9252a2a482d2/grpcio-1.81.1.tar.gz", hash = "sha256:6fa10a767143a5e82e8eaab53918af0cd8909a57a27f8cb2288b80a613ac671b", size = 13026582, upload-time = "2026-06-11T12:46:51.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9d/cd/bb7b7e54084a344c03d68144450da7ddd5564e51a298ae1662de65f48e2d/grpcio-1.80.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:886457a7768e408cdce226ad1ca67d2958917d306523a0e21e1a2fdaa75c9c9c", size = 6050363, upload-time = "2026-03-30T08:46:20.894Z" },
{ url = "https://files.pythonhosted.org/packages/16/02/1417f5c3460dea65f7a2e3c14e8b31e77f7ffb730e9bfadd89eda7a9f477/grpcio-1.80.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:7b641fc3f1dc647bfd80bd713addc68f6d145956f64677e56d9ebafc0bd72388", size = 12026037, upload-time = "2026-03-30T08:46:25.144Z" },
{ url = "https://files.pythonhosted.org/packages/43/98/c910254eedf2cae368d78336a2de0678e66a7317d27c02522392f949b5c6/grpcio-1.80.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:33eb763f18f006dc7fee1e69831d38d23f5eccd15b2e0f92a13ee1d9242e5e02", size = 6602306, upload-time = "2026-03-30T08:46:27.593Z" },
{ url = "https://files.pythonhosted.org/packages/7c/f8/88ca4e78c077b2b2113d95da1e1ab43efd43d723c9a0397d26529c2c1a56/grpcio-1.80.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:52d143637e3872633fc7dd7c3c6a1c84e396b359f3a72e215f8bf69fd82084fc", size = 7301535, upload-time = "2026-03-30T08:46:29.556Z" },
{ url = "https://files.pythonhosted.org/packages/f9/96/f28660fe2fe0f153288bf4a04e4910b7309d442395135c88ed4f5b3b8b40/grpcio-1.80.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c51bf8ac4575af2e0678bccfb07e47321fc7acb5049b4482832c5c195e04e13a", size = 6808669, upload-time = "2026-03-30T08:46:31.984Z" },
{ url = "https://files.pythonhosted.org/packages/47/eb/3f68a5e955779c00aeef23850e019c1c1d0e032d90633ba49c01ad5a96e0/grpcio-1.80.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:50a9871536d71c4fba24ee856abc03a87764570f0c457dd8db0b4018f379fed9", size = 7409489, upload-time = "2026-03-30T08:46:34.684Z" },
{ url = "https://files.pythonhosted.org/packages/5b/a7/d2f681a4bfb881be40659a309771f3bdfbfdb1190619442816c3f0ffc079/grpcio-1.80.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a72d84ad0514db063e21887fbacd1fd7acb4d494a564cae22227cd45c7fbf199", size = 8423167, upload-time = "2026-03-30T08:46:36.833Z" },
{ url = "https://files.pythonhosted.org/packages/97/8a/29b4589c204959aa35ce5708400a05bba72181807c45c47b3ec000c39333/grpcio-1.80.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f7691a6788ad9196872f95716df5bc643ebba13c97140b7a5ee5c8e75d1dea81", size = 7846761, upload-time = "2026-03-30T08:46:40.091Z" },
{ url = "https://files.pythonhosted.org/packages/6b/d2/ed143e097230ee121ac5848f6ff14372dba91289b10b536d54fb1b7cbae7/grpcio-1.80.0-cp310-cp310-win32.whl", hash = "sha256:46c2390b59d67f84e882694d489f5b45707c657832d7934859ceb8c33f467069", size = 4156534, upload-time = "2026-03-30T08:46:42.026Z" },
{ url = "https://files.pythonhosted.org/packages/d5/c9/df8279bb49b29409995e95efa85b72973d62f8aeff89abee58c91f393710/grpcio-1.80.0-cp310-cp310-win_amd64.whl", hash = "sha256:dc053420fc75749c961e2a4c906398d7c15725d36ccc04ae6d16093167223b58", size = 4889869, upload-time = "2026-03-30T08:46:44.219Z" },
{ url = "https://files.pythonhosted.org/packages/5d/db/1d56e5f5823257b291962d6c0ce106146c6447f405b60b234c4f222a7cde/grpcio-1.80.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:dfab85db094068ff42e2a3563f60ab3dddcc9d6488a35abf0132daec13209c8a", size = 6055009, upload-time = "2026-03-30T08:46:46.265Z" },
{ url = "https://files.pythonhosted.org/packages/6e/18/c83f3cad64c5ca63bca7e91e5e46b0d026afc5af9d0a9972472ceba294b3/grpcio-1.80.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5c07e82e822e1161354e32da2662f741a4944ea955f9f580ec8fb409dd6f6060", size = 12035295, upload-time = "2026-03-30T08:46:49.099Z" },
{ url = "https://files.pythonhosted.org/packages/0f/8e/e14966b435be2dda99fbe89db9525ea436edc79780431a1c2875a3582644/grpcio-1.80.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba0915d51fd4ced2db5ff719f84e270afe0e2d4c45a7bdb1e8d036e4502928c2", size = 6610297, upload-time = "2026-03-30T08:46:52.123Z" },
{ url = "https://files.pythonhosted.org/packages/cc/26/d5eb38f42ce0e3fdc8174ea4d52036ef8d58cc4426cb800f2610f625dd75/grpcio-1.80.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3cb8130ba457d2aa09fa6b7c3ed6b6e4e6a2685fce63cb803d479576c4d80e21", size = 7300208, upload-time = "2026-03-30T08:46:54.859Z" },
{ url = "https://files.pythonhosted.org/packages/25/51/bd267c989f85a17a5b3eea65a6feb4ff672af41ca614e5a0279cc0ea381c/grpcio-1.80.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09e5e478b3d14afd23f12e49e8b44c8684ac3c5f08561c43a5b9691c54d136ab", size = 6813442, upload-time = "2026-03-30T08:46:57.056Z" },
{ url = "https://files.pythonhosted.org/packages/9e/d9/d80eef735b19e9169e30164bbf889b46f9df9127598a83d174eb13a48b26/grpcio-1.80.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:00168469238b022500e486c1c33916acf2f2a9b2c022202cf8a1885d2e3073c1", size = 7414743, upload-time = "2026-03-30T08:46:59.682Z" },
{ url = "https://files.pythonhosted.org/packages/de/f2/567f5bd5054398ed6b0509b9a30900376dcf2786bd936812098808b49d8d/grpcio-1.80.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8502122a3cc1714038e39a0b071acb1207ca7844208d5ea0d091317555ee7106", size = 8426046, upload-time = "2026-03-30T08:47:02.474Z" },
{ url = "https://files.pythonhosted.org/packages/62/29/73ef0141b4732ff5eacd68430ff2512a65c004696997f70476a83e548e7e/grpcio-1.80.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce1794f4ea6cc3ca29463f42d665c32ba1b964b48958a66497917fe9069f26e6", size = 7851641, upload-time = "2026-03-30T08:47:05.462Z" },
{ url = "https://files.pythonhosted.org/packages/46/69/abbfa360eb229a8623bab5f5a4f8105e445bd38ce81a89514ba55d281ad0/grpcio-1.80.0-cp311-cp311-win32.whl", hash = "sha256:51b4a7189b0bef2aa30adce3c78f09c83526cf3dddb24c6a96555e3b97340440", size = 4154368, upload-time = "2026-03-30T08:47:08.027Z" },
{ url = "https://files.pythonhosted.org/packages/6f/d4/ae92206d01183b08613e846076115f5ac5991bae358d2a749fa864da5699/grpcio-1.80.0-cp311-cp311-win_amd64.whl", hash = "sha256:02e64bb0bb2da14d947a49e6f120a75e947250aebe65f9629b62bb1f5c14e6e9", size = 4894235, upload-time = "2026-03-30T08:47:10.839Z" },
{ url = "https://files.pythonhosted.org/packages/5c/e8/a2b749265eb3415abc94f2e619bbd9e9707bebdda787e61c593004ec927a/grpcio-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:c624cc9f1008361014378c9d776de7182b11fe8b2e5a81bc69f23a295f2a1ad0", size = 6015616, upload-time = "2026-03-30T08:47:13.428Z" },
{ url = "https://files.pythonhosted.org/packages/3e/97/b1282161a15d699d1e90c360df18d19165a045ce1c343c7f313f5e8a0b77/grpcio-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f49eddcac43c3bf350c0385366a58f36bed8cc2c0ec35ef7b74b49e56552c0c2", size = 12014204, upload-time = "2026-03-30T08:47:15.873Z" },
{ url = "https://files.pythonhosted.org/packages/6e/5e/d319c6e997b50c155ac5a8cb12f5173d5b42677510e886d250d50264949d/grpcio-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d334591df610ab94714048e0d5b4f3dd5ad1bee74dfec11eee344220077a79de", size = 6563866, upload-time = "2026-03-30T08:47:18.588Z" },
{ url = "https://files.pythonhosted.org/packages/ae/f6/fdd975a2cb4d78eb67769a7b3b3830970bfa2e919f1decf724ae4445f42c/grpcio-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0cb517eb1d0d0aaf1d87af7cc5b801d686557c1d88b2619f5e31fab3c2315921", size = 7273060, upload-time = "2026-03-30T08:47:21.113Z" },
{ url = "https://files.pythonhosted.org/packages/db/f0/a3deb5feba60d9538a962913e37bd2e69a195f1c3376a3dd44fe0427e996/grpcio-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e78c4ac0d97dc2e569b2f4bcbbb447491167cb358d1a389fc4af71ab6f70411", size = 6782121, upload-time = "2026-03-30T08:47:23.827Z" },
{ url = "https://files.pythonhosted.org/packages/ca/84/36c6dcfddc093e108141f757c407902a05085e0c328007cb090d56646cdf/grpcio-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2ed770b4c06984f3b47eb0517b1c69ad0b84ef3f40128f51448433be904634cd", size = 7383811, upload-time = "2026-03-30T08:47:26.517Z" },
{ url = "https://files.pythonhosted.org/packages/7c/ef/f3a77e3dc5b471a0ec86c564c98d6adfa3510d38f8ee99010410858d591e/grpcio-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:256507e2f524092f1473071a05e65a5b10d84b82e3ff24c5b571513cfaa61e2f", size = 8393860, upload-time = "2026-03-30T08:47:29.439Z" },
{ url = "https://files.pythonhosted.org/packages/9b/8d/9d4d27ed7f33d109c50d6b5ce578a9914aa68edab75d65869a17e630a8d1/grpcio-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a6284a5d907c37db53350645567c522be314bac859a64a7a5ca63b77bb7958f", size = 7830132, upload-time = "2026-03-30T08:47:33.254Z" },
{ url = "https://files.pythonhosted.org/packages/14/e4/9990b41c6d7a44e1e9dee8ac11d7a9802ba1378b40d77468a7761d1ad288/grpcio-1.80.0-cp312-cp312-win32.whl", hash = "sha256:c71309cfce2f22be26aa4a847357c502db6c621f1a49825ae98aa0907595b193", size = 4140904, upload-time = "2026-03-30T08:47:35.319Z" },
{ url = "https://files.pythonhosted.org/packages/2f/2c/296f6138caca1f4b92a31ace4ae1b87dab692fc16a7a3417af3bb3c805bf/grpcio-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe648599c0e37594c4809d81a9e77bd138cc82eb8baa71b6a86af65426723ff", size = 4880944, upload-time = "2026-03-30T08:47:37.831Z" },
{ url = "https://files.pythonhosted.org/packages/2f/3a/7c3c25789e3f069e581dc342e03613c5b1cb012c4e8c7d9d5cf960a75856/grpcio-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e9e408fc016dffd20661f0126c53d8a31c2821b5c13c5d67a0f5ed5de93319ad", size = 6017243, upload-time = "2026-03-30T08:47:40.075Z" },
{ url = "https://files.pythonhosted.org/packages/04/19/21a9806eb8240e174fd1ab0cd5b9aa948bb0e05c2f2f55f9d5d7405e6d08/grpcio-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:92d787312e613754d4d8b9ca6d3297e69994a7912a32fa38c4c4e01c272974b0", size = 12010840, upload-time = "2026-03-30T08:47:43.11Z" },
{ url = "https://files.pythonhosted.org/packages/18/3a/23347d35f76f639e807fb7a36fad3068aed100996849a33809591f26eca6/grpcio-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac393b58aa16991a2f1144ec578084d544038c12242da3a215966b512904d0f", size = 6567644, upload-time = "2026-03-30T08:47:46.806Z" },
{ url = "https://files.pythonhosted.org/packages/ff/40/96e07ecb604a6a67ae6ab151e3e35b132875d98bc68ec65f3e5ab3e781d7/grpcio-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:68e5851ac4b9afe07e7f84483803ad167852570d65326b34d54ca560bfa53fb6", size = 7277830, upload-time = "2026-03-30T08:47:49.643Z" },
{ url = "https://files.pythonhosted.org/packages/9b/e2/da1506ecea1f34a5e365964644b35edef53803052b763ca214ba3870c856/grpcio-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:873ff5d17d68992ef6605330127425d2fc4e77e612fa3c3e0ed4e668685e3140", size = 6783216, upload-time = "2026-03-30T08:47:52.817Z" },
{ url = "https://files.pythonhosted.org/packages/44/83/3b20ff58d0c3b7f6caaa3af9a4174d4023701df40a3f39f7f1c8e7c48f9d/grpcio-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2bea16af2750fd0a899bf1abd9022244418b55d1f37da2202249ba4ba673838d", size = 7385866, upload-time = "2026-03-30T08:47:55.687Z" },
{ url = "https://files.pythonhosted.org/packages/47/45/55c507599c5520416de5eefecc927d6a0d7af55e91cfffb2e410607e5744/grpcio-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba0db34f7e1d803a878284cd70e4c63cb6ae2510ba51937bf8f45ba997cefcf7", size = 8391602, upload-time = "2026-03-30T08:47:58.303Z" },
{ url = "https://files.pythonhosted.org/packages/10/bb/dd06f4c24c01db9cf11341b547d0a016b2c90ed7dbbb086a5710df7dd1d7/grpcio-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8eb613f02d34721f1acf3626dfdb3545bd3c8505b0e52bf8b5710a28d02e8aa7", size = 7826752, upload-time = "2026-03-30T08:48:01.311Z" },
{ url = "https://files.pythonhosted.org/packages/f9/1e/9d67992ba23371fd63d4527096eb8c6b76d74d52b500df992a3343fd7251/grpcio-1.80.0-cp313-cp313-win32.whl", hash = "sha256:93b6f823810720912fd131f561f91f5fed0fda372b6b7028a2681b8194d5d294", size = 4142310, upload-time = "2026-03-30T08:48:04.594Z" },
{ url = "https://files.pythonhosted.org/packages/cf/e6/283326a27da9e2c3038bc93eeea36fb118ce0b2d03922a9cda6688f53c5b/grpcio-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:e172cf795a3ba5246d3529e4d34c53db70e888fa582a8ffebd2e6e48bc0cba50", size = 4882833, upload-time = "2026-03-30T08:48:07.363Z" },
{ url = "https://files.pythonhosted.org/packages/c5/6d/e65307ce20f5a09244ba9e9d8476e99fb039de7154f37fb85f26978b59c3/grpcio-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3d4147a97c8344d065d01bbf8b6acec2cf86fb0400d40696c8bdad34a64ffc0e", size = 6017376, upload-time = "2026-03-30T08:48:10.005Z" },
{ url = "https://files.pythonhosted.org/packages/69/10/9cef5d9650c72625a699c549940f0abb3c4bfdb5ed45a5ce431f92f31806/grpcio-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8e11f167935b3eb089ac9038e1a063e6d7dbe995c0bb4a661e614583352e76f", size = 12018133, upload-time = "2026-03-30T08:48:12.927Z" },
{ url = "https://files.pythonhosted.org/packages/04/82/983aabaad82ba26113caceeb9091706a0696b25da004fe3defb5b346e15b/grpcio-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f14b618fc30de822681ee986cfdcc2d9327229dc4c98aed16896761cacd468b9", size = 6574748, upload-time = "2026-03-30T08:48:16.386Z" },
{ url = "https://files.pythonhosted.org/packages/07/d7/031666ef155aa0bf399ed7e19439656c38bbd143779ae0861b038ce82abd/grpcio-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4ed39fbdcf9b87370f6e8df4e39ca7b38b3e5e9d1b0013c7b6be9639d6578d14", size = 7277711, upload-time = "2026-03-30T08:48:19.627Z" },
{ url = "https://files.pythonhosted.org/packages/e8/43/f437a78f7f4f1d311804189e8f11fb311a01049b2e08557c1068d470cb2e/grpcio-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dcc70e9f0ba987526e8e8603a610fb4f460e42899e74e7a518bf3c68fe1bf05", size = 6785372, upload-time = "2026-03-30T08:48:22.373Z" },
{ url = "https://files.pythonhosted.org/packages/93/3d/f6558e9c6296cb4227faa5c43c54a34c68d32654b829f53288313d16a86e/grpcio-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448c884b668b868562b1bda833c5fce6272d26e1926ec46747cda05741d302c1", size = 7395268, upload-time = "2026-03-30T08:48:25.638Z" },
{ url = "https://files.pythonhosted.org/packages/06/21/0fdd77e84720b08843c371a2efa6f2e19dbebf56adc72df73d891f5506f0/grpcio-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a1dc80fe55685b4a543555e6eef975303b36c8db1023b1599b094b92aa77965f", size = 8392000, upload-time = "2026-03-30T08:48:28.974Z" },
{ url = "https://files.pythonhosted.org/packages/f5/68/67f4947ed55d2e69f2cc199ab9fd85e0a0034d813bbeef84df6d2ba4d4b7/grpcio-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:31b9ac4ad1aa28ffee5503821fafd09e4da0a261ce1c1281c6c8da0423c83b6e", size = 7828477, upload-time = "2026-03-30T08:48:32.054Z" },
{ url = "https://files.pythonhosted.org/packages/44/b6/8d4096691b2e385e8271911a0de4f35f0a6c7d05aff7098e296c3de86939/grpcio-1.80.0-cp314-cp314-win32.whl", hash = "sha256:367ce30ba67d05e0592470428f0ec1c31714cab9ef19b8f2e37be1f4c7d32fae", size = 4218563, upload-time = "2026-03-30T08:48:34.538Z" },
{ url = "https://files.pythonhosted.org/packages/e5/8c/bbe6baf2557262834f2070cf668515fa308b2d38a4bbf771f8f7872a7036/grpcio-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:3b01e1f5464c583d2f567b2e46ff0d516ef979978f72091fd81f5ab7fa6e2e7f", size = 5019457, upload-time = "2026-03-30T08:48:37.308Z" },
{ url = "https://files.pythonhosted.org/packages/9a/d5/f2b159d8eec08be2a855ef698f5b6f7f9fdda022e4dd9e4f5d968affd678/grpcio-1.81.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:6f9a0c9c1cc15c112d1c053064fd032b64917062292c3d70aea280e02ae10b77", size = 6086868, upload-time = "2026-06-11T12:44:19.364Z" },
{ url = "https://files.pythonhosted.org/packages/80/41/9c95232b94b219ed8b14029d9cd000e0381cafba869c451dda60af84f4ba/grpcio-1.81.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:69ef28e54fc85397f91b8c19592b8ef3d81952080366914823bd8572a2958120", size = 12062291, upload-time = "2026-06-11T12:44:27.142Z" },
{ url = "https://files.pythonhosted.org/packages/83/8b/bd9284bdd665ddf877a3e8bc2930d1bcf6ebdbae7b0da5c783dc26bd6e33/grpcio-1.81.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:15641444eca4a29358107b3dceb74c1c6305c55c822fd199b458aaea4068a7fb", size = 6635242, upload-time = "2026-06-11T12:44:30.741Z" },
{ url = "https://files.pythonhosted.org/packages/60/24/78fa025517a925f1a17da71c4ef9d5f1c6f9fa65af22dfb523c5c6317a21/grpcio-1.81.1-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d4b2dddfc219f54f956ccd53cf76a1d338ffe68fc7f2849ec9c7feb9927ff692", size = 7332974, upload-time = "2026-06-11T12:44:33.72Z" },
{ url = "https://files.pythonhosted.org/packages/f7/11/402295b388dd35861007f8a26a37c2e2f284212d57bdf407c31f36043746/grpcio-1.81.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ca1cc11d82677b9662082e5478b7528e2b7db7beaa6bdff42bd62789d81be399", size = 6836597, upload-time = "2026-06-11T12:44:36.108Z" },
{ url = "https://files.pythonhosted.org/packages/4d/71/37b10fd4fd579ffade6e695c14e9df5e8cba9e2365b81c131da438b67c34/grpcio-1.81.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa2ba7d2ad6df4d80127cea65e5b8d5e2c3adbf153ff4804452836328aca7c54", size = 7440660, upload-time = "2026-06-11T12:44:38.664Z" },
{ url = "https://files.pythonhosted.org/packages/4a/d5/40203f828abc83d458b634666df6df13778032f178c03845ad5a93682388/grpcio-1.81.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:592b5fee597faa91cce2dd294dd7d9a1c83d76c4dbf877e33ec1adb866b2fbed", size = 8443171, upload-time = "2026-06-11T12:44:41.678Z" },
{ url = "https://files.pythonhosted.org/packages/eb/2c/0ed82ea35b5ec595e10444940c1db8c0e0ef57aa46bc8797d5ff838a219e/grpcio-1.81.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62481553b1793a27e9b9c3cf9e5bd483ef045ca72462592074b46d42b0c4d9b9", size = 7868905, upload-time = "2026-06-11T12:44:44.854Z" },
{ url = "https://files.pythonhosted.org/packages/9e/1f/dcbdc1a68a07cc2b631c3098953794f17d75f93426a019240b90ce5423d6/grpcio-1.81.1-cp310-cp310-win32.whl", hash = "sha256:bb693b1e3d9a2f3fd228e2110daf4b5aeedb36761ca1e4282f74725f6d89f611", size = 4202215, upload-time = "2026-06-11T12:44:47.165Z" },
{ url = "https://files.pythonhosted.org/packages/75/a1/d7ab9f1f42efcb7d9e6111d38be6b367737a72ea2c534e1f55c81e1b6436/grpcio-1.81.1-cp310-cp310-win_amd64.whl", hash = "sha256:88268ca418cacea64cecb0d1d600d3c6b3a8038fcba02e1e205178c5b1f47661", size = 4936582, upload-time = "2026-06-11T12:44:49.479Z" },
{ url = "https://files.pythonhosted.org/packages/52/ea/1c2fa386b718ff493225e61cfc052ef400b4d6ffc54cbe261026432624b5/grpcio-1.81.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:d71d30f2d92f67d944631c523713934fee37292469e182ebcd2c1dd8a64ce53f", size = 6093112, upload-time = "2026-06-11T12:44:52.131Z" },
{ url = "https://files.pythonhosted.org/packages/2b/18/acf45fa8bd1bc5d7b0c2fd3dc4c209379fbd5bb396b440b68a83342226b7/grpcio-1.81.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b137f4bf3ada9dc44d411478decc6ff09a79ed30b306cd2abaa98408c3588137", size = 12074277, upload-time = "2026-06-11T12:44:55.354Z" },
{ url = "https://files.pythonhosted.org/packages/48/d7/ee86a60699b7db039f772a2c4a7e4facc7138984ff42c0130933a0063884/grpcio-1.81.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a3acb384427816dd5d470f47e62137b87f74da694faa8a50147012cf40df276a", size = 6640348, upload-time = "2026-06-11T12:44:59.223Z" },
{ url = "https://files.pythonhosted.org/packages/26/ee/d2de5e47378ffc207d476c230fea3be4d2601edbce9995f4fe45535d4896/grpcio-1.81.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f9a0ebbe45c29b5e5866593c12b78bd9035f0f0f0d4bc8361680cd580d99db49", size = 7331842, upload-time = "2026-06-11T12:45:02.001Z" },
{ url = "https://files.pythonhosted.org/packages/23/d6/abeda5c2b896a0b341584fe5ac411bbf72e197a9a374c355fb90965e08d2/grpcio-1.81.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a37165cc80b1a368384b383e63a4c38116a10467ae44c904d2d7468c4470ec2", size = 6842229, upload-time = "2026-06-11T12:45:04.76Z" },
{ url = "https://files.pythonhosted.org/packages/10/1c/1f0da7d590b4aeee006826ba568d0e419ca14b23e18f901a3da3e9fba613/grpcio-1.81.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6282caffb41ec326d4cb67ca9cf53b739d1b2f975a2acb498c7418e9f7d9a416", size = 7446096, upload-time = "2026-06-11T12:45:07.499Z" },
{ url = "https://files.pythonhosted.org/packages/6a/81/5c505d508f7c887aa7982d21443a4126597c80d34b0bcf40f9cec576d7f3/grpcio-1.81.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a35009284d0d3d5c2c9601c164a911b8b4331608d98a9a66d47d97bb2f522b70", size = 8445238, upload-time = "2026-06-11T12:45:10.243Z" },
{ url = "https://files.pythonhosted.org/packages/f7/b2/524847365122ee509ca17bcc4e092198b700e94af7bfd5bb5e6dd9f3ee66/grpcio-1.81.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1b22c80559854b789a01fd89e8929b3798a156c0829b5282a8939f33ad4115ad", size = 7873989, upload-time = "2026-06-11T12:45:13.102Z" },
{ url = "https://files.pythonhosted.org/packages/18/fa/07c037c50b006909d1d13a5848774f8aa7b242f70dc03a035c64eea0e6db/grpcio-1.81.1-cp311-cp311-win32.whl", hash = "sha256:428bec0161b48d8cf583c068591bc0016d0d9cfff52462b72b3884861ea768c5", size = 4202223, upload-time = "2026-06-11T12:45:16.166Z" },
{ url = "https://files.pythonhosted.org/packages/41/ed/6bff15376920942fac6b95b9802752b837437172c9e8fc2d3170546b89cc/grpcio-1.81.1-cp311-cp311-win_amd64.whl", hash = "sha256:30e825f6848d9f18bba350ed6c75c1b02a0b5184474a31db9a32b1fa66fd8c79", size = 4941303, upload-time = "2026-06-11T12:45:18.724Z" },
{ url = "https://files.pythonhosted.org/packages/85/07/9a979c81738863a738dc23d65177056e71fbb2db817740ed870b33434e7a/grpcio-1.81.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:8b39472beafc0bdcafc4c8c73ad082ebfdb449d566897a61e7acb4fa88089115", size = 6053264, upload-time = "2026-06-11T12:45:21.017Z" },
{ url = "https://files.pythonhosted.org/packages/75/95/539706ca0d3bd40dbad583dc56fd883da941f37556b629132da5762781b9/grpcio-1.81.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:12b7524c88d4026d3dcb7b0ebe16b6714f3b4af402ddd0f0639ab064a00c87c3", size = 12052560, upload-time = "2026-06-11T12:45:23.652Z" },
{ url = "https://files.pythonhosted.org/packages/e0/44/f257b7e0bd69c93b06c6cb8ac8d1b901ccb42bedabd83c1a4c77a71f8810/grpcio-1.81.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1e123f9b37edb8375fd74130d1f69c944bbf0a7b06761ae7211154b8759e94d2", size = 6595983, upload-time = "2026-06-11T12:45:26.963Z" },
{ url = "https://files.pythonhosted.org/packages/b9/f3/19782aa04c960968bef8c5539329d8e3bbc3364e2e46d19eb5e5cc5e43b7/grpcio-1.81.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2c2e2ae6867c2966b8daccc836d54a13218e0007e9a490aeb81dd05be64d22d7", size = 7303455, upload-time = "2026-06-11T12:45:29.707Z" },
{ url = "https://files.pythonhosted.org/packages/eb/8c/dea020b6d91508cd84463917a63149ec196ee7db505d032ae43fcb3303b9/grpcio-1.81.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:766bc7c9a9c340342f4c864ccbda8e78111e4751f13b895812b9c148fb79e9d0", size = 6809167, upload-time = "2026-06-11T12:45:32.52Z" },
{ url = "https://files.pythonhosted.org/packages/1c/c7/3030dd940408083bd32cd95d634777a71605ade4887154d93e8a89244946/grpcio-1.81.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b259a04a737cb3496be0901328eb8b7552ed8df4865d8c8f1cf1bffcfc0776a3", size = 7412536, upload-time = "2026-06-11T12:45:35.403Z" },
{ url = "https://files.pythonhosted.org/packages/e0/dd/1172a9e42b168edcafefad6115346ef619a3fc02158bb170e66ced24bcdd/grpcio-1.81.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:85b10a45b8993d195c4f3ff57025b8d1e11834909ee475c403bfa60cb4caefaf", size = 8408276, upload-time = "2026-06-11T12:45:37.78Z" },
{ url = "https://files.pythonhosted.org/packages/25/7a/71437c7f3596e5246155c515852795a85a1a8d228190212432b13b97a95d/grpcio-1.81.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8ea1936c26b99999b27479853039a7f34713f56c49375ad52b38535ec93a796c", size = 7849660, upload-time = "2026-06-11T12:45:40.627Z" },
{ url = "https://files.pythonhosted.org/packages/65/40/7debc0da45d2efebafb82da75644be347497fe4ee250514b8cd3b86ae8bf/grpcio-1.81.1-cp312-cp312-win32.whl", hash = "sha256:a185a04039df6cae8648bc8ab6d6fde7bf94f7188ecf7828e76ac52eef1e41d6", size = 4185819, upload-time = "2026-06-11T12:45:43.027Z" },
{ url = "https://files.pythonhosted.org/packages/2e/b9/8fe3ba5ed462067774ebc1f9c7f26aa7ebcc280ddd476be107153de1339e/grpcio-1.81.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ad74f8bb1a18963914c5452d289422830b39459e8776ebbcd207be1fbfb1d94", size = 4930461, upload-time = "2026-06-11T12:45:45.775Z" },
{ url = "https://files.pythonhosted.org/packages/7a/42/dcc2e4b600538ef18327c0839d56b7d3c3812337c5d710df5877dbb39b1e/grpcio-1.81.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b10e1ff4756ed27d5a29d7fc79cfce7ef1ff56ad20025b89bac7cf79e09abbbe", size = 6054466, upload-time = "2026-06-11T12:45:48.43Z" },
{ url = "https://files.pythonhosted.org/packages/7b/4a/a36e03210183a8a7d4c80c3936acee679f4bd77d5861f369db47b2cc5f05/grpcio-1.81.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:819edbdcb42ab8598b494bcf0222684bbb7a3c772bd1b1f0be7e029a6063c28e", size = 12048795, upload-time = "2026-06-11T12:45:54.011Z" },
{ url = "https://files.pythonhosted.org/packages/b0/d5/d68e30b29098f63beab6fe501100fe82674ff142b32c672532da86a99b3a/grpcio-1.81.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5bf2dc311127d91230cc79b92188c082634a06cf66c5234db49a43b910183b0", size = 6599094, upload-time = "2026-06-11T12:45:57.799Z" },
{ url = "https://files.pythonhosted.org/packages/3d/b3/e837954d279754f638a11cca5dcf6b24a005efb398984cefaf7735945a54/grpcio-1.81.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e8ca6a1fcdb2943c9cbc1804a1baf3acb6071d72a471591678ded84218006e14", size = 7307182, upload-time = "2026-06-11T12:46:00.568Z" },
{ url = "https://files.pythonhosted.org/packages/0d/1e/b47957057e729adc6cdf519a47f8be2562b7140e280f1418443eb4022192/grpcio-1.81.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e64dd101d380a115cc5a0c7856788adb535f1a4e21fc543775602f8be95180ae", size = 6810962, upload-time = "2026-06-11T12:46:03.312Z" },
{ url = "https://files.pythonhosted.org/packages/40/26/569868e364e05b19ec8f969da53d230bcd89c962cd198f7c29943155c4d3/grpcio-1.81.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:98a07f9bf591e3a8919797bee1c53f026ba4acd587e5a4404c8e57c9ec36b2a5", size = 7415698, upload-time = "2026-06-11T12:46:06.005Z" },
{ url = "https://files.pythonhosted.org/packages/36/0c/5440a0582cb5653fc42a6e262eeb22700943313f8076f9dc927491b20a59/grpcio-1.81.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c261d74b1a945cf895a9d6eccd1685a8e837531beaab782da4d630a8d12deffb", size = 8407779, upload-time = "2026-06-11T12:46:08.84Z" },
{ url = "https://files.pythonhosted.org/packages/ff/aa/66fe9f39871d766987d869a03ee0842a026f499c7b1e62decb9e78a8088e/grpcio-1.81.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58ad1131c300d3c9b933802b3cc4dc69d380822935ba50b28703156ea826fbf7", size = 7844521, upload-time = "2026-06-11T12:46:12.171Z" },
{ url = "https://files.pythonhosted.org/packages/f0/9e/69bb7194861bcd28fb3193261d4f9c3831b4446993f002cf59068943e7ab/grpcio-1.81.1-cp313-cp313-win32.whl", hash = "sha256:78e29211f26da2fdd0e9c6d2b79f489476140cf7029b6a64808ade7ca4156a42", size = 4182786, upload-time = "2026-06-11T12:46:15.192Z" },
{ url = "https://files.pythonhosted.org/packages/0d/20/3da8bb0d637feccdc3e1e419bb511ce93651ce7d54164f95de22cc0b8b34/grpcio-1.81.1-cp313-cp313-win_amd64.whl", hash = "sha256:edb59506291b647a30884b1d51a599d605f40b20af4a7dc3d33786a47a31de60", size = 4928648, upload-time = "2026-06-11T12:46:17.823Z" },
{ url = "https://files.pythonhosted.org/packages/b6/58/19414622b1bf6981bc9c05a365bd548e71876c89000083b3af489251e9c0/grpcio-1.81.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:506f48f2f9c29b143fca3dad7b0d518c188b6c9648c75a2ae6e2d9f2c13a060b", size = 6055336, upload-time = "2026-06-11T12:46:20.557Z" },
{ url = "https://files.pythonhosted.org/packages/32/f1/2ec88adb92b0eba970dd0e0e7dd086341daa3c75eba4f735f9e44bf684b0/grpcio-1.81.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d865db4a6318e1c1bea83292e0ed231090538fc4ca45425b0f0480eb338bbc6e", size = 12056279, upload-time = "2026-06-11T12:46:24.255Z" },
{ url = "https://files.pythonhosted.org/packages/41/36/e8c5f8c6ec71de73733695ebc809e98b178b534ec6d8eaa31a7ebab4ad4c/grpcio-1.81.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2aa72e3ce1770317ef534f63d397b55e130725f5149bd36077c3b539019db27", size = 6608225, upload-time = "2026-06-11T12:46:27.601Z" },
{ url = "https://files.pythonhosted.org/packages/30/22/96fc577a845ab093326d9ab1adb874bd4936c8cf98ac8ed2f3db13a0a2fb/grpcio-1.81.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0490c30c261eded63f3f354979f9dc4502a9fb944cccb60cd9dc85f5a7349854", size = 7306576, upload-time = "2026-06-11T12:46:30.514Z" },
{ url = "https://files.pythonhosted.org/packages/76/7b/61dab5d5969f28d97fb1009cead1df0a5cd987d3315e1b37f18a4449f8bc/grpcio-1.81.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:410482da976329fe5f4067270401b12cf2bd552ff8020f054ecfaddb5475f9d6", size = 6812165, upload-time = "2026-06-11T12:46:33.699Z" },
{ url = "https://files.pythonhosted.org/packages/82/78/6e501929d4f5f96462fd82fd9f0f06e5f9612207582b862868d68757b27d/grpcio-1.81.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3657301562ac3cb8018d30d0d3ebfa39932239f7b5703422057ef14b69949f5", size = 7422962, upload-time = "2026-06-11T12:46:36.511Z" },
{ url = "https://files.pythonhosted.org/packages/2a/7e/f2157589e66daa78ebb3165942d05a08bdea93b9d11c2bc1e172aef89685/grpcio-1.81.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24c8e57504c8f45b237e40b99262d181071e5099a07053695b75d97bb53053a0", size = 8408176, upload-time = "2026-06-11T12:46:39.803Z" },
{ url = "https://files.pythonhosted.org/packages/da/df/c6717fef716e00d235ffb96123baf6dce76d6004f6233fa767c502861460/grpcio-1.81.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b427c19380991a4eaab2f6144b64b99b412043314c6bf4ab544f97bb31ee4190", size = 7846681, upload-time = "2026-06-11T12:46:43.013Z" },
{ url = "https://files.pythonhosted.org/packages/36/84/3502e9f210a6a5c4438c8aca3f88edd2e04f6a27f3d41b26cf0a0024b096/grpcio-1.81.1-cp314-cp314-win32.whl", hash = "sha256:61233fe8951e5c85dff81c2458b6528624760166946b5b47ea150a589168411f", size = 4264615, upload-time = "2026-06-11T12:46:45.741Z" },
{ url = "https://files.pythonhosted.org/packages/ff/b0/4af731ff7492c68a96e4c71bfd0f4590acde92b31c6fe4894e6465c10ff6/grpcio-1.81.1-cp314-cp314-win_amd64.whl", hash = "sha256:3768a5ff1b2125e6f552e561b6b2dca0e64982d8949689b4df145cf8b98d7821", size = 5070275, upload-time = "2026-06-11T12:46:48.486Z" },
]
[[package]]
name = "grpcio-health-checking"
version = "1.80.0"
version = "1.81.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "grpcio" },
{ name = "protobuf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d1/a2/aa3cc47f19c03f8e5287b987317059753141a3af8f66b96d5a64b3be10b8/grpcio_health_checking-1.80.0.tar.gz", hash = "sha256:2cc5f08bc8b816b8655ab6f59c71450063ba20766d31e21a493e912e3560c8b1", size = 17117, upload-time = "2026-03-30T08:54:41.899Z" }
sdist = { url = "https://files.pythonhosted.org/packages/65/46/6b6678b5a922765ae7637205bb6d0618a4da8b35f2ce6116f8bcff262370/grpcio_health_checking-1.81.1.tar.gz", hash = "sha256:ecc61480e25058a4a04e11e4ab6900ad7439b32e60a8ce4ece7d9f219221c85d", size = 17107, upload-time = "2026-06-11T12:58:49.632Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5e/d1/d97eb30386feff6ac2a662620e2ed68be352e9a182d62e06213db694906a/grpcio_health_checking-1.80.0-py3-none-any.whl", hash = "sha256:d804d4549cbb71e90ca2c7bf0c501060135dfd220aca8e2c54f96d3e79e210e5", size = 19125, upload-time = "2026-03-30T08:53:44.835Z" },
{ url = "https://files.pythonhosted.org/packages/6e/87/174bbfb5613794862c45b5cdf567fcfb478ad5a3a7b594e11d998656b2f9/grpcio_health_checking-1.81.1-py3-none-any.whl", hash = "sha256:cbc6a4171825ec64389de2f062d296ba129a5c27eefd0dd55fa837909184bdf9", size = 19120, upload-time = "2026-06-11T12:58:38.008Z" },
]
[[package]]
name = "grpcio-tools"
version = "1.80.0"
version = "1.81.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "grpcio" },
{ name = "protobuf" },
{ name = "setuptools" },
]
sdist = { url = "https://files.pythonhosted.org/packages/94/c8/1223f29c84a143ae9a56c084fc96894de0ba84b6e8d60a26241abd81d278/grpcio_tools-1.80.0.tar.gz", hash = "sha256:26052b19c6ce0dcf52d1024496aea3e2bdfa864159f06dc7b97b22d041a94b26", size = 6133212, upload-time = "2026-03-30T08:52:39.077Z" }
sdist = { url = "https://files.pythonhosted.org/packages/83/b3/1c5951352d6777fd7f99a0ccee04617fdfd8a5dbf2918a1f58c8b2b280b8/grpcio_tools-1.81.1.tar.gz", hash = "sha256:a22a3870180927fdd84e2b27d079ef5b7f5f8c6110181b6736afc17a463481f1", size = 6236155, upload-time = "2026-06-11T12:51:21.235Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7a/54/1de67f5080da305a258758a8deb33f85666fa759f56785042a80b114a53f/grpcio_tools-1.80.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:727477b9afa4b53f5ec70cafb41c3965d893835e0d4ea9b542fe3d0d005602bf", size = 2549601, upload-time = "2026-03-30T08:50:09.498Z" },
{ url = "https://files.pythonhosted.org/packages/9a/b4/6d57ea199c5b880d182a2234aafa9a686f9c54c708ea7be75bd19d5aa825/grpcio_tools-1.80.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:85fe8d15f146c62cb76f38d963e256392d287442b9232717d30ae9e3bbda9bc3", size = 5712717, upload-time = "2026-03-30T08:50:15.028Z" },
{ url = "https://files.pythonhosted.org/packages/6a/1a/5505ee2277d368b409c796c78f22ea34a2a517b7d16755247efd663dc7af/grpcio_tools-1.80.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:95f0fffb5ca00519f3b602f938169b4dfa04b165e03258323965a9dfe8cc4d80", size = 2595941, upload-time = "2026-03-30T08:50:17.299Z" },
{ url = "https://files.pythonhosted.org/packages/4e/39/7fc1d16d8b767805079d76365d73e82c88dfaf179034473dbc9fbccedb77/grpcio_tools-1.80.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:7a0106af212748823a6ebd8ffbd9043414216f47cae3835f3187de0a62c415d3", size = 2909304, upload-time = "2026-03-30T08:50:19.485Z" },
{ url = "https://files.pythonhosted.org/packages/97/d8/276ee759755d8f34f2ca5e9d2debd1a59f29f66059fb790bc369f2236c26/grpcio_tools-1.80.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:31fd01a4038b5dfc4ec79504a17061344f670f851833411717fef66920f13cd7", size = 2660269, upload-time = "2026-03-30T08:50:21.266Z" },
{ url = "https://files.pythonhosted.org/packages/51/04/a6bb47942ad52901d777a649324d3203cf19d487f1d446263637f7a5bf12/grpcio_tools-1.80.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:57da9e19607fac4a01c48ead333c0dd15d91ed38794dce1194eda308f73e2038", size = 3109798, upload-time = "2026-03-30T08:50:23.267Z" },
{ url = "https://files.pythonhosted.org/packages/be/50/7ee69b2919916739787d725f205b878e8d1619dd30422b8278e324664669/grpcio_tools-1.80.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:90968f751851abb8b145593609800fa70c837e1c93ba0792c480b1c8d8bc29ef", size = 3658930, upload-time = "2026-03-30T08:50:25.458Z" },
{ url = "https://files.pythonhosted.org/packages/92/61/6d50783092b0e8bbcb04152d5388bf50ecf3ea2f783d95288ff6c3bb00fa/grpcio_tools-1.80.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b69dc5d6376ab43406304d1e2fc61ccf960b287d4325d77c3d45448c37a9d2da", size = 3326562, upload-time = "2026-03-30T08:50:27.809Z" },
{ url = "https://files.pythonhosted.org/packages/ea/58/d272ba549f6b1f0d8504f5fc4cd0a296f2c495a64d6e987fe871c4151557/grpcio_tools-1.80.0-cp310-cp310-win32.whl", hash = "sha256:3e8dcfebe34cb54df095de3d5871a4562a85a29f26d0f8bb41ee2c3dcfb11c3c", size = 997620, upload-time = "2026-03-30T08:50:29.959Z" },
{ url = "https://files.pythonhosted.org/packages/70/5f/9f45a9946a0298711c72ca48b2c1f46a7d0c207a44cd3e4bb59d04556ba3/grpcio_tools-1.80.0-cp310-cp310-win_amd64.whl", hash = "sha256:fc622ed4ca400695f41c9eae3266276c6ba007e4c28164ce53b44e7ccc5e492b", size = 1162466, upload-time = "2026-03-30T08:50:32.242Z" },
{ url = "https://files.pythonhosted.org/packages/8a/d7/225dc91e6cb4f8d4830f16a478a468e9c6f342dcdf8cacc3772cc1d1f607/grpcio_tools-1.80.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:1c43e5c768578fe0c6de3dbfaabe64af642951e1aa05c487cacedda63fa6c6c4", size = 2549937, upload-time = "2026-03-30T08:50:34.651Z" },
{ url = "https://files.pythonhosted.org/packages/97/3d/a3684cb7677f3bea8db434eae02a9ce30135d7a268cd473b1bc8041c4722/grpcio_tools-1.80.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a225348456575f3ac7851d8e23163195e76d2a905ee340cf73f33da62fba08aa", size = 5713099, upload-time = "2026-03-30T08:50:37.158Z" },
{ url = "https://files.pythonhosted.org/packages/b1/81/5665c697173ec346076358bfbfed0f7386825852494593ca14386478dfee/grpcio_tools-1.80.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a9396f02820d3f51c368c2c9dee15c55c77636c91be48a4d5c702e98d6fe0fdc", size = 2595776, upload-time = "2026-03-30T08:50:39.087Z" },
{ url = "https://files.pythonhosted.org/packages/03/4f/fb81384f08a8226fa079972ba88272ac6277581fc72e8ab234d74c7e065b/grpcio_tools-1.80.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:797c08460cae16b402326eac329aec720dccf45c9f9279b95a352792eb53cf0f", size = 2909144, upload-time = "2026-03-30T08:50:40.922Z" },
{ url = "https://files.pythonhosted.org/packages/4d/9c/c957618f1c2a3195ecf5e83b03edcb364c2c1391f74183cb76e5763fa536/grpcio_tools-1.80.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1872a867eb6217de19edb70a4ce4a374ced9d94293533dfd42fa649713f55bf4", size = 2660477, upload-time = "2026-03-30T08:50:42.766Z" },
{ url = "https://files.pythonhosted.org/packages/42/c7/23913da184febfd4eaf04de256a26bc5ff0411a5feb753e2adcff10fa86a/grpcio_tools-1.80.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:db122ba5ee357e3bb14e8944d69bbebcbdae91d5eace29ed4df3edc53cbc6528", size = 3110164, upload-time = "2026-03-30T08:50:44.761Z" },
{ url = "https://files.pythonhosted.org/packages/af/fa/b25ed85ebdb0396910eaa250b1346d75527d22fca586265416bd4330dcd5/grpcio_tools-1.80.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ddefd48c227e6f4d640fe576fac5fb2c4a8898196f513604c8ec7671b3b3d421", size = 3658988, upload-time = "2026-03-30T08:50:47.546Z" },
{ url = "https://files.pythonhosted.org/packages/60/85/2a55147cc9645e2ed777d1afcd2dc68cb34ba6f6c726bd4378ddb001a5ea/grpcio_tools-1.80.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:970ec058fa469dd6dae6ebc687501c5da670d95dead75f62f5b0933dce2c9794", size = 3326662, upload-time = "2026-03-30T08:50:49.59Z" },
{ url = "https://files.pythonhosted.org/packages/68/ed/b05bee2a992e6f9bda81909692ea920d0896cfa05c5c9dd77ba03f2d22fb/grpcio_tools-1.80.0-cp311-cp311-win32.whl", hash = "sha256:526b4402d47a0e9b31cd6087e42b7674784617916cc73c764e0bc35ed41b4ee5", size = 997969, upload-time = "2026-03-30T08:50:51.539Z" },
{ url = "https://files.pythonhosted.org/packages/b6/9a/cb50c8270e2f6285ff2761130ae257ac4e51789ded4b9d9710ce0381814d/grpcio_tools-1.80.0-cp311-cp311-win_amd64.whl", hash = "sha256:ee101ecda7231770f6a5da1024a9a6ed587a7785f8fe23ab8283f4a1acb3ffe6", size = 1162742, upload-time = "2026-03-30T08:50:54.232Z" },
{ url = "https://files.pythonhosted.org/packages/0c/b9/65929df8c9614792db900a8e45d4997fadbd1734c827da3f0eb1f2fe4866/grpcio_tools-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:d19d5a8244311947b96f749c417b32d144641c6953f1164824579e1f0a51d040", size = 2550856, upload-time = "2026-03-30T08:50:57.3Z" },
{ url = "https://files.pythonhosted.org/packages/28/17/af1557544d68d1aeca9d9ea53ed16524022d521fec6ba334ab3530e9c1a6/grpcio_tools-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:fb599a3dc89ed1bb24489a2724b2f6dd4cddbbf0f7bdd69c073477bab0dc7554", size = 5710883, upload-time = "2026-03-30T08:51:00.077Z" },
{ url = "https://files.pythonhosted.org/packages/cc/48/aa9b4f7519ca972bc40d315d5c28f05ca28fa08de13d4e8b69f551b798ab/grpcio_tools-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:623ee31fc2ff7df9a987b4f3d139c30af17ce46a861ae0e25fb8c112daa32dd8", size = 2598004, upload-time = "2026-03-30T08:51:02.102Z" },
{ url = "https://files.pythonhosted.org/packages/b4/b8/b01371c119924b3beca1fe3f047b1bc2cdc66b3d37f0f3acc9d10c567a43/grpcio_tools-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b46570a68378539ee2b75a5a43202561f8d753c832798b1047099e3c551cf5d6", size = 2909568, upload-time = "2026-03-30T08:51:04.159Z" },
{ url = "https://files.pythonhosted.org/packages/4f/7c/1108f7bdb58475a7e701ec89b55eb494538b6e76acd211ba0d4cc5fd28e8/grpcio_tools-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51caf99c28999e7e0f97e9cea190c1405b7681a57bb2e0631205accd92b43fa4", size = 2660938, upload-time = "2026-03-30T08:51:06.126Z" },
{ url = "https://files.pythonhosted.org/packages/67/59/d1c0063d4cd3b85363c7044ff3e5159d6d5df96e2692a9a5312d9c8cb290/grpcio_tools-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cdaa1c9aa8d3a87891a96700cadd29beec214711d6522818d207277f6452567c", size = 3113814, upload-time = "2026-03-30T08:51:08.834Z" },
{ url = "https://files.pythonhosted.org/packages/76/21/18d34a4efe524c903cf66b0cfa5260d81f277b6ae668b647edf795df9ce5/grpcio_tools-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3399b5fd7b59bcffd59c6b9975a969d9f37a3c87f3e3d63c3a09c147907acb0d", size = 3662793, upload-time = "2026-03-30T08:51:11.094Z" },
{ url = "https://files.pythonhosted.org/packages/f3/40/cf2d9295a6bd593244ea703858f8fc2efd315046ca3ef7c6f9ebc5b810fa/grpcio_tools-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9c6abc08d3485b2aac99bb58afcd31dc6cd4316ce36cf263ff09cb6df15f287f", size = 3329149, upload-time = "2026-03-30T08:51:13.066Z" },
{ url = "https://files.pythonhosted.org/packages/0d/1d/fc34b32167966df20d69429b71dfca83c48434b047a5ac4fd6cd91ca4eed/grpcio_tools-1.80.0-cp312-cp312-win32.whl", hash = "sha256:18c51e07652ac7386fcdbd11866f8d55a795de073337c12447b5805575339f74", size = 997519, upload-time = "2026-03-30T08:51:14.87Z" },
{ url = "https://files.pythonhosted.org/packages/91/98/6d6563cdf51085b75f8ec24605c6f2ce84197571878ca8ab4af949c6be2d/grpcio_tools-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:ac6fdd42d5bb18f0d903a067e2825be172deff70cf197164b6f65676cb506c9b", size = 1162407, upload-time = "2026-03-30T08:51:16.793Z" },
{ url = "https://files.pythonhosted.org/packages/44/d9/f7887a4805939e9a85d03744b66fc02575dc1df3c3e8b4d9ec000ee7a33d/grpcio_tools-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e7046837859bbfd10b01786056145480155c16b222c9e209215b68d3be13060e", size = 2550319, upload-time = "2026-03-30T08:51:19.117Z" },
{ url = "https://files.pythonhosted.org/packages/57/5a/c8a05b32bd7203f1b9f4c0151090a2d6179d6c97692d32f2066dc29c67a6/grpcio_tools-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a447f28958a8fe84ff0d9d3d9473868feb27ee4a9c9c805e66f5b670121cec59", size = 5709681, upload-time = "2026-03-30T08:51:21.991Z" },
{ url = "https://files.pythonhosted.org/packages/82/6b/794350ed645c12c310008f97068f6a6fd927150b0d0d08aad1d909e880b1/grpcio_tools-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:75f00450e08fe648ad8a1eeb25bc52219679d54cdd02f04dfdddc747309d83f6", size = 2596820, upload-time = "2026-03-30T08:51:24.323Z" },
{ url = "https://files.pythonhosted.org/packages/f9/b2/b39e7b79f7c878135e0784a53cd7260ee77260c8c7f2c9e46bca8e05d017/grpcio_tools-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3db830eaff1f2c2797328f2fa86c9dcdbd7d81af573a68db81e27afa2182a611", size = 2909193, upload-time = "2026-03-30T08:51:27.025Z" },
{ url = "https://files.pythonhosted.org/packages/10/f3/abe089b058f87f9910c9a458409505cbeb0b3e1c2d993a79721d02ee6a32/grpcio_tools-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7982b5fe42f012686b667dda12916884de95c4b1c65ff64371fb7232a1474b23", size = 2660197, upload-time = "2026-03-30T08:51:29.392Z" },
{ url = "https://files.pythonhosted.org/packages/09/c3/3f7806ad8b731d8a89fe3c6ed496473abd1ef4c9c42c9e9a8836ce96e377/grpcio_tools-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6451b3f4eb52d12c7f32d04bf8e0185f80521f3f088ad04b8d222b3a4819c71e", size = 3113144, upload-time = "2026-03-30T08:51:31.671Z" },
{ url = "https://files.pythonhosted.org/packages/fe/f5/415ef205e0b7e75d2a2005df6120145c4f02fda28d7b3715b55d924fe1a4/grpcio_tools-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:258bc30654a9a2236be4ca8e2ad443e2ac6db7c8cc20454d34cce60265922726", size = 3661897, upload-time = "2026-03-30T08:51:34.849Z" },
{ url = "https://files.pythonhosted.org/packages/e3/d3/2ad54764c2a9547080dd8518f4a4dc7899c7e6e747a1b1de542ce6a12066/grpcio_tools-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:865a2b8e6334c838976ab02a322cbd55c863d2eaf3c1e1a0255883c63996772a", size = 3328786, upload-time = "2026-03-30T08:51:37.265Z" },
{ url = "https://files.pythonhosted.org/packages/eb/63/23ab7db01f9630ab4f3742a2fc9fbff38b0cfc30c976114f913950664a75/grpcio_tools-1.80.0-cp313-cp313-win32.whl", hash = "sha256:f760ac1722f33e774814c37b6aa0444143f612e85088ead7447a0e9cd306a1f1", size = 997087, upload-time = "2026-03-30T08:51:39.137Z" },
{ url = "https://files.pythonhosted.org/packages/9b/af/b1c1c4423fb49cb7c8e9d2c02196b038c44160b7028b425466743c6c81fa/grpcio_tools-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:7843b9ac6ff8ca508424d0dd968bd9a1a4559967e4a290f26be5bd6f04af2234", size = 1162167, upload-time = "2026-03-30T08:51:41.498Z" },
{ url = "https://files.pythonhosted.org/packages/0e/44/7beeee2348f9f412804f5bf80b7d13b81d522bf926a338ae3da46b2213b7/grpcio_tools-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:12f950470449dbeec78317dbc090add7a00eb6ca812af7b0538ab7441e0a42c3", size = 2550303, upload-time = "2026-03-30T08:51:44.373Z" },
{ url = "https://files.pythonhosted.org/packages/2d/aa/f77dd85409a1855f8c6319ffc69d81e8c3ffe122ee3a7136653e1991d8b6/grpcio_tools-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d3f9a376a29c9adf62bb56f7ff5bc81eb4abeaf53d1e7dde5015564832901a51", size = 5709778, upload-time = "2026-03-30T08:51:47.112Z" },
{ url = "https://files.pythonhosted.org/packages/9c/7c/ab7af4883ebdfdc228b853de89fed409703955e8d47285b321a5794856bd/grpcio_tools-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ba1ffbf2cff71533615e2c5a138ed5569611eec9ae7f9c67b8898e127b54ac0", size = 2597928, upload-time = "2026-03-30T08:51:49.494Z" },
{ url = "https://files.pythonhosted.org/packages/22/e8/4381a963d472e3ab6690ba067ed2b1f1abf8518b10f402678bd2dcb79a54/grpcio_tools-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:13f60f8d9397c514c6745a967d22b5c8c698347e88deebca1ff2e1b94555e450", size = 2909333, upload-time = "2026-03-30T08:51:52.124Z" },
{ url = "https://files.pythonhosted.org/packages/94/cb/356b5fdf79dd99455b425fb16302fe60995554ceb721afbf3cf770a19208/grpcio_tools-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:88d77bad5dd3cd5e6f952c4ecdd0ee33e0c02ecfc2e4b0cbee3391ac19e0a431", size = 2660217, upload-time = "2026-03-30T08:51:55.066Z" },
{ url = "https://files.pythonhosted.org/packages/2b/d7/1752018cc2c36b2c5612051379e2e5f59f2dbe612de23e817d2f066a9487/grpcio_tools-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:017945c3e98a4ed1c4e21399781b4137fc08dfc1f802c8ace2e64ef52d32b142", size = 3113896, upload-time = "2026-03-30T08:51:57.3Z" },
{ url = "https://files.pythonhosted.org/packages/cc/17/695bbe454f70df35c03e22b48c5314683b913d3e6ed35ec90d065418c1ab/grpcio_tools-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a33e265d4db803495007a6c623eafb0f6b9bb123ff4a0af89e44567dad809b88", size = 3661950, upload-time = "2026-03-30T08:51:59.867Z" },
{ url = "https://files.pythonhosted.org/packages/9c/d0/533d87629ec823c02c9169ee20228f734c264b209dcdf55268b5a14cde0a/grpcio_tools-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6c129da370c5f85f569be2e545317dda786a60dd51d7deea29b03b0c05f6aac3", size = 3328755, upload-time = "2026-03-30T08:52:02.942Z" },
{ url = "https://files.pythonhosted.org/packages/08/a1/504d7838770c73a9761e8a8ff4869dba1146b44f297ff0ac6641481942d3/grpcio_tools-1.80.0-cp314-cp314-win32.whl", hash = "sha256:25742de5958ae4325249a37e724e7c0e5120f8e302a24a977ebd1737b48a5e97", size = 1019620, upload-time = "2026-03-30T08:52:05.342Z" },
{ url = "https://files.pythonhosted.org/packages/f3/75/8b7cd281c5cdfb4ca2c308f7e9b2799bab2be6e7a9e9212ea5a82e2aecd4/grpcio_tools-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:bbf8eeef78fda1966f732f79c1c802fadd5cfd203d845d2af4d314d18569069c", size = 1194210, upload-time = "2026-03-30T08:52:08.105Z" },
{ url = "https://files.pythonhosted.org/packages/5a/e1/1fcf884902ae7255d8da224cfa638ea88a46d50f62a33d06d35c8960b029/grpcio_tools-1.81.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:9b6ba8a72cfda576508701a7c0bbeebe6f6f9843320d4f12e74efd19ddccd965", size = 2586261, upload-time = "2026-06-11T12:49:21.447Z" },
{ url = "https://files.pythonhosted.org/packages/a2/d7/1815110b2d40ec99dbb0a7e6d7eafd591cd1f1e9bf9d3858cd9cf3ffacbd/grpcio_tools-1.81.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:ac47a9ea1224df8b653072614e6f0207e9fbfe63fdabaa5918a60ca5fc931b88", size = 5817509, upload-time = "2026-06-11T12:49:25.958Z" },
{ url = "https://files.pythonhosted.org/packages/23/e8/af99579842b5a555312fa782f32ce0f99bd35b2b7a1243294b2755468857/grpcio_tools-1.81.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eac4bb645ceff0c147cc720a40ae68f97427eaafb4968e866dd8fcc20d3d4831", size = 2634112, upload-time = "2026-06-11T12:49:27.937Z" },
{ url = "https://files.pythonhosted.org/packages/b5/cd/235ad56ac728c49c17e9218c4daccd5831e6ec7af94236bec0cc66c71c68/grpcio_tools-1.81.1-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:cc410b621dd85193766c12dca2e238696199a27a65d2b31b6f0a4c6c0043ff26", size = 2957950, upload-time = "2026-06-11T12:49:29.619Z" },
{ url = "https://files.pythonhosted.org/packages/77/3e/9103e8b4610597bf89db49eb112091c91bf5d63ddef2a951e11a4be05f2b/grpcio_tools-1.81.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b62d254c214faa3773eac709376ae25cf7abff1a76ba5fc4dbcd7b14fc4e4ae6", size = 2697765, upload-time = "2026-06-11T12:49:31.702Z" },
{ url = "https://files.pythonhosted.org/packages/3c/86/beb2a43fbb93570a2305696083f6736566301d957869f463308ec6839f95/grpcio_tools-1.81.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bd0b68dc76b10b3384b9b6e9f59202b83dcaafd8098eb644759a69316686acf8", size = 3147588, upload-time = "2026-06-11T12:49:33.748Z" },
{ url = "https://files.pythonhosted.org/packages/af/4d/b0182d9948631cd837a372b6625cf59d6e335d4aab0f425d4b7306619074/grpcio_tools-1.81.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a28d231455ab6e3558299f7d831a73c8be8ee6b7ec614ecf39eb50c0ed15767f", size = 3708798, upload-time = "2026-06-11T12:49:35.979Z" },
{ url = "https://files.pythonhosted.org/packages/23/9b/f452a189d399051d85cf82fe2f27a070efaa52512a2c5e3ae6ef1ae99a1f/grpcio_tools-1.81.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:82740248eb6f3b6a38988cb5e64adb7303af9ea5cb4197c8ed08c1fabc767440", size = 3366969, upload-time = "2026-06-11T12:49:37.911Z" },
{ url = "https://files.pythonhosted.org/packages/9b/48/0075cb4f6ae7db280f461de2dbba700b22ae62e351ae13e6e461cd6804de/grpcio_tools-1.81.1-cp310-cp310-win32.whl", hash = "sha256:801d9d8ab5cddf8f8e064225292f0713427011252a07828a6b54e2ed64d534de", size = 1008713, upload-time = "2026-06-11T12:49:39.791Z" },
{ url = "https://files.pythonhosted.org/packages/17/bd/7692bc698259e5645b68720e77e7b176d376f6ae0c9db8b5b750a02f1958/grpcio_tools-1.81.1-cp310-cp310-win_amd64.whl", hash = "sha256:3c8611d6e4e859ac5373422ef27c4b7540cf98c9991c9abc6722613ef72b13aa", size = 1174752, upload-time = "2026-06-11T12:49:41.43Z" },
{ url = "https://files.pythonhosted.org/packages/18/76/14ff87090199a36f914388299a1148d0734a20cea1b0ca8480bae1f373f1/grpcio_tools-1.81.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:8161f398f957a376cae7385ea7c8684f439d460ef702b528912da3bcb31fc515", size = 2586251, upload-time = "2026-06-11T12:49:43.514Z" },
{ url = "https://files.pythonhosted.org/packages/87/a8/d5aa99de9d8b2dd2a8192c1779796eda8b0d0f1dd915422e0a8a61b80391/grpcio_tools-1.81.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:53ef76cc3b0493ff734a5e8c39d5b519e1822236fcccdfe7677c5e1efd767761", size = 5818063, upload-time = "2026-06-11T12:49:45.975Z" },
{ url = "https://files.pythonhosted.org/packages/ba/cb/2e9a6dbc6a514dd3cd264fb3bf9217937453a4d45dbc3ca6ca4ee34ba1a7/grpcio_tools-1.81.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:690e6dcaa8b8a7886ce206ba344e2127211597e1a1ddab73df9f3d80c8f6707e", size = 2634061, upload-time = "2026-06-11T12:49:48.13Z" },
{ url = "https://files.pythonhosted.org/packages/c8/2b/2ccd1a929e6c8ad84a0aa8d66ad9f615b4a8e79d9927373d86aa36b4ba2e/grpcio_tools-1.81.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ad7a997c07bd345e84842e60561e7e2cc090ce6c4e1d2f0407e31b85b40fc49a", size = 2958029, upload-time = "2026-06-11T12:49:50.466Z" },
{ url = "https://files.pythonhosted.org/packages/e7/67/2da8cd312edc348f44f26f82096b25cdb7d2905cd786acc6bf777b169502/grpcio_tools-1.81.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b6bd163ece4535726e5292b845ed80ae9b2cae73ba091c7d6c66033c430e3857", size = 2698031, upload-time = "2026-06-11T12:49:52.292Z" },
{ url = "https://files.pythonhosted.org/packages/d9/ba/ad1680fbdf9317c4f1e54c37c96d1f422370df66ac9adbd175c7cb3531d7/grpcio_tools-1.81.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2baa7e735f35b2a648144c03348a126097b13e101d3c242d5edb6ac91437ccbe", size = 3147541, upload-time = "2026-06-11T12:49:54.43Z" },
{ url = "https://files.pythonhosted.org/packages/57/c1/57cd08eef293d713cb8935295e4f08d8f0013480b2ba3aad1af0271eb7ba/grpcio_tools-1.81.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1d602b410b2b2addc434cace9ce4fe2035974a3078228f98ffa049a5c90acc2f", size = 3708524, upload-time = "2026-06-11T12:49:56.544Z" },
{ url = "https://files.pythonhosted.org/packages/52/31/01ea8ca9c82fe2c79b5b594c3ae427d56699bc106b2d91caca129add8b10/grpcio_tools-1.81.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f8cb64f87c45ccca8234fa47e6b21f09e43801ff11b556deecb461b3b3e9f292", size = 3367022, upload-time = "2026-06-11T12:49:59.608Z" },
{ url = "https://files.pythonhosted.org/packages/7d/35/8140cd175602df3d17215cfb28a7ea55b7a67e2b872be76e1ee4af5c4df9/grpcio_tools-1.81.1-cp311-cp311-win32.whl", hash = "sha256:87b25ca0e27373a4a32a629a4ba976f5764b9887dd50d6fe017d38009a0363e8", size = 1008980, upload-time = "2026-06-11T12:50:01.422Z" },
{ url = "https://files.pythonhosted.org/packages/be/86/1bd29ab3c52457702b96536f1f208ab27695322d855f95c9666dfb713019/grpcio_tools-1.81.1-cp311-cp311-win_amd64.whl", hash = "sha256:204de03b539a4b08772c6553b92bcc112cbc965e0ac22f909f6d133b8ac33a8c", size = 1174840, upload-time = "2026-06-11T12:50:03.408Z" },
{ url = "https://files.pythonhosted.org/packages/3b/8a/824a9ca20bcdce8a568bb8c9f98bfeb7fad62129235e6d2ae7576fd1250a/grpcio_tools-1.81.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:353b1fafcc739c31ed42271052709595b340d34f27c459beeb78a32938305bb5", size = 2585927, upload-time = "2026-06-11T12:50:05.671Z" },
{ url = "https://files.pythonhosted.org/packages/2f/35/e5f9f671378b1b89a896150d3e4fa2c6ec61a5e1e9e5107ce4c140ccc931/grpcio_tools-1.81.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:768f584c2423cbeb6cb6867817a39365b987ff16b8259a3adbc6546b9e303a4e", size = 5815665, upload-time = "2026-06-11T12:50:08.178Z" },
{ url = "https://files.pythonhosted.org/packages/c6/02/631b628e4072e988c669bd8f1b2406ef3c9a4cfcb2625bbf2a308a07b71d/grpcio_tools-1.81.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1680b35a84f4694401819ac4acac42dda6dbc7bb8fc74112fd1a60425a07adf4", size = 2635518, upload-time = "2026-06-11T12:50:10.391Z" },
{ url = "https://files.pythonhosted.org/packages/de/7c/2e3537e3ea3d1c0ddd6766cf6a7c62b487d89fb005713df2781d5f21483a/grpcio_tools-1.81.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f64e665c8ec639278ecf009beb92cbdcc5994f617c1af3d58036e1f70b1423ec", size = 2958252, upload-time = "2026-06-11T12:50:12.677Z" },
{ url = "https://files.pythonhosted.org/packages/35/68/14013cb2942bdac354746b643b4c37dd91906da8dce00f41c616e88bf33d/grpcio_tools-1.81.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba8f1ae82ad199f43448995715445cc623fb20d3882382e4be61f0da8ccb3f0e", size = 2698439, upload-time = "2026-06-11T12:50:15.017Z" },
{ url = "https://files.pythonhosted.org/packages/bd/45/000c14c0338a7ad36054b9f17ea41842deb7841c05c067dd36cc831bc0f4/grpcio_tools-1.81.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b7b6d1e986d5923751bfe2b5cca9c4cb3d5653446e4fa4aacd438033e2dc360a", size = 3152160, upload-time = "2026-06-11T12:50:17.3Z" },
{ url = "https://files.pythonhosted.org/packages/41/97/881930ca3967d2c8a95649bea8ebc991a7cf2331bc96679fd3600450dccc/grpcio_tools-1.81.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7f208c207aca639dcb34648d3826c38d7cf3485118fb2065117e9fc4827406b3", size = 3710468, upload-time = "2026-06-11T12:50:19.479Z" },
{ url = "https://files.pythonhosted.org/packages/c7/b5/67baeba7366162652cdc1dbd962289accde07241bc8f42f6f02b305efcc6/grpcio_tools-1.81.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:724ecb69af63d2f6d4ccea3e6fa0ca110ed9c5824d48c2f887c631bbb03c1c3c", size = 3370797, upload-time = "2026-06-11T12:50:21.501Z" },
{ url = "https://files.pythonhosted.org/packages/f2/5d/34f2dce2125ccb107e32b57f5a9c1257edcc0793b0d2fef1e8b13a6bac3c/grpcio_tools-1.81.1-cp312-cp312-win32.whl", hash = "sha256:895a6782cec86beac71ccebb4b9848259c6f04a3028b8e42fa8d40cfe5146593", size = 1008453, upload-time = "2026-06-11T12:50:23.358Z" },
{ url = "https://files.pythonhosted.org/packages/8a/be/09da8256ec8d2a5ce8a1acc51cbbc4ca52a462d78ed3412778440a56502e/grpcio_tools-1.81.1-cp312-cp312-win_amd64.whl", hash = "sha256:0265fd1386b7458302f79542558345880d484f8fa92ae196c0c0268242c5f23a", size = 1174857, upload-time = "2026-06-11T12:50:25.685Z" },
{ url = "https://files.pythonhosted.org/packages/76/90/5faa8b26e03495e5117f93bef8293cbada4af136362745dad7d1813ef0b0/grpcio_tools-1.81.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:3d604b4fd114b79ebb9f865bf3e04fd3ae93c704e1fad96f7fd03b0865c263b7", size = 2586071, upload-time = "2026-06-11T12:50:28.4Z" },
{ url = "https://files.pythonhosted.org/packages/e8/9a/85dc589fa6ae2439451eaa81a1578de31e29c676980d38bef7549b8a1f45/grpcio_tools-1.81.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:3389e705460efa3f3758141ba5520e6743b131c9576197c944fb9cbe49048126", size = 5813299, upload-time = "2026-06-11T12:50:31.295Z" },
{ url = "https://files.pythonhosted.org/packages/77/fd/c53994e58a837e6eefe48f53eb3492afc04f2b8af255df4adb37d14378f8/grpcio_tools-1.81.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8a17d8ceeb6a855fadf39f5171c80a382d97c4db98d5943eca553497fdebf84b", size = 2634668, upload-time = "2026-06-11T12:50:33.938Z" },
{ url = "https://files.pythonhosted.org/packages/34/32/de988e86688686a2117e7ce6ce9eff4f638c929bb55b0afe60d6fbd2e45c/grpcio_tools-1.81.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:43baf71dc60fd653062da2e95e95c73b35dd130be8f9fa3d544c3af3f808a290", size = 2957930, upload-time = "2026-06-11T12:50:36.726Z" },
{ url = "https://files.pythonhosted.org/packages/72/97/3f18a0ea32b5f809d21961dbd0bc382b589a4c3d501e3d67c345d5456ed3/grpcio_tools-1.81.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:136e90906af0df51ad929713244ba812d0dbb1844b4f467d5d86bdb054698f90", size = 2697760, upload-time = "2026-06-11T12:50:39.108Z" },
{ url = "https://files.pythonhosted.org/packages/49/c0/dbf5cbc877290ff7504a59959a8af4fdcfdaa1e84237948405ccf1aa82a6/grpcio_tools-1.81.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd6c3bf3ea6a61eb58c54368d72ada591f2a270f3a31a32e8536e773337e76d9", size = 3151456, upload-time = "2026-06-11T12:50:41.983Z" },
{ url = "https://files.pythonhosted.org/packages/de/ea/16fe2dc83140a59e5c0a0b9dc2693dd36bfaa6bd835724b4ec66a68eab7b/grpcio_tools-1.81.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c306c307f8f74cddc4056fdbb6f1da55de087a21120efbd02bd915daa5a52fd", size = 3710469, upload-time = "2026-06-11T12:50:44.596Z" },
{ url = "https://files.pythonhosted.org/packages/22/7d/df987d7d81e7ad2f7516d9e9d56ff29c54dbc6d8587e425688dca9a28e49/grpcio_tools-1.81.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bdbdc927be2e0ea13c32564a72ee31d712a716fb6f8c0d53d37a77d8277c272c", size = 3370488, upload-time = "2026-06-11T12:50:47.199Z" },
{ url = "https://files.pythonhosted.org/packages/ba/c5/5a63444d694ea47bf670138208f71830cc1759c402c8818092b28ab2dc5f/grpcio_tools-1.81.1-cp313-cp313-win32.whl", hash = "sha256:9d383724bcd67244b6def9e9164c640ee9380c0b7534ee7545a6fb0022a59afe", size = 1008229, upload-time = "2026-06-11T12:50:49.527Z" },
{ url = "https://files.pythonhosted.org/packages/00/75/3945e26d5c94ae6ed9be5caef73d4d66c47dc8cfdd7b4995efaf942754e0/grpcio_tools-1.81.1-cp313-cp313-win_amd64.whl", hash = "sha256:f3eb15849979ca7bb864ce81a74d68b0f225a7f111ed3fe212bfc08cf9812b10", size = 1174523, upload-time = "2026-06-11T12:50:51.755Z" },
{ url = "https://files.pythonhosted.org/packages/0d/08/e581ad42ae517a61172285047e4d710e2ac75f2f1915f7c91f284254e6d5/grpcio_tools-1.81.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:7d168ea26390717d0462c0d0408331dc98a60fc7f7e6118afac9b73f5a66d87c", size = 2585944, upload-time = "2026-06-11T12:50:54.528Z" },
{ url = "https://files.pythonhosted.org/packages/78/c8/200d90ebad685af7eea5ff7e0360c504dd01ec053fe0f1f9c4abe3ea2d5a/grpcio_tools-1.81.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:43c528655b226375013036692d8db4cd59060c1f41dd62c77f4d17b69f6ce828", size = 5813492, upload-time = "2026-06-11T12:50:57.291Z" },
{ url = "https://files.pythonhosted.org/packages/2d/c0/60da2a1af37aa8eb47308cec24d9f7709a8976fdec3a53fd35b56b358326/grpcio_tools-1.81.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a9c6fcc68c9d5a208967bfe4fd3224d3c3be9a950c3e827e8f4b17e15c2dc555", size = 2634991, upload-time = "2026-06-11T12:50:59.767Z" },
{ url = "https://files.pythonhosted.org/packages/0c/7f/dede28b579ae9bf9079ba1aa913e8088d1dc0cdbe21c85caa22f0790cad2/grpcio_tools-1.81.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:a987c85dcbe1b32066d7acd46266d1a428aecbd629331bf5b853e74c835bf876", size = 2957913, upload-time = "2026-06-11T12:51:02.31Z" },
{ url = "https://files.pythonhosted.org/packages/4c/38/4de2118adb58ec7ffba65ec623b5836db769665c192517cbf187db3f6145/grpcio_tools-1.81.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a882382507bb5ec6d7edc9648053dfd3bc8f9285cde56a6fa9b9a83b4bd07f1c", size = 2697709, upload-time = "2026-06-11T12:51:05.016Z" },
{ url = "https://files.pythonhosted.org/packages/6b/e1/762ced51059e4f694fd337ecae491581d42a4e61dcb0415d8c5c60e6ddcb/grpcio_tools-1.81.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7746e508d4239a02f7e93638be5bc0ebb0120ddb796f7506aaae9d47a4599d97", size = 3151884, upload-time = "2026-06-11T12:51:07.593Z" },
{ url = "https://files.pythonhosted.org/packages/19/d8/9823090dc801e7229944874e7429c3b98e741ac778d8dc373f60240e1c43/grpcio_tools-1.81.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fc3d2a41a7a4467fa03b391394fffada9291fe8feebc8679b526f6bc36942b25", size = 3710404, upload-time = "2026-06-11T12:51:10.172Z" },
{ url = "https://files.pythonhosted.org/packages/64/4e/4eae98d02148cb6f9f452f09942afba407afa6851e6c1fddc5ae9ec0b4ed/grpcio_tools-1.81.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:21bb3ba90e6d8df1ff663d4ee39a4e5b25a64e8ed4902476ca9ded0954d3917a", size = 3370525, upload-time = "2026-06-11T12:51:12.678Z" },
{ url = "https://files.pythonhosted.org/packages/e0/3e/2206e597a128da6a03a6106d2eaf2c3e72c7d80843d4be933e3a3d10d02a/grpcio_tools-1.81.1-cp314-cp314-win32.whl", hash = "sha256:3dca56016d90a710c4d9861bae793dc089c1430a90c79ce672e948ddb65fa539", size = 1030582, upload-time = "2026-06-11T12:51:14.906Z" },
{ url = "https://files.pythonhosted.org/packages/cf/f2/bbeef86c687225b7bbc7c0acdfbd25c8bcaa3f5b1c941db053e5c3d9e859/grpcio_tools-1.81.1-cp314-cp314-win_amd64.whl", hash = "sha256:cb08172b7b629e75cb33866928d319a3196540a725eaab628ba721007140f1af", size = 1207490, upload-time = "2026-06-11T12:51:17.598Z" },
]
[[package]]
@@ -1418,7 +1418,8 @@ dependencies = [
{ name = "pyyaml" },
{ name = "tenacity" },
{ name = "typing-extensions" },
{ name = "uuid-utils" },
{ name = "uuid-utils", version = "0.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.14'" },
{ name = "uuid-utils", version = "0.19.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.14'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ed/ed/ceecd3bfcfc77a94cf30a9f1506e907fdea447458825e01914a72d2d1a6f/langchain_core-1.6.5.tar.gz", hash = "sha256:bdb059fef65473fd444558dbb18f50901657402c43ceed9dc0fdc7b76a393c6b", size = 1007041, upload-time = "2026-09-24T18:11:06.487Z" }
wheels = [
@@ -1439,7 +1440,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.2.19"
version = "1.2.20"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },
@@ -1582,9 +1583,10 @@ test = [
[[package]]
name = "langgraph-api"
version = "0.10.0"
version = "0.17.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "cloudpickle" },
{ name = "cryptography" },
{ name = "grpcio" },
@@ -1602,8 +1604,10 @@ dependencies = [
{ name = "langsmith", extra = ["otel"] },
{ name = "opentelemetry-api" },
{ name = "opentelemetry-exporter-otlp-proto-http" },
{ name = "opentelemetry-exporter-prometheus" },
{ name = "opentelemetry-sdk" },
{ name = "orjson" },
{ name = "prometheus-client" },
{ name = "protobuf" },
{ name = "pyjwt" },
{ name = "sse-starlette" },
@@ -1611,16 +1615,16 @@ dependencies = [
{ name = "structlog" },
{ name = "tenacity" },
{ name = "truststore" },
{ name = "uuid-utils" },
{ name = "uuid-utils", version = "0.19.0", source = { registry = "https://pypi.org/simple" } },
{ name = "uvicorn" },
{ name = "uvloop", marker = "sys_platform != 'win32'" },
{ name = "watchfiles" },
{ name = "websockets" },
{ name = "zstandard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7d/17/a23ecdad3de7e48209bc8d534312ba83e332126ec60da9e6487461b02597/langgraph_api-0.10.0.tar.gz", hash = "sha256:f594a160857e2cd7fb2352bba585111177a4a394625ad0cf5898814754fb7bc1", size = 713903, upload-time = "2026-06-11T14:36:50.469Z" }
sdist = { url = "https://files.pythonhosted.org/packages/33/c2/3a5baab0dd010dffdbf92fecd34f0fe25d4fac42281469ed8b494e4b3511/langgraph_api-0.17.1.tar.gz", hash = "sha256:08bf7df9cc2bd7e45fbf2f1cb97267264f149794032010e55dafb50d64cbded7", size = 886040, upload-time = "2026-11-03T21:27:09.883Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/38/6b0cdcaaed9b3105102b452a5a70fb08894a9511458cf00c061c4725347c/langgraph_api-0.10.0-py3-none-any.whl", hash = "sha256:f4b545bf1936c4e90bed1cc07a6852b1be37e1667007ab4736d800cc18e9f9b3", size = 577475, upload-time = "2026-06-11T14:36:48.838Z" },
{ url = "https://files.pythonhosted.org/packages/89/84/a61803f1a2dd895718abbe4dbb076bb19ff04238c36d217697f5a485188e/langgraph_api-0.17.1-py3-none-any.whl", hash = "sha256:e070f5675713adf3322ba993992e618f5ec587ec0aec80441c563dfc404d14f2", size = 663034, upload-time = "2026-11-03T21:27:07.552Z" },
]
[[package]]
@@ -1874,7 +1878,7 @@ test = [
[[package]]
name = "langgraph-runtime-inmem"
version = "0.30.0"
version = "0.37.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "blockbuster" },
@@ -1885,9 +1889,9 @@ dependencies = [
{ name = "starlette" },
{ name = "structlog" },
]
sdist = { url = "https://files.pythonhosted.org/packages/98/19/5d330e22e3ee2569742d3577b4ddfbb6b8e383f2edd9724b4a4b73735717/langgraph_runtime_inmem-0.30.0.tar.gz", hash = "sha256:31954e1de6cd43c284b425243d4faeae818f68b3daca4dbe4313e651b6f5e11d", size = 129940, upload-time = "2026-06-11T14:22:46.985Z" }
sdist = { url = "https://files.pythonhosted.org/packages/a3/03/96d55a65eedf319da923a93e99d7a879364e0cd5b1159a15b7a58f2eaf9b/langgraph_runtime_inmem-0.37.1.tar.gz", hash = "sha256:c52a5105d0559994b3c9218a67eee3e746b65eb7b40d731ebb7d87247683395f", size = 142119, upload-time = "2026-11-03T21:20:31.19Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/df/cfb15c70fa4dcab9ea48f8fda9f4ed87d1767eea6e4d2901dc704da297ba/langgraph_runtime_inmem-0.30.0-py3-none-any.whl", hash = "sha256:08a36b83b5e32fe6e30cb25062a1e671a42e2a0206e4043cc95f41198dcbbb12", size = 51762, upload-time = "2026-06-11T14:22:46.012Z" },
{ url = "https://files.pythonhosted.org/packages/8c/bc/df823ed5cdf54cac16a85fb7f0bc4dde9a77a736b0ce1d38e9da97e620af/langgraph_runtime_inmem-0.37.1-py3-none-any.whl", hash = "sha256:b6504d50970e962947bd43a1dd2666b4f1f069d8fa08da6b7364b3f5a23b35a9", size = 62099, upload-time = "2026-11-03T21:20:29.734Z" },
]
[[package]]
@@ -1947,7 +1951,8 @@ dependencies = [
{ name = "pydantic" },
{ name = "requests" },
{ name = "requests-toolbelt" },
{ name = "uuid-utils" },
{ name = "uuid-utils", version = "0.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.14'" },
{ name = "uuid-utils", version = "0.19.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.14'" },
{ name = "websockets" },
{ name = "xxhash" },
{ name = "zstandard" },
@@ -2248,6 +2253,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" },
]
[[package]]
name = "opentelemetry-exporter-prometheus"
version = "0.66b1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-sdk" },
{ name = "prometheus-client" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ed/58/e552853748c3a1478d3f0db31bb4e3bef2e45385f64d658148693183410e/opentelemetry_exporter_prometheus-0.66b1.tar.gz", hash = "sha256:1c702a0cc7a1b8c5e1f3f246aeb4273dbd707179af30dbb66182a75e16a06ed8", size = 16987, upload-time = "2026-10-06T17:33:06.358Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/8a/5e7262d970586a8d69dd159bebc68fbee7457088883b179599a562881a6f/opentelemetry_exporter_prometheus-0.66b1-py3-none-any.whl", hash = "sha256:a938e6af7295d5bacf82da9ca845cab9a9cb1c5f565abe39f54ef8bf511c05d9", size = 13222, upload-time = "2026-10-06T17:32:44.94Z" },
]
[[package]]
name = "opentelemetry-proto"
version = "1.45.1"
@@ -3730,6 +3749,11 @@ wheels = [
name = "uuid-utils"
version = "0.13.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.15'",
"python_full_version == '3.14.*'",
"python_full_version < '3.11'",
]
sdist = { url = "https://files.pythonhosted.org/packages/fe/8a/17b11768dcb473d3a255c02ffdd94fbd1b345c906efea0a39124dcbaed52/uuid_utils-0.13.0.tar.gz", hash = "sha256:4c17df6427a9e23a4cd7fb9ee1efb53b8abb078660b9bdb2524ca8595022dfe1", size = 21921, upload-time = "2026-01-08T15:48:10.841Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/85/b8/d40848ca22781f206c60a1885fc737d2640392bd6b5792d455525accd89c/uuid_utils-0.13.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:83628283e977fb212e756bc055df8fdd2f9f589a2e539ba1abe755b8ce8df7a4", size = 602130, upload-time = "2026-01-08T15:47:34.877Z" },
@@ -3755,6 +3779,69 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0a/20/a6929e98d9a461ca49e96194a82a1cc3fd5420f3a2f53cbb34fca438549e/uuid_utils-0.13.0-pp311-pypy311_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:b7ccaa20e24c5f60f41a69ef571ed820737f9b0ade4cbeef56aaa8f80f5aa475", size = 333610, upload-time = "2026-01-08T15:48:09.375Z" },
]
[[package]]
name = "uuid-utils"
version = "0.19.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.11' and python_full_version < '3.14'",
]
sdist = { url = "https://files.pythonhosted.org/packages/f1/0f/cd9b3e495e514441e9af3a42c5c63436aee620f61412cafcce67e259228b/uuid_utils-0.19.0.tar.gz", hash = "sha256:4b2996bb9d12da9b62f6f4c64a066b26926fa75dbe47b4f8e309e24e187abf1d", size = 43343, upload-time = "2026-10-26T10:55:23.821Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fa/ce/78f9d79c820b8afc9b0498b0a5f5ef87ea970a19cc382127087f36cd4839/uuid_utils-0.19.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:84929d8c594381c6f1444afa2a25cce9ff7edac99d35e071ebc48275bae83730", size = 574701, upload-time = "2026-10-26T10:54:06.751Z" },
{ url = "https://files.pythonhosted.org/packages/fc/63/fe81dda73d651fb6bf8b0e21c4ea34209fc535ca109634c0d93f14ac21dc/uuid_utils-0.19.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:a416c78167b750186095a0ea950d95b8b4e157e3172fc6ccdc317b6ca2896580", size = 294226, upload-time = "2026-10-26T10:54:08.422Z" },
{ url = "https://files.pythonhosted.org/packages/81/6c/118c7e11fb579b5631212bfbbb31542a1778936dfb7c55591aa9b52c7c99/uuid_utils-0.19.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82573c0e5090732c0ae2e18626e06cb35fe7cfcf6fe9534d4c1638af41d25d36", size = 327869, upload-time = "2026-10-26T10:54:10.016Z" },
{ url = "https://files.pythonhosted.org/packages/a3/76/80e9bad735cbf92f127183ee5c44ee0d83604e56197bddcaaaed2c66f7e6/uuid_utils-0.19.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c78d527fddf5495e80032caeca2f140731ef394298ef4cbecaaf51aa2c257e1e", size = 334737, upload-time = "2026-10-26T10:54:11.434Z" },
{ url = "https://files.pythonhosted.org/packages/9e/7b/c8eb8e45569e8681f20e381859248d18635d823b8aedc70de8a0bd5faca6/uuid_utils-0.19.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c33b5c62cf82d509945c770201b1656448c2560e12931d1a8d698a683e9125d", size = 440438, upload-time = "2026-10-26T10:54:12.864Z" },
{ url = "https://files.pythonhosted.org/packages/be/45/8238eac588b668e5c500cb187fd74a9f6d984c09f3f3f2fa4161d3c07eb7/uuid_utils-0.19.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8060530a8fa27f11947489c79bc36e6ebb465b2d714945364dc397283309417", size = 330342, upload-time = "2026-10-26T10:54:14.309Z" },
{ url = "https://files.pythonhosted.org/packages/0e/45/8215129786e67842d479480a44066cac8a7086f345ac9707f629ed777cfe/uuid_utils-0.19.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:96429c78eab28ac71b2121fb47c66783bfe5d06632b5bd5200004707fdc0faad", size = 354275, upload-time = "2026-10-26T10:54:15.753Z" },
{ url = "https://files.pythonhosted.org/packages/7f/bc/a673bfd116eb26419dd82bffa9bc2bc1e915af02bf68ac8c09598dc678ec/uuid_utils-0.19.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80e0b37b3ddf60c50c51b869e1b02df61415941aa68bc903f61562fd8df04b18", size = 504446, upload-time = "2026-10-26T10:54:17.45Z" },
{ url = "https://files.pythonhosted.org/packages/af/ff/61867210f202c603d4a5ca1c5b3c4c76e8d8724eeebd7cf1307d18b63a9a/uuid_utils-0.19.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f629f2a4f95e46d99ee673536ab9706899696cdc6686e93314ea756d3b394ce4", size = 609016, upload-time = "2026-10-26T10:54:18.914Z" },
{ url = "https://files.pythonhosted.org/packages/42/81/6fb8a2ee8010a6a0c5cb29f5efed2604717613cc9888f9e5c999dc36b84b/uuid_utils-0.19.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:ec330297c09e40b82d613326b57bb381d68535e8a341fa2e501b46791cbdfeec", size = 570293, upload-time = "2026-10-26T10:54:20.328Z" },
{ url = "https://files.pythonhosted.org/packages/0f/2f/e1c749b65c584ebd8bc5379cf18e26c67cdff8f2bd9f8f77111a47083408/uuid_utils-0.19.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:313de97d59dbbb09f4598cece2c799cc233d8673bb075ad7eff442cd485623c2", size = 535321, upload-time = "2026-10-26T10:54:21.735Z" },
{ url = "https://files.pythonhosted.org/packages/87/ba/f423f68fd784124e68ab1b1afe27ab4591ffd5363b39eccfcbaf8f3b0ff3/uuid_utils-0.19.0-cp310-abi3-pyemscripten_2025_0_wasm32.whl", hash = "sha256:5c47c7fc35e91a801996477f0d746d506a2e5b47a13accbf41dc29844786c40c", size = 103141, upload-time = "2026-10-26T10:54:23.192Z" },
{ url = "https://files.pythonhosted.org/packages/f1/9a/7a0a0dad166db9f4437f3d259e47ce74d552ccc50d8aa63d05b2510e3096/uuid_utils-0.19.0-cp310-abi3-pyemscripten_2026_0_wasm32.whl", hash = "sha256:23868cdc4dfa71021fec6cf43fbb83797345362ac1c866b50dbf17f479587413", size = 103069, upload-time = "2026-10-26T10:54:24.709Z" },
{ url = "https://files.pythonhosted.org/packages/e1/3c/c5fef96cea027de66a035b4173951fc29c2e83ed49a5d5b3fa7b632f95b5/uuid_utils-0.19.0-cp310-abi3-win32.whl", hash = "sha256:0cff166cf6348e9a705740e6575ce7bc727554a71a6add60b9ceceaaf169002a", size = 175479, upload-time = "2026-10-26T10:54:25.949Z" },
{ url = "https://files.pythonhosted.org/packages/3f/16/edfb8edc8a4624cfaf1387a57bf37cd53e5d7f213ebeb8dd6fbd2f6b2c64/uuid_utils-0.19.0-cp310-abi3-win_amd64.whl", hash = "sha256:b763ae8f23334ccd95d0b81fb19ee418f38f9e80ebfb521f313a9745280bc07c", size = 182891, upload-time = "2026-10-26T10:54:27.527Z" },
{ url = "https://files.pythonhosted.org/packages/63/17/258e5dd380bc594731b2b705082360fb4cef1e5cbaab46ef307439a6f415/uuid_utils-0.19.0-cp310-abi3-win_arm64.whl", hash = "sha256:b244ea25bfe1b34e40fabf8f079d59a9f153b22bd4095842065bb0a9dfaa477b", size = 180849, upload-time = "2026-10-26T10:54:28.86Z" },
{ url = "https://files.pythonhosted.org/packages/cb/64/b5554f686d5395db970bc07944b9c59c26ab0e192a27d7c8b555e5fc6332/uuid_utils-0.19.0-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:9adf8d69574f0018a25aed0cc194bffacc05fb91b922fdd8e794109c45ad80ed", size = 565592, upload-time = "2026-10-26T10:54:30.242Z" },
{ url = "https://files.pythonhosted.org/packages/4a/a3/d8d0b7bf4892c41dbbb0e81a356d20aad781cc28d3029669e167eb72d9a1/uuid_utils-0.19.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ac37f30acc7fe01266c73f19a686d0ea1fbd22495d81205cc3b572e2fb2293a6", size = 292083, upload-time = "2026-10-26T10:54:32.053Z" },
{ url = "https://files.pythonhosted.org/packages/fa/c1/770ccb569b4c15ee81c36bc40e74f03c5e0a2ee71ecc534d3fe7cfa5696d/uuid_utils-0.19.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afc1dc05c83163ec58304350ee61d20f1f3285d0e8fb873e4d85a97988c284ad", size = 323275, upload-time = "2026-10-26T10:54:33.896Z" },
{ url = "https://files.pythonhosted.org/packages/c7/12/798e09469358bf59848534291aa62d16cc6fbd66767f469c5d8278d0bc4d/uuid_utils-0.19.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16e68b1a4b81301fa7ba52d500947094226d8b0c1521695746ec57ff0779ff6f", size = 328681, upload-time = "2026-10-26T10:54:35.506Z" },
{ url = "https://files.pythonhosted.org/packages/42/16/71df642a7969381747de36d4e365e59683cdaa2c425da0530e9383e4de86/uuid_utils-0.19.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:833edd2b38ef08a324e9f3aed4269d2ab955d4cb66417422eeb3af28a6237427", size = 434504, upload-time = "2026-10-26T10:54:36.96Z" },
{ url = "https://files.pythonhosted.org/packages/c1/18/35c86d901bbd6d3f088217f37a6ff01aae4ce8f8cdb3f9ddb5220887a122/uuid_utils-0.19.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21356ba99b82c813c1945054a09ff7ebc1bf55b858b41a6ae084e6ed2c8bb9a9", size = 325810, upload-time = "2026-10-26T10:54:38.492Z" },
{ url = "https://files.pythonhosted.org/packages/fc/51/25427eee1031946d5abe0d8985123f6fd4a1931c8a5a8660b1c76368ca12/uuid_utils-0.19.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2477161656105e787a5f986adb63191b61f129c9d7399b2bce406f87a1407b4d", size = 346300, upload-time = "2026-10-26T10:54:39.864Z" },
{ url = "https://files.pythonhosted.org/packages/26/4b/edbb0ebdc948549a0250363f441883c31fef42fb4e4bb607b9a083efaeba/uuid_utils-0.19.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ddf61d8443993d683c8d6fcd05cbb711c2655042952850fc0cf25af969e3a213", size = 501365, upload-time = "2026-10-26T10:54:41.268Z" },
{ url = "https://files.pythonhosted.org/packages/68/e4/f5a788fae45e315d8d04d5889a0b0809543e27c8d08b9fe10b09d0766b9b/uuid_utils-0.19.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cb9ead0aeae67ad2de3f660f2e978ece77e14ad3f2a7082b768e2c75866664a2", size = 604290, upload-time = "2026-10-26T10:54:42.686Z" },
{ url = "https://files.pythonhosted.org/packages/a7/90/3800a0b2083d6c156a4d686e75f26c055a48fcf315b9310ba211b7e3659c/uuid_utils-0.19.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4e5155902b7779ae7b692ac69e6031f41481d62fa85a530d3725c270799940a0", size = 563566, upload-time = "2026-10-26T10:54:44.068Z" },
{ url = "https://files.pythonhosted.org/packages/7f/37/1b58a17a041bd560a1d652afccd57ecd5d0e0c4d6280deded663b783de43/uuid_utils-0.19.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd3bcc556e249bb70b1c7fd78e659a1ebfb5572e7ff2091d096aecbac62edb1c", size = 531143, upload-time = "2026-10-26T10:54:45.599Z" },
{ url = "https://files.pythonhosted.org/packages/66/ac/f6286bcfad2f321be2acd04961934a58e4a6c15ba78070a13d193cbbb9f0/uuid_utils-0.19.0-cp314-cp314t-win32.whl", hash = "sha256:9b9064d96c6fe5e43ff4486722c5c891083a7fa91ee36311286cfd833b4b5562", size = 172229, upload-time = "2026-10-26T10:54:46.925Z" },
{ url = "https://files.pythonhosted.org/packages/87/45/0df876abf12875ef82bcd8f034c0c7adc1507dd721a5942232120b6267e8/uuid_utils-0.19.0-cp314-cp314t-win_amd64.whl", hash = "sha256:eeaf6b77ed2ec9815988e10d043d1533a1ae8c6535e7fb5479fd7e2e6ad25dff", size = 178436, upload-time = "2026-10-26T10:54:48.249Z" },
{ url = "https://files.pythonhosted.org/packages/d2/ae/c9b4aaa8ee95581b444a7be681dc0ceb57a465b50b28b2f2517f5fec1c51/uuid_utils-0.19.0-cp314-cp314t-win_arm64.whl", hash = "sha256:ebb011b9010ddbd8acd4650896e87db2715f2cfc70fb194c03ebcbf4150d3844", size = 175725, upload-time = "2026-10-26T10:54:49.562Z" },
{ url = "https://files.pythonhosted.org/packages/41/6e/3a3fdaa9689b687f105ba885e61f6b2e8336ec4d209fd51d41923ad83479/uuid_utils-0.19.0-cp315-cp315t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8c76f01a815a0ca8b698202f2f5e40098a4b3cd56305e1fc9ed3d959ac718ff0", size = 565325, upload-time = "2026-10-26T10:54:50.924Z" },
{ url = "https://files.pythonhosted.org/packages/5e/1a/23dacf86dfe52f4606694501a6072379156025a3c6988e16f477f627249c/uuid_utils-0.19.0-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:f3828e1fc0831a9d539e702992e9fcfeb400e80f737467b8e557921cdc8ae067", size = 291930, upload-time = "2026-10-26T10:54:52.529Z" },
{ url = "https://files.pythonhosted.org/packages/4e/f2/adf223ded569b0d75e8ac10db14be8fc253da900a63e678436bc3ff3b72a/uuid_utils-0.19.0-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25ddec2775c2f500a36029ab93af09f39d5c417786df02a464464e030152014", size = 322729, upload-time = "2026-10-26T10:54:54.01Z" },
{ url = "https://files.pythonhosted.org/packages/88/df/0f78e7f22c778d2e48ef092a3a1c44d5b8b487b7e178e1044e7a39195b17/uuid_utils-0.19.0-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1fc412a5ef2f1e6f96d132a6656af59bae2dba9fd04f8df2a2f6eb913fe6180a", size = 328838, upload-time = "2026-10-26T10:54:55.473Z" },
{ url = "https://files.pythonhosted.org/packages/b3/96/9a9d6f7bef37b3545203eaf643e7f4d574f4ea3a6364af8865c071847df5/uuid_utils-0.19.0-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94cbef978d789b18014c477f51c8311800803cd2a0c7e1de6d2af85377210053", size = 433992, upload-time = "2026-10-26T10:54:56.911Z" },
{ url = "https://files.pythonhosted.org/packages/07/92/62f67dc40a036ab890ac3f55eaab610999c713654bb3d0b56d5676ce3eaa/uuid_utils-0.19.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4b012d2a2c5516e11ded4fb70cdd730f36e2d40b45018b9a40c6605dc5c0666", size = 326138, upload-time = "2026-10-26T10:54:58.533Z" },
{ url = "https://files.pythonhosted.org/packages/c8/b7/740525de1aa1a150eb7a80a667c997d54284f407b7f3b38ce8f90f948813/uuid_utils-0.19.0-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4062c0dfd6697ce7c91a504d8ff6eb8dcaf4e790fb6adec69c4c83fd89870d4a", size = 346714, upload-time = "2026-10-26T10:55:00.01Z" },
{ url = "https://files.pythonhosted.org/packages/3c/1a/c9d2be770a2061da608164d022becda3fc7ddabb34ec7a1950d29ae51ca0/uuid_utils-0.19.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:361cfd0fba0a49f1c8b6df741a35f7d948f0fae3d3914f8622ba261dc4656a5a", size = 500974, upload-time = "2026-10-26T10:55:01.392Z" },
{ url = "https://files.pythonhosted.org/packages/36/2e/ee7522207a7f9477c58757f040f05c4eedb68f362623d4e40da4983bdf5b/uuid_utils-0.19.0-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:a15c8943e02ac59992eaad1ce69793720dc711ce66188d386be1cbf8f5b5a2e4", size = 604607, upload-time = "2026-10-26T10:55:03.141Z" },
{ url = "https://files.pythonhosted.org/packages/07/b8/c9a1f21627238171a2021dd777177a211df20b12c0c5e45b828ee9bc3c84/uuid_utils-0.19.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:4bd00a485e973cb3bcefe06f13a663cf4ee0c54caeacb2a656a5f901d1505a03", size = 563749, upload-time = "2026-10-26T10:55:04.809Z" },
{ url = "https://files.pythonhosted.org/packages/e5/9e/ed7d14d4d7bdb94159909e722337a557c855e8990a7fdff55ef6b016fb38/uuid_utils-0.19.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:9fe68c4b2e017fb13c5e460b76b76c5cfa717ddd653cea184c2664bc2a852011", size = 530994, upload-time = "2026-10-26T10:55:06.511Z" },
{ url = "https://files.pythonhosted.org/packages/07/92/5b84a87dd4127a14337d42eb4a24a764583e4decc2675a9949d4568b16e1/uuid_utils-0.19.0-cp315-cp315t-win32.whl", hash = "sha256:5397581f40e268535cd48230e4f2f372dc5f3e70ce79e0aff9cb5ceb838163c8", size = 172512, upload-time = "2026-10-26T10:55:08.261Z" },
{ url = "https://files.pythonhosted.org/packages/d6/43/a11a33e39f64c50f17483842652933b8ef769ce7cac3399ed7d2a2b928c2/uuid_utils-0.19.0-cp315-cp315t-win_amd64.whl", hash = "sha256:225752e3c36b7c861eb1bbceb5c9197c134c806b210ed53dcacf6339a3d599e6", size = 178626, upload-time = "2026-10-26T10:55:09.658Z" },
{ url = "https://files.pythonhosted.org/packages/ab/78/4f9dc891db194ce8107c9704d7e2e51da558af457e64134da1b1a7c5db1f/uuid_utils-0.19.0-cp315-cp315t-win_arm64.whl", hash = "sha256:bfcda2352c1500230227da5475139ab177bb2516309254cd74ade19a28cb842d", size = 175582, upload-time = "2026-10-26T10:55:11.001Z" },
{ url = "https://files.pythonhosted.org/packages/61/60/751fb8688322e107e4b4b66747f084ac0e3de599eec6f800b1661b1bbf7b/uuid_utils-0.19.0-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:492e621a6aa0d8b4a47cad5e71b9fbbebf5e7af6d0f568daea65d38353f713c1", size = 569090, upload-time = "2026-10-26T10:55:12.449Z" },
{ url = "https://files.pythonhosted.org/packages/7a/0c/2971bf13e98e2cab194fb43a81c3ffc47d2eaf84d4669d1d5b96ed89d98f/uuid_utils-0.19.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:68ab7e44d8be02c5d34e2501c465e32747b384c04cf7476de80c01729466a9dc", size = 290711, upload-time = "2026-10-26T10:55:13.836Z" },
{ url = "https://files.pythonhosted.org/packages/bf/18/a446ae37e9489a4dd905c738fcd98a4a49cd3c57ba36b300fc50372162b8/uuid_utils-0.19.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0edba30609b6f1d6bc5eb9be5cc90759bf5d0c77f132d2b63fc7c62038b9304e", size = 325610, upload-time = "2026-10-26T10:55:15.237Z" },
{ url = "https://files.pythonhosted.org/packages/41/ee/32ae97908244fdbed279b4c7215fe186891a9aa1fe9d25f6760b4fe415bc/uuid_utils-0.19.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6e137ad199860077c8cf732b71fc8350dbf4bc5e427c01fb82b27dbddd45e370", size = 333087, upload-time = "2026-10-26T10:55:16.728Z" },
{ url = "https://files.pythonhosted.org/packages/25/f5/dc1ad3783eb0409a912d7c87f0f00143b450cb60cecbb9e1648aabc06f30/uuid_utils-0.19.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42f53089ba59a2d1777be98db285aa4948169d434421ee1474ce4615215326ea", size = 438894, upload-time = "2026-10-26T10:55:18.202Z" },
{ url = "https://files.pythonhosted.org/packages/5d/4c/07bbee76758ee4e425455477120899463eb8e05e5c533eb61c6314bf188c/uuid_utils-0.19.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10d689c0f2715502b7d42426947e3bb5c76ac5ba91ae9bd23afc34283189b82", size = 328432, upload-time = "2026-10-26T10:55:19.512Z" },
{ url = "https://files.pythonhosted.org/packages/14/d6/1e70bbe1c3828fec80b060137f11db69d16b4f40f8ae32526d98b9e604ec/uuid_utils-0.19.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0da57ba80366ae75307b686376bf437bdc76b3710d52e6ffe012c99a5c800927", size = 351628, upload-time = "2026-10-26T10:55:20.922Z" },
{ url = "https://files.pythonhosted.org/packages/79/df/75f58b9ee63449e8f81b1698e9e3cb76efada622e6b5590b329d26242a6b/uuid_utils-0.19.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bb0c63bfa8d9af4228353b5c1b06917acf76067674d4d741c31d2117a3db09c5", size = 181546, upload-time = "2026-10-26T10:55:22.522Z" },
]
[[package]]
name = "uvicorn"
version = "0.40.0"
+1 -1
View File
@@ -286,7 +286,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.2.19"
version = "1.2.20"
source = { editable = "../langgraph" }
dependencies = [
{ name = "langchain-core" },
+1 -1
View File
@@ -5,7 +5,7 @@ from langgraph_sdk.encryption.types import DecryptResult, EncryptionContext
from langgraph_sdk.priority import Priority
from langgraph_sdk.priority.types import PriorityContext, PrioritySettings
__version__ = "0.4.12"
__version__ = "0.4.14"
__all__ = [
"Auth",
+23 -1
View File
@@ -100,6 +100,7 @@ class RunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
strict_resume: bool | None = None,
version: Literal["v1"] = "v1",
) -> AsyncIterator[StreamPart]: ...
@@ -133,6 +134,7 @@ class RunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
strict_resume: bool | None = None,
version: Literal["v2"],
) -> AsyncIterator[StreamPartV2]: ...
@@ -228,6 +230,7 @@ class RunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
strict_resume: bool | None = None,
version: StreamVersion = "v1",
) -> AsyncIterator[StreamPart | StreamPartV2]:
"""Create a run and stream the results.
@@ -272,6 +275,10 @@ class RunsClient:
"async" means checkpoints are persisted async while next graph step executes, replaces checkpoint_during=True
"sync" means checkpoints are persisted sync after graph step executes, replaces checkpoint_during=False
"exit" means checkpoints are only persisted when the run exits, does not save intermediate steps
strict_resume: Refuse a resume that answers an interrupt that isn't waiting,
instead of ignoring the answer: the run fails with `UnknownInterruptError`
and the thread stays as it was. Takes effect on servers whose `/info`
reports `strict_resume`.
version: Stream format version. "v1" (default) returns raw SSE StreamPart
NamedTuples. "v2" returns typed dicts with `type`, `ns`, and `data` keys.
@@ -305,7 +312,7 @@ class RunsClient:
StreamPart(event='metadata', data={'run_id': '1ef4a9b8-d7da-679a-a45a-872054341df2'})
StreamPart(event='values', data={'messages': [{'content': 'how are you?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'fe0a5778-cfe9-42ee-b807-0adaa1873c10', 'example': False}]})
StreamPart(event='values', data={'messages': [{'content': 'how are you?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'fe0a5778-cfe9-42ee-b807-0adaa1873c10', 'example': False}, {'content': "I'm doing well, thanks for asking! I'm an AI assistant created by Anthropic to be helpful, honest, and harmless.", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-159b782c-b679-4830-83c6-cef87798fe8b', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]})
StreamPart(event='end', data=None)
StreamPart(event='end', data={'run_id': '1ef4a9b8-d7da-679a-a45a-872054341df2', 'status': 'success'})
```
"""
@@ -341,6 +348,7 @@ class RunsClient:
"on_completion": on_completion,
"after_seconds": after_seconds,
"durability": durability,
"strict_resume": strict_resume,
"langsmith_tracer": langsmith_tracing,
}
endpoint = (
@@ -422,6 +430,7 @@ class RunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
strict_resume: bool | None = None,
) -> Run: ...
async def create(
@@ -452,6 +461,7 @@ class RunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
strict_resume: bool | None = None,
) -> Run:
"""Create a background run.
@@ -491,6 +501,10 @@ class RunsClient:
"async" means checkpoints are persisted async while next graph step executes, replaces checkpoint_during=True
"sync" means checkpoints are persisted sync after graph step executes, replaces checkpoint_during=False
"exit" means checkpoints are only persisted when the run exits, does not save intermediate steps
strict_resume: Refuse a resume that answers an interrupt that isn't waiting,
instead of ignoring the answer: the run fails with `UnknownInterruptError`
and the thread stays as it was. Takes effect on servers whose `/info`
reports `strict_resume`.
Returns:
The created background run.
@@ -595,6 +609,7 @@ class RunsClient:
"on_completion": on_completion,
"after_seconds": after_seconds,
"durability": durability,
"strict_resume": strict_resume,
"langsmith_tracer": langsmith_tracing,
}
payload = {k: v for k, v in payload.items() if v is not None}
@@ -656,6 +671,7 @@ class RunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
strict_resume: bool | None = None,
) -> builtins.list[dict] | dict[str, Any]: ...
@overload
@@ -712,6 +728,7 @@ class RunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
strict_resume: bool | None = None,
) -> builtins.list[dict] | dict[str, Any]:
"""Create a run, wait until it finishes and return the final state.
@@ -749,6 +766,10 @@ class RunsClient:
"async" means checkpoints are persisted async while next graph step executes, replaces checkpoint_during=True
"sync" means checkpoints are persisted sync after graph step executes, replaces checkpoint_during=False
"exit" means checkpoints are only persisted when the run exits, does not save intermediate steps
strict_resume: Refuse a resume that answers an interrupt that isn't waiting,
instead of ignoring the answer: the run fails with `UnknownInterruptError`
and the thread stays as it was. Takes effect on servers whose `/info`
reports `strict_resume`.
Returns:
The output of the run.
@@ -829,6 +850,7 @@ class RunsClient:
"on_completion": on_completion,
"after_seconds": after_seconds,
"durability": durability,
"strict_resume": strict_resume,
"langsmith_tracer": langsmith_tracing,
}
endpoint = (
+23 -1
View File
@@ -100,6 +100,7 @@ class SyncRunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
strict_resume: bool | None = None,
version: Literal["v1"] = "v1",
) -> Iterator[StreamPart]: ...
@@ -133,6 +134,7 @@ class SyncRunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
strict_resume: bool | None = None,
version: Literal["v2"],
) -> Iterator[StreamPartV2]: ...
@@ -228,6 +230,7 @@ class SyncRunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
strict_resume: bool | None = None,
version: StreamVersion = "v1",
) -> Iterator[StreamPart | StreamPartV2]:
"""Create a run and stream the results.
@@ -271,6 +274,10 @@ class SyncRunsClient:
"async" means checkpoints are persisted async while next graph step executes, replaces checkpoint_during=True
"sync" means checkpoints are persisted sync after graph step executes, replaces checkpoint_during=False
"exit" means checkpoints are only persisted when the run exits, does not save intermediate steps
strict_resume: Refuse a resume that answers an interrupt that isn't waiting,
instead of ignoring the answer: the run fails with `UnknownInterruptError`
and the thread stays as it was. Takes effect on servers whose `/info`
reports `strict_resume`.
version: Stream format version. "v1" (default) returns raw SSE StreamPart
NamedTuples. "v2" returns typed dicts with `type`, `ns`, and `data` keys.
@@ -302,7 +309,7 @@ class SyncRunsClient:
StreamPart(event='metadata', data={'run_id': '1ef4a9b8-d7da-679a-a45a-872054341df2'})
StreamPart(event='values', data={'messages': [{'content': 'how are you?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'fe0a5778-cfe9-42ee-b807-0adaa1873c10', 'example': False}]})
StreamPart(event='values', data={'messages': [{'content': 'how are you?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'fe0a5778-cfe9-42ee-b807-0adaa1873c10', 'example': False}, {'content': "I'm doing well, thanks for asking! I'm an AI assistant created by Anthropic to be helpful, honest, and harmless.", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-159b782c-b679-4830-83c6-cef87798fe8b', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]})
StreamPart(event='end', data=None)
StreamPart(event='end', data={'run_id': '1ef4a9b8-d7da-679a-a45a-872054341df2', 'status': 'success'})
```
"""
if checkpoint_during is not None:
@@ -336,6 +343,7 @@ class SyncRunsClient:
"on_completion": on_completion,
"after_seconds": after_seconds,
"durability": durability,
"strict_resume": strict_resume,
"langsmith_tracer": langsmith_tracing,
}
endpoint = (
@@ -417,6 +425,7 @@ class SyncRunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
strict_resume: bool | None = None,
) -> Run: ...
def create(
@@ -447,6 +456,7 @@ class SyncRunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
strict_resume: bool | None = None,
) -> Run:
"""Create a background run.
@@ -486,6 +496,10 @@ class SyncRunsClient:
"async" means checkpoints are persisted async while next graph step executes, replaces checkpoint_during=True
"sync" means checkpoints are persisted sync after graph step executes, replaces checkpoint_during=False
"exit" means checkpoints are only persisted when the run exits, does not save intermediate steps
strict_resume: Refuse a resume that answers an interrupt that isn't waiting,
instead of ignoring the answer: the run fails with `UnknownInterruptError`
and the thread stays as it was. Takes effect on servers whose `/info`
reports `strict_resume`.
Returns:
The created background `Run`.
@@ -590,6 +604,7 @@ class SyncRunsClient:
"on_completion": on_completion,
"after_seconds": after_seconds,
"durability": durability,
"strict_resume": strict_resume,
"langsmith_tracer": langsmith_tracing,
}
payload = {k: v for k, v in payload.items() if v is not None}
@@ -651,6 +666,7 @@ class SyncRunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
strict_resume: bool | None = None,
) -> builtins.list[dict] | dict[str, Any]: ...
@overload
@@ -707,6 +723,7 @@ class SyncRunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
strict_resume: bool | None = None,
) -> builtins.list[dict] | dict[str, Any]:
"""Create a run, wait until it finishes and return the final state.
@@ -745,6 +762,10 @@ class SyncRunsClient:
"async" means checkpoints are persisted async while next graph step executes, replaces checkpoint_during=True
"sync" means checkpoints are persisted sync after graph step executes, replaces checkpoint_during=False
"exit" means checkpoints are only persisted when the run exits, does not save intermediate steps
strict_resume: Refuse a resume that answers an interrupt that isn't waiting,
instead of ignoring the answer: the run fails with `UnknownInterruptError`
and the thread stays as it was. Takes effect on servers whose `/info`
reports `strict_resume`.
Returns:
The output of the `Run`.
@@ -826,6 +847,7 @@ class SyncRunsClient:
"on_completion": on_completion,
"after_seconds": after_seconds,
"durability": durability,
"strict_resume": strict_resume,
"langsmith_tracer": langsmith_tracing,
}
+7
View File
@@ -58,6 +58,8 @@ StreamMode = Literal[
"debug",
"custom",
"messages-tuple",
"tools",
"lifecycle",
]
"""
Defines the mode of streaming:
@@ -69,6 +71,8 @@ Defines the mode of streaming:
- "tasks": Stream task start and finish events.
- "debug": Stream detailed debug information.
- "custom": Stream custom events.
- "tools": Stream tool events: `on_tool_start`, `on_tool_event`, `on_tool_end` and `on_tool_error`.
- "lifecycle": Stream an event when each subgraph starts and ends, with the id of the tool call that started it.
"""
DisconnectMode = Literal["cancel", "continue"]
@@ -380,6 +384,8 @@ class Run(TypedDict):
"""The run metadata."""
multitask_strategy: MultitaskStrategy
"""Strategy to handle concurrent runs on the same thread."""
cancel_reason: NotRequired[str | None]
"""Why the run was cancelled, if it was. Omitted by older servers."""
class Cron(TypedDict):
@@ -505,6 +511,7 @@ RunSelectField = Literal[
"metadata",
"kwargs",
"multitask_strategy",
"cancel_reason",
]
CronSelectField = Literal[
+1 -1
View File
@@ -136,7 +136,7 @@ def test_sync_api_matches_async(async_cls, sync_cls):
# and stateful ones (`thread_id: str`) leave out `on_completion`, which only
# applies to stateless runs.
_RUN_OVERLOAD_OMISSIONS = {
"None": {"checkpoint", "checkpoint_id", "multitask_strategy"},
"None": {"checkpoint", "checkpoint_id", "multitask_strategy", "strict_resume"},
"str": {"on_completion"},
}
+52
View File
@@ -212,3 +212,55 @@ async def test_wait_returns_the_run_error_without_raise_error(asynchronous):
assert result == run_error
assert "raise_error" not in json.loads(requests[0].content)
def _strict_resume_handler(requests: list[httpx.Request]):
def handler(request: httpx.Request) -> httpx.Response:
requests.append(request)
if request.url.path.endswith("/stream"):
return httpx.Response(
200,
headers={"Content-Type": "text/event-stream"},
content=b"event: end\ndata: null\n\n",
)
return httpx.Response(200, json={"run_id": "r1"})
return handler
@pytest.mark.parametrize("method", ["create", "stream", "wait"])
@pytest.mark.parametrize("asynchronous", [False, True])
@pytest.mark.parametrize("strict_resume", [True, None], ids=["opted-in", "default"])
async def test_runs_send_strict_resume_only_when_set(
method: str, asynchronous: bool, strict_resume: bool | None
):
requests: list[httpx.Request] = []
transport = httpx.MockTransport(_strict_resume_handler(requests))
kwargs: dict[str, Any] = {
"command": {"resume": {"0" * 32: "yes"}},
"strict_resume": strict_resume,
}
if asynchronous:
async with httpx.AsyncClient(
transport=transport, base_url="https://example.com"
) as client:
result = getattr(RunsClient(HttpClient(client)), method)(
"t1", "agent", **kwargs
)
if method == "stream":
async for _ in result:
pass
else:
await result
else:
with httpx.Client(
transport=transport, base_url="https://example.com"
) as client:
result = getattr(SyncRunsClient(SyncHttpClient(client)), method)(
"t1", "agent", **kwargs
)
if method == "stream":
for _ in result:
pass
assert json.loads(requests[0].content).get("strict_resume") == strict_resume
+1 -1
View File
@@ -301,7 +301,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.2.19"
version = "1.2.20"
source = { editable = "../langgraph" }
dependencies = [
{ name = "langchain-core" },