Repository navigation
Conversation
…iterable - collect_helpers walked messages before the request body was built from the same object, so a generator reached the body exhausted and beta create, parse, stream and tool_runner sent messages: [] - only a sequence is inspected now, which can be read twice; a one-shot iterable is left alone and keeps its contents - the tool runner collects tags from the list it already materialized - tests for create, parse and tool_runner on both clients
sigley
approved these changes
Oct 7, 2026
sigley
left a comment
There was a problem hiding this comment.
Validated exact head 0264c11c independently. A generator is a valid Iterable[BetaMessageParam], and current main can consume it while collecting helper tags before the same iterable is serialized into the request body. This head leaves one-shot iterables untouched in the helper collector and makes the tool runner use its already-materialized message list, so the wire body retains the caller's messages. The branch merges cleanly with current main; tests/lib/test_stainless_helpers.py passes 20/20 and compile/diff checks are clean. The possible loss of a helper telemetry tag for generator-backed messages is an acceptable non-wire tradeoff here. I do not see a blocking correctness issue.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
messagesis typedIterable[BetaMessageParam], so a generator is a legal argument.collect_helperswalks it to gather helper tags, and the request body is built from the same object a few lines later, so the body gets an iterable that has already been consumed.Before, the request goes out as
{"messages": [], ...}and the API answers with a 400 aboutmessages. Nothing is logged on the client side, so it reads like a server-side complaint about a field the caller did fill in. After, the request carries the message.It hits
beta.messages.create,parse,streamandtool_runner.tool_runneris the worst of them:BetaToolRunner.__init__does keep its own copy with[message for message in params["messages"]], but the header is built first, so the copy is already empty.count_tokensis unaffected, it does not build the header, and the stablemessagesresource does not either.What changed
collect_helpersonly readsmessageswhen it is aSequence, which can be read twice. A one-shot iterable is left untouched, so the body keeps its contents. The cost is that a helper tag carried by a message inside a generator is not collected, which affects the telemetry header only, never the request. If you would rather keep tag fidelity, the alternative is amessages = list(messages)next to the existingtools = _to_tool_params(tools)at each of the eight call sites, and I will switch it over.The tool runner now passes
self._params["messages"], the list it has just materialized, to the header builder instead of the caller's iterable it consumed building that list.Wire-level: a request that used to be sent with an empty
messagesarray is now sent with the messages. The header can lose a tag in the generator case described above.How I know it works
Six new tests in
tests/lib/test_stainless_helpers.py, coveringcreate,parseandtool_runneron the sync and async clients, asserting on the wire body throughrespx. All six fail onmainand pass with the change.streamshares the same path and is left to the fixture-based tests.tests/libis green other thantest_aws*andtest_bedrock*, which needbotocorethat I do not have installed, andtests/test_client.pypasses, 197 tests.No cross-SDK port needed.
collectStainlessHelpersin the TypeScript SDK takesreadonly unknown[], an array it can read again, so it has no equivalent of this.