feat(filter): add A2A classifier following MCP routing pattern#368
feat(filter): add A2A classifier following MCP routing pattern#368nerdalert wants to merge 1 commit into
Conversation
PR too largeThis PR adds 2679 lines (limit: 500). Large PRs are difficult to review and more likely to introduce subtle bugs. Please break this contribution into smaller, focused PRs that each address a single concern. See our coding conventions for guidance.
|
There was a problem hiding this comment.
I like how isolated to its module all the A2A stuff is, much like MCP.
I really like how more of the PR is tests and examples than code.
This makes it very easy for us to merge early, and iterate on it since it's well-isolated. Thank you @nerdalert 🖖
|
Converted to draft: required checks failing. |
949a70f to
b1144ab
Compare
b1144ab to
02e6b76
Compare
| "ListTaskPushNotificationConfigs" => Self::ListTaskPushNotificationConfigs, | ||
| "DeleteTaskPushNotificationConfig" => Self::DeleteTaskPushNotificationConfig, | ||
| "GetExtendedAgentCard" => Self::GetExtendedAgentCard, | ||
| other => Self::Unknown(other.to_owned()), |
There was a problem hiding this comment.
Does the protocol talk about case-sensitivity in methods? Right now "sendmessage" would fall through to unknown. 🤔
There was a problem hiding this comment.
A2A v1.0 method names are exact JSON-RPC method strings in PascalCase, so I’m treating classification as case-sensitive. Legacy v0.3 slash-delimited names are accepted only through explicit method_aliases. That means sendmessage intentionally falls through to unknown instead of being silently normalized into SendMessage.
Change:
- Added docs in A2aMethod::from_method_str() explaining that A2A method strings are matched exactly.
- Added a unit test confirming lowercase sendmessage classifies A2aMethod::Unknown("sendmessage").
| } | ||
|
|
||
| // ----------------------------------------------------------------------------- | ||
| // Helpers |
There was a problem hiding this comment.
| // Helpers | |
| // Test Utilities |
There was a problem hiding this comment.
Maybe leave this as // Helpers? The file already has a separate // Test Utilities section, and this section contains production helpers like build_json_rpc_config, handle_parse_error, and metadata/header promotion functions.
WDYT?
| async fn batch_rejected_even_with_on_invalid_continue() { | ||
| let filter = make_filter(r#"{"on_invalid": "continue"}"#); | ||
| let body_str = r#"[{"jsonrpc":"2.0","id":1,"method":"SendMessage"}]"#; | ||
| let req = make_a2a_request(&[]); | ||
| let mut ctx = crate::test_utils::make_filter_context(&req); | ||
| let mut body = Some(Bytes::from(body_str)); | ||
|
|
||
| let action = filter.on_request_body(&mut ctx, &mut body, true).await.unwrap(); | ||
|
|
||
| assert!( | ||
| matches!(action, FilterAction::Reject(_)), | ||
| "batch should be rejected regardless of on_invalid" | ||
| ); | ||
| } |
There was a problem hiding this comment.
Do we document anywhere why this is the behavior? 🤔
There was a problem hiding this comment.
Documented. A2A classification produces one static routing decision per HTTP request; JSON-RPC batches can mix methods, task IDs, and streaming semantics, so they are rejected even when invalid non-A2A input would otherwise continue.
Adds the `a2a` HTTP payload-processing filter for static A2A JSON-RPC classification and routing. This follows the same classifier pattern as the existing MCP filter: reuse the shared JSON-RPC parser, inspect the buffered request body at EOS, extract protocol-specific metadata, promote bounded internal routing headers/filter results/durable metadata, then release to `router`/`load_balancer` for static routing. Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
02e6b76 to
af27f40
Compare
Summary
Adds the
a2aHTTP payload-processing filter for static A2A JSON-RPC classification and routing. This gets parity with the existing MCP classifier functionality merged in #195This follows the same classifier pattern as the existing MCP filter: reuse the shared JSON-RPC parser, inspect the buffered request body at EOS, extract protocol-specific metadata, promote bounded internal routing headers/filter results/durable metadata, then release to
router/load_balancerfor static routing.This implements issue #346 by:
SendMessage,SendStreamingMessage, andGetTaskmessage/sendparams.idandparams.taskIdx-praxis-a2a-*routing headers, filter results, and durable metadataThis is classifier/static routing support only. Stateful task routing, response parsing, and SSE task-event parsing remain out of scope for the later A2A task-routing work.
This does not include, focused on parity with the MCP classifier and its already overly large but no new patterns here:
Tests
cargo +nightly fmt --checkcargo test -p praxis-proxy-filter a2acargo test -p praxis-tests-integration --test suite -- a2acargo test -p praxis-tests-schema --test suite -- all_example_configs_parsecargo clippy --workspace --all-targetsRefs #346