fix(aws-strands): unify the terminal error codes and message text across both bridges - #2585
Open
ranst91 wants to merge 1 commit into
Open
fix(aws-strands): unify the terminal error codes and message text across both bridges#2585ranst91 wants to merge 1 commit into
ranst91 wants to merge 1 commit into
Conversation
…oss both bridges The two AWS Strands bridges had drifted on the RUN_ERROR frames they put on the wire. Clients and mock harnesses match both the code and the message literally, so the drift broke consumers rather than merely looking untidy. Two behaviour differences are resolved, both toward failing loud. When every attached image or document fails to convert and no text remains, TypeScript aborted the run and Python logged a warning and carried on with an empty prompt. Python now emits MEDIA_RESOLUTION_FAILED and ends the run, with the same message TypeScript already used. The degrade below that gate, where text does survive, is unchanged; note that under the default configuration the replay path supplies history and the recovered text reaches the model that way rather than through the fallback assignment. TypeScript distinguished a defect in this adapter from a failure elsewhere and Python labelled everything an SDK failure. Python now makes the same split. The rule then lives in one function per language rather than at four catch sites, and a fault arriving from inside the Strands call is marked foreign before it reaches the classifier, so an integrator's unserializable tool result or a client's malformed state is no longer reported as this adapter's bug. Wrapping a fault is total on both sides: it cannot itself raise the type it exists to rule out. Shared codes now carry identical text. PARTIAL_RESUME and INVALID_PAYLOAD render one sorted comma-joined list instead of a Python list repr on one side and a JSON array on the other. UNKNOWN_INTERRUPT_ID uses one spelling. THREAD_BUSY shares an opening and a tail through a scope helper on each side, and the parked-orchestrator refusal, which only Python can produce, is its own sentence because no run is in flight then and answering the interrupt rather than waiting for RUN_FINISHED is the way out. Both bridges pick the article for a vowel-initial JSON Schema type, so an integer violation reads correctly. Two wire changes beyond the codes, both for parity. A stream failure in TypeScript no longer splits by exception type: every failure from that call now reports STRANDS_FORCE_STOP after the message and tool-call closeout, which is what Python already did, where one branch previously reported STRANDS_ERROR and skipped the closeout. And a non-mapping client state now yields no opening state snapshot on either side, where TypeScript put an array on the wire as an index-keyed object that Python never produced. The guard against this recurring is error-codes.json beside ARCHITECTURE.md. It is not a hand-maintained checklist. Each side derives from its own source every code it can emit and the normalised message template for each, and asserts that against the file, so the file follows the source and cannot drift silently. A code only one bridge emits is legitimate and carries a note saying why, which a test enforces. A corpus of synthetic sources, one per emission shape, proves the derivation sees each shape, and a mutation harness perturbs a copy of the contract and requires each assertion to redden, so a guard that stops checking fails rather than passing quietly. It has already earned this three times while the branch was open, catching two codes and one reworded sentence that arrived on main from other work.
Contributor
Python Preview PackagesVersion
Install with uvAdd the TestPyPI index to your [[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
explicit = trueThen install the packages you need: # Core SDK
uv add 'ag-ui-protocol==0.0.0.dev1788169517' --index testpypi
# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1788169517' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1788169517' --index testpypi
# NOTE: ag-ui-agent-spec depends on pyagentspec (git-only, not on PyPI).
# You will need to install pyagentspec separately from its git repo.
uv add 'ag-ui-agent-spec==0.0.0.dev1788169517' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1788169517' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1788169517' --index testpypiInstall with pippip install \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
ag-ui-protocol==0.0.0.dev1788169517
Commit: 1b1418d |
@ag-ui/a2a-middleware
@ag-ui/a2ui-middleware
@ag-ui/event-throttle-middleware
@ag-ui/mcp-apps-middleware
@ag-ui/mcp-middleware
@ag-ui/a2a
@ag-ui/adk
@ag-ui/ag2
@ag-ui/agno
@ag-ui/aws-strands
@ag-ui/claude-agent-sdk
@ag-ui/claude-managed-agents
@ag-ui/crewai
@ag-ui/langchain
@ag-ui/langgraph
@ag-ui/llamaindex
@ag-ui/mastra
@ag-ui/pydantic-ai
@ag-ui/vercel-ai-sdk
@ag-ui/watsonx
@ag-ui/a2ui-toolkit
create-ag-ui-app
@ag-ui/client
@ag-ui/core
@ag-ui/encoder
@ag-ui/proto
commit: |
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 this is about
The two AWS Strands bridges put terminal
RUN_ERRORframes on the wire carrying a code and a message. Clients and mock harnesses match both literally, and the two sides had drifted, so the drift broke consumers rather than merely looking untidy.This resolves the two behaviour differences, makes shared codes carry identical text, and adds the thing that stops it recurring.
The two behaviour differences, both resolved toward failing loud
Every attachment fails to convert and no text remains. TypeScript aborted the run; Python logged a warning and carried on with an empty prompt. Python now emits
MEDIA_RESOLUTION_FAILEDand ends the run, with the message TypeScript already used.One honest narrowing: the degrade path below that gate, where text does survive, behaves as it always did, and under the default configuration the replay path supplies history so the recovered text reaches the model that way rather than through the fallback assignment. What this change adds is the gate, not the degrade.
Telling our bug from someone else's. TypeScript distinguished a defect in this adapter from a failure elsewhere; Python labelled everything an SDK failure. Python now makes the same split, the rule lives in one function per language instead of at four catch sites, and a fault arriving from inside the Strands call is marked foreign before it reaches the classifier. An integrator's unserializable tool result and a client's malformed state are no longer reported as this adapter's bug. Wrapping a fault is total on both sides, so wrapping can never itself raise the type it exists to rule out.
Runtime strings changed deliberately
Every one of these is a string another system could match, so each was checked across the repository first. No test, fixture, harness, doc or dojo page matched any of the old texts. The only occurrences were the source lines themselves.
PARTIAL_RESUMEandINVALID_PAYLOADrendered a Python list repr on one side and a JSON array on the other. Both now render one sorted comma-joined list.UNKNOWN_INTERRUPT_IDused a singular on one side and a plural on the other. One spelling now.THREAD_BUSYshared an opening and a tail through a scope helper on each side. The parked-orchestrator refusal, which only Python can produce, became its own sentence: no run is in flight then, so telling the caller to wait forRUN_FINISHEDwas false, and answering the interrupt is the way out."None"instead of the shared fallback.Two wire changes beyond the codes, both for parity
STRANDS_FORCE_STOPafter the message and tool-call closeout, which is what Python already did; one branch previously reportedSTRANDS_ERRORand skipped the closeout, leaving an unclosed message envelope ahead of the error.The guard
error-codes.jsonsits besideARCHITECTURE.mdand lists every code each bridge can emit with the message template it owes. It is deliberately not a hand-maintained checklist, which the parent ticket warns against: each side derives the list from its own source and asserts the derivation against the file, so the file follows the source and cannot drift silently. A code only one bridge emits is legitimate and carries a note saying why, and a test enforces that the note exists.Two things back it up. A corpus of small synthetic sources, one per shape in which an error can be emitted, proves the derivation actually sees each shape. And a mutation harness perturbs a copy of the contract and requires each assertion to redden, so a guard that quietly stops checking fails instead of passing.
It earned this three times while the branch was open, catching drift from other people's merges: two new codes, and one shared sentence reworded upstream. Each time the suite went red naming exactly what moved.
Known limits, stated rather than papered over
THREAD_AGENT_CONFIG_ERRORandTHREAD_AGENT_KWARGS_ERROR. Both are listed as they are spelled, each noting the other. Unifying them is for whoever owns the per-thread agent provider; renaming another chip's just-landed code here would collide with them.Not regressed
The interrupt protocol's four typed error paths are untouched and on the list with their reasons, along with the codes behind durable frontend-result recovery. Nothing was renamed or deleted to buy symmetry.
Verification
Python 1049 passing, TypeScript 1123 across 66 files, typecheck clean. The red-green demonstrations for adding, removing and rewording a code were run rather than asserted, and the mutation harness re-proves that criterion on every run.