Skip to content

a2a_http_client

A2A HTTP Client with WASM default and native testing support

Target Adaptive: WASM (Spin SDK) + Native (Reqwest) with identical APIs ✅ Complete Feature Parity: All A2A protocol methods work identically ✅ Testing Excellence: Native async runtime for comprehensive testing ✅ Clean Interface: Same call() method across all targets ✅ Activation Aware: Retry with backoff for KEDA scale-to-zero cold starts

Configuration for activation-aware retries when calling potentially cold agents.

These values drive the SDK-side retry budget. The KEDA HTTP Add-on HTTPScaledObject does NOT have a CRD-level queueDepth — this is the platform’s enforcement layer.

Fields

FieldTypeDescription
max_cold_start_timeoutDurationMax time to wait for a cold agent to respond. Default: 5s.
initial_backoffDurationInitial retry delay. Default: 100ms.
max_backoffDurationMax retry delay cap. Default: 2s.
max_retriesu32Max retries before giving up. Default: 3.
jitterboolAdd jitter to backoff. Default: true.

Methods

fn backoff_for_attempt(&self, attempt: u32) -> Duration

Compute the backoff duration for a given attempt (0-indexed).

Uses exponential backoff: initial * 2^attempt, capped at max_backoff. When jitter is enabled, the result is multiplied by a factor in [0.5, 1.0].

fn is_retriable_status(status: u16) -> bool

Check if an HTTP status code indicates a retriable cold-start scenario.

fn is_retriable_error(error_msg: &str) -> bool

Check if an error message indicates a retriable connection failure.

External A2A Client - Native implementation using Reqwest

Built streaming-first: the internal reqwest::Client has only a connect_timeout — no total request timeout. RPC (non-streaming) calls add a per-request .timeout(). Streaming calls use first_byte_ms + IdleTimeoutStream instead of wall-clock limits.

Methods

fn external<impl Into<String>>(url: impl Into) -> Self

Create client for external agent with default streaming policy.

fn external_with_policy<impl Into<String>>(url: impl Into, policy: StreamingPolicy) -> Self

Create client for external agent with explicit streaming policy.

fn with_header(self, key: String, value: String) -> Self

Add custom header

fn url(&self) -> &str

Get URL (for testing)

fn has_header(&self, key: &str) -> bool

Check if header exists (for testing)

async fn call(&self, method: &str, params: Value) -> Result<Value, RpcError>

UNIVERSAL CALL - Same interface as core Client::call()

async fn ping(&self) -> Result<Value, RpcError>
async fn get_agent_card(&self) -> Result<Value, RpcError>
async fn metadata(&self) -> Result<Value, RpcError>

Backward-compat alias for get_agent_card.

async fn run(&self, input: Value) -> Result<Value, RpcError>
async fn message_send(&self, message: Message, metadata: Option<HashMap<String, Value>>) -> Result<Value, RpcError>

SendMessage — Send message using A2A v1.0 protocol.

async fn task_get(&self, task_id: String) -> Result<Task, RpcError>

GetTask — Retrieve task state and artifacts.

async fn task_cancel(&self, task_id: String) -> Result<Task, RpcError>

CancelTask — Cancel an ongoing task.

async fn task_list(&self, context_id: Option<String>, status: Option<String>, page_size: Option<u32>, page_token: Option<String>) -> Result<Value, RpcError>

ListTasks — List agent tasks with filtering.

async fn get_extended_agent_card(&self, auth_token: Option<String>, scope: Option<Vec<String>>, metadata: Option<HashMap<String, Value>>) -> Result<Value, RpcError>

GetExtendedAgentCard — Get extended agent information.

async fn agent_card(&self) -> Result<String, ClientError>

AGENT CARD: Get agent discovery info (HTTP GET, not JSON-RPC)

async fn call_with_activation(&self, method: &str, params: serde_json::Value, config: &ActivationConfig, request_id: &str) -> Result<serde_json::Value, RpcError>

Call an A2A method with activation-aware retries for cold-start tolerance.

When KEDA scales an agent to 0 replicas, the KEDA interceptor buffers the request and triggers scale-up. If the interceptor returns a retriable error (503, 502, 504, connection refused), this method retries with exponential backoff per the provided [ActivationConfig].

Non-retriable errors (4xx, 500) are returned immediately without retry.

An idempotency key is generated per attempt using request_id for safe retries on state-mutating methods like message/send. The key is available for future HTTP header injection (X-Idempotency-Key).

RPC Error type - Compatible with core client interface

Fields

FieldTypeDescription
codei32
messageString

Methods

fn internal_error(message: &str) -> Self

Client errors

Variants

VariantDescription
Network(String)
Serialization(String)
JsonRpc(String)
Validation(String)
async fn activation_delay(duration: Duration)

Async delay for activation retries. Uses tokio::time::sleep on native and std::thread::sleep (WASI monotonic clock) on WASM.

fn idempotency_key(request_id: &str, attempt: u32) -> String

Generate an idempotency key for safe retries on state-mutating requests.

Format: {request_id}:{attempt} — the server should deduplicate by this key.

async fn retry_with_activation<T, E, F, Fut>(config: &ActivationConfig, call_fn: F) -> Result<T, E>
where
E: Display,
F: FnMut,
Fut: Future

Execute an async operation with activation-aware retries.

call_fn receives the 0-indexed attempt number and returns Result<T, E>. Errors whose Display output matches [ActivationConfig::is_retriable_error] are retried with exponential backoff; all other errors are returned immediately.

Returns the first Ok, or the last Err when retries are exhausted.

async fn check_connectivity(url: &str) -> bool

CONVENIENCE: Check if target is reachable