Skip to content

Commit 7efcf99

Browse files
scale-ballenclaude
andcommitted
fix: replace BaseHTTPMiddleware with pure ASGI middleware to fix streaming
BaseHTTPMiddleware.call_next() silently buffers or drops StreamingResponse bodies in several starlette versions. Since message_send_wrapper always returns an AsyncGenerator (wrapped in StreamingResponse), the Agentex server proxy received result=null for every message/send call through that path. Replace RequestIDMiddleware with a pure ASGI implementation that sets the request-ID context variable and passes scope/receive/send through unmodified, never touching the response body. This unblocks all message/send tutorial integration tests (010_multiturn, 020_streaming, 030_langgraph). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e26b11d commit 7efcf99

File tree

1 file changed

+19
-11
lines changed

1 file changed

+19
-11
lines changed

src/agentex/lib/sdk/fastacp/base/base_acp_server.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from fastapi import FastAPI, Request
1313
from pydantic import TypeAdapter, ValidationError
1414
from fastapi.responses import StreamingResponse
15-
from starlette.middleware.base import BaseHTTPMiddleware
15+
from starlette.types import ASGIApp, Receive, Scope, Send
1616

1717
from agentex.lib.types.acp import (
1818
RPC_SYNC_METHODS,
@@ -43,17 +43,25 @@
4343
task_message_update_adapter = TypeAdapter(TaskMessageUpdate)
4444

4545

46-
class RequestIDMiddleware(BaseHTTPMiddleware):
47-
"""Middleware to extract or generate request IDs and add them to logs and response headers"""
46+
class RequestIDMiddleware:
47+
"""Pure ASGI middleware to extract or generate request IDs and set them in the logging context.
4848
49-
async def dispatch(self, request: Request, call_next): # type: ignore[override]
50-
# Extract request ID from header or generate a new one if there isn't one
51-
request_id = request.headers.get("x-request-id") or uuid.uuid4().hex
52-
# Store request ID in request state for access in handlers
53-
ctx_var_request_id.set(request_id)
54-
# Process request
55-
response = await call_next(request)
56-
return response
49+
Implemented as a pure ASGI middleware (rather than BaseHTTPMiddleware) so that it never
50+
buffers the response body. BaseHTTPMiddleware's call_next() silently swallows
51+
StreamingResponse bodies in several starlette versions, which caused message/send handlers
52+
to return result=null through the Agentex server proxy.
53+
"""
54+
55+
def __init__(self, app: ASGIApp) -> None:
56+
self.app = app
57+
58+
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
59+
if scope["type"] == "http":
60+
headers = dict(scope.get("headers", []))
61+
raw_request_id = headers.get(b"x-request-id", b"")
62+
request_id = raw_request_id.decode() if raw_request_id else uuid.uuid4().hex
63+
ctx_var_request_id.set(request_id)
64+
await self.app(scope, receive, send)
5765

5866

5967
class BaseACPServer(FastAPI):

0 commit comments

Comments
 (0)