v3.3.7 — Extract Task Queue + Payloads; Unify Inbound/Outbound
v3.3.7 — Extract Task Queue + Payloads; Unify Inbound/Outbound Paths
Three architectural moves that complete the refactoring arc started in v3.3.3.
1. Extract task_queue.py (290 lines)
_PendingTask + TaskQueue decoupled from HTTP transport. The state machine for inbound tasks is now independent of the transport that receives them. server.py imports and re-exports for backward compatibility.
2. Extract payloads.py (121 lines)
A2A wire-format builders (_build_task_object, _build_status_update_payload, _build_paginated_task_list) as pure functions. No socket knowledge. No HTTP header knowledge. Just dict shapes.
3. Unify inbound and outbound paths
INBOUND — _prepare_and_enqueue() shared by all three send paths:
_handle_task_send(JSON-RPC)_rest_send_message(REST)_rest_send_message_stream(SSE streaming)
Security fix: The streaming endpoint previously bypassed _enqueue_and_await_task's idempotency and task-ID collision guards. Idempotent replay and task-ID collision attacks were possible through the SSE path that JSON-RPC and REST correctly blocked. _prepare_and_enqueue() closes this — all three paths now share one preamble.
OUTBOUND — _resolve_rpc_target() + _rpc_call() shared by:
handle_send_protocol_taskhandle_cancel_protocol_task_handle_call_mode3
_resolve_target (9 parameters, threw ValueError) → _resolve_rpc_target (1 parameter, returns structured error dicts). Callers got thinner. _rpc_call() consolidates the JSON-RPC POST + error-parse triad that was duplicated across three outbound paths.
Numbers
server.py: 2,044 → 1,490 (-554)
task_queue.py: 290 (new)
payloads.py: 121 (new)
─────────────────────────
Net: 8,344 → 8,147 (-197)
Tests: 625/625 ✅
Attribution
Refactoring by Emil. Britney gated and tested.