Skip to content

Commit c196a24

Browse files
Add source parameter to /search endpoint for event filtering (#1224)
Co-authored-by: openhands <openhands@all-hands.dev>
1 parent 6525d8d commit c196a24

File tree

4 files changed

+496
-21
lines changed

4 files changed

+496
-21
lines changed

openhands-agent-server/openhands/agent_server/event_router.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ async def search_conversation_events(
7373
title="Optional filter by event kind/type (e.g., ActionEvent, MessageEvent)"
7474
),
7575
] = None,
76+
source: Annotated[
77+
str | None,
78+
Query(title="Optional filter by event source (e.g., agent, user, environment)"),
79+
] = None,
80+
body: Annotated[
81+
str | None,
82+
Query(title="Optional filter by message content (case-insensitive)"),
83+
] = None,
7684
sort_order: Annotated[
7785
EventSortOrder,
7886
Query(title="Sort order for events"),
@@ -102,7 +110,7 @@ async def search_conversation_events(
102110
)
103111

104112
return await event_service.search_events(
105-
page_id, limit, kind, sort_order, normalized_gte, normalized_lt
113+
page_id, limit, kind, source, body, sort_order, normalized_gte, normalized_lt
106114
)
107115

108116

@@ -114,6 +122,14 @@ async def count_conversation_events(
114122
title="Optional filter by event kind/type (e.g., ActionEvent, MessageEvent)"
115123
),
116124
] = None,
125+
source: Annotated[
126+
str | None,
127+
Query(title="Optional filter by event source (e.g., agent, user, environment)"),
128+
] = None,
129+
body: Annotated[
130+
str | None,
131+
Query(title="Optional filter by message content (case-insensitive)"),
132+
] = None,
117133
timestamp__gte: Annotated[
118134
datetime | None,
119135
Query(title="Filter: event timestamp >= this datetime"),
@@ -135,7 +151,9 @@ async def count_conversation_events(
135151
normalize_datetime_to_server_timezone(timestamp__lt) if timestamp__lt else None
136152
)
137153

138-
count = await event_service.count_events(kind, normalized_gte, normalized_lt)
154+
count = await event_service.count_events(
155+
kind, source, body, normalized_gte, normalized_lt
156+
)
139157

140158
return count
141159

openhands-agent-server/openhands/agent_server/event_service.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ async def search_events(
8686
page_id: str | None = None,
8787
limit: int = 100,
8888
kind: str | None = None,
89+
source: str | None = None,
90+
body: str | None = None,
8991
sort_order: EventSortOrder = EventSortOrder.TIMESTAMP,
9092
timestamp__gte: datetime | None = None,
9193
timestamp__lt: datetime | None = None,
@@ -109,6 +111,15 @@ async def search_events(
109111
):
110112
continue
111113

114+
# Apply source filter if provided
115+
if source is not None and event.source != source:
116+
continue
117+
118+
# Apply body filter if provided (case-insensitive substring match)
119+
if body is not None:
120+
if not self._event_matches_body(event, body):
121+
continue
122+
112123
# Apply timestamp filters if provided (ISO string comparison)
113124
if (
114125
timestamp_gte_str is not None
@@ -152,6 +163,8 @@ async def search_events(
152163
async def count_events(
153164
self,
154165
kind: str | None = None,
166+
source: str | None = None,
167+
body: str | None = None,
155168
timestamp__gte: datetime | None = None,
156169
timestamp__lt: datetime | None = None,
157170
) -> int:
@@ -174,6 +187,15 @@ async def count_events(
174187
):
175188
continue
176189

190+
# Apply source filter if provided
191+
if source is not None and event.source != source:
192+
continue
193+
194+
# Apply body filter if provided (case-insensitive substring match)
195+
if body is not None:
196+
if not self._event_matches_body(event, body):
197+
continue
198+
177199
# Apply timestamp filters if provided (ISO string comparison)
178200
if (
179201
timestamp_gte_str is not None
@@ -187,6 +209,32 @@ async def count_events(
187209

188210
return count
189211

212+
def _event_matches_body(self, event: Event, body: str) -> bool:
213+
"""Check if event's message content matches body filter (case-insensitive)."""
214+
# Import here to avoid circular imports
215+
from openhands.sdk.event.llm_convertible.message import MessageEvent
216+
from openhands.sdk.llm.message import content_to_str
217+
218+
# Only check MessageEvent instances for body content
219+
if not isinstance(event, MessageEvent):
220+
return False
221+
222+
# Extract text content from the message
223+
text_parts = content_to_str(event.llm_message.content)
224+
225+
# Also check extended content if present
226+
if event.extended_content:
227+
extended_text_parts = content_to_str(event.extended_content)
228+
text_parts.extend(extended_text_parts)
229+
230+
# Also check reasoning content if present
231+
if event.reasoning_content:
232+
text_parts.append(event.reasoning_content)
233+
234+
# Combine all text content and perform case-insensitive substring match
235+
full_text = " ".join(text_parts).lower()
236+
return body.lower() in full_text
237+
190238
async def batch_get_events(self, event_ids: list[str]) -> list[Event | None]:
191239
"""Given a list of ids, get events (Or none for any which were not found)"""
192240
results = await asyncio.gather(

0 commit comments

Comments
 (0)