fix(gateway): bound run event type filters (#7370)

* fix(gateway): bound run event type filters

* test(gateway): cover run event filter cardinality

* style(gateway): group run event filter limit with module constants
This commit is contained in:
NanPan 2026-10-24 16:11:57 +08:00 committed by GitHub
parent 9a11f9aec7
commit 7cfd3c57d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 67 additions and 1 deletions

View File

@ -90,6 +90,7 @@ _artifact_archive_slots = asyncio.Semaphore(4)
_MISSING_REGENERATE_BASE_DETAIL = "Could not find an addressable checkpoint before the target user message"
_UNSAFE_REGENERATE_LINEAGE_DETAIL = "Could not safely resolve the checkpoint before the target user message"
THREAD_MESSAGE_LEGACY_SCAN_BATCH = 201
MAX_EVENT_TYPE_FILTERS = 100
_LEGACY_IDEMPOTENCY_REQUEST_KEY = "idempotency_request"
@ -1850,7 +1851,15 @@ async def list_run_events(
await _require_run_visible_to_scope(run_id, thread_id, request)
user_id = await _run_scope_user_id(request, thread_id)
event_store = get_run_event_store(request)
types = event_types.split(",") if event_types else None
if event_types:
types = event_types.split(",")
if len(types) > MAX_EVENT_TYPE_FILTERS:
raise HTTPException(
status_code=422,
detail=f"event_types accepts at most {MAX_EVENT_TYPE_FILTERS} values",
)
else:
types = None
events = await event_store.list_events(
thread_id,
run_id,

View File

@ -196,3 +196,60 @@ async def test_effective_memory_flows_from_injection_to_the_existing_debug_api()
"project_context_revision": None,
"project_shelf_revision": None,
}
@pytest.mark.parametrize("count", [100, 101])
@pytest.mark.anyio
async def test_list_run_events_bounds_event_type_filter_cardinality(count: int) -> None:
from fastapi import HTTPException
from app.gateway.routers.thread_runs import MAX_EVENT_TYPE_FILTERS, list_run_events
calls: dict = {}
class FakeStore:
async def list_events(
self,
thread_id,
run_id,
*,
event_types=None,
task_id=None,
limit=500,
after_seq=None,
user_id=None,
):
calls["event_types"] = event_types
return []
class FakeState:
run_event_store = FakeStore()
class FakeApp:
state = FakeState()
class FakeRequest:
app = FakeApp()
_deerflow_test_bypass_auth = True
cookies: dict = {}
event_types = ",".join(f"type-{i}" for i in range(count))
kwargs = {
"thread_id": "t1",
"run_id": "r1",
"request": FakeRequest(),
"event_types": event_types,
"task_id": None,
"limit": 500,
"after_seq": None,
}
if count > MAX_EVENT_TYPE_FILTERS:
with pytest.raises(HTTPException) as exc_info:
await list_run_events(**kwargs)
assert exc_info.value.status_code == 422
assert str(MAX_EVENT_TYPE_FILTERS) in exc_info.value.detail
assert calls == {}
else:
assert await list_run_events(**kwargs) == []
assert len(calls["event_types"]) == MAX_EVENT_TYPE_FILTERS