openhands-agent-server 1.8.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. openhands/agent_server/__init__.py +0 -0
  2. openhands/agent_server/__main__.py +118 -0
  3. openhands/agent_server/api.py +331 -0
  4. openhands/agent_server/bash_router.py +105 -0
  5. openhands/agent_server/bash_service.py +379 -0
  6. openhands/agent_server/config.py +187 -0
  7. openhands/agent_server/conversation_router.py +321 -0
  8. openhands/agent_server/conversation_service.py +692 -0
  9. openhands/agent_server/dependencies.py +72 -0
  10. openhands/agent_server/desktop_router.py +47 -0
  11. openhands/agent_server/desktop_service.py +212 -0
  12. openhands/agent_server/docker/Dockerfile +244 -0
  13. openhands/agent_server/docker/build.py +825 -0
  14. openhands/agent_server/docker/wallpaper.svg +22 -0
  15. openhands/agent_server/env_parser.py +460 -0
  16. openhands/agent_server/event_router.py +204 -0
  17. openhands/agent_server/event_service.py +648 -0
  18. openhands/agent_server/file_router.py +121 -0
  19. openhands/agent_server/git_router.py +34 -0
  20. openhands/agent_server/logging_config.py +56 -0
  21. openhands/agent_server/middleware.py +32 -0
  22. openhands/agent_server/models.py +307 -0
  23. openhands/agent_server/openapi.py +21 -0
  24. openhands/agent_server/pub_sub.py +80 -0
  25. openhands/agent_server/py.typed +0 -0
  26. openhands/agent_server/server_details_router.py +43 -0
  27. openhands/agent_server/sockets.py +173 -0
  28. openhands/agent_server/tool_preload_service.py +76 -0
  29. openhands/agent_server/tool_router.py +22 -0
  30. openhands/agent_server/utils.py +63 -0
  31. openhands/agent_server/vscode_extensions/openhands-settings/extension.js +22 -0
  32. openhands/agent_server/vscode_extensions/openhands-settings/package.json +12 -0
  33. openhands/agent_server/vscode_router.py +70 -0
  34. openhands/agent_server/vscode_service.py +232 -0
  35. openhands_agent_server-1.8.2.dist-info/METADATA +15 -0
  36. openhands_agent_server-1.8.2.dist-info/RECORD +39 -0
  37. openhands_agent_server-1.8.2.dist-info/WHEEL +5 -0
  38. openhands_agent_server-1.8.2.dist-info/entry_points.txt +2 -0
  39. openhands_agent_server-1.8.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,204 @@
1
+ """
2
+ Local Event router for OpenHands SDK.
3
+ """
4
+
5
+ import logging
6
+ from datetime import datetime
7
+ from typing import Annotated
8
+
9
+ from fastapi import (
10
+ APIRouter,
11
+ Depends,
12
+ HTTPException,
13
+ Query,
14
+ status,
15
+ )
16
+
17
+ from openhands.agent_server.dependencies import get_event_service
18
+ from openhands.agent_server.event_service import EventService
19
+ from openhands.agent_server.models import (
20
+ ConfirmationResponseRequest,
21
+ EventPage,
22
+ EventSortOrder,
23
+ SendMessageRequest,
24
+ Success,
25
+ )
26
+ from openhands.sdk import Message
27
+ from openhands.sdk.event import Event
28
+
29
+
30
+ event_router = APIRouter(
31
+ prefix="/conversations/{conversation_id}/events", tags=["Events"]
32
+ )
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ # Read methods
37
+
38
+
39
+ def normalize_datetime_to_server_timezone(dt: datetime) -> datetime:
40
+ """
41
+ Normalize datetime to server timezone for consistent comparison.
42
+
43
+ If the datetime has timezone info, convert to server native timezone.
44
+ If it's naive (no timezone), assume it's already in server timezone.
45
+
46
+ Args:
47
+ dt: Input datetime (may be timezone-aware or naive)
48
+
49
+ Returns:
50
+ Datetime in server native timezone (timezone-aware)
51
+ """
52
+ if dt.tzinfo is not None:
53
+ # Timezone-aware: convert to server native timezone
54
+ return dt.astimezone(None)
55
+ else:
56
+ # Naive datetime: assume it's already in server timezone
57
+ return dt
58
+
59
+
60
+ @event_router.get("/search", responses={404: {"description": "Conversation not found"}})
61
+ async def search_conversation_events(
62
+ page_id: Annotated[
63
+ str | None,
64
+ Query(title="Optional next_page_id from the previously returned page"),
65
+ ] = None,
66
+ limit: Annotated[
67
+ int,
68
+ Query(title="The max number of results in the page", gt=0, lte=100),
69
+ ] = 100,
70
+ kind: Annotated[
71
+ str | None,
72
+ Query(
73
+ title="Optional filter by event kind/type (e.g., ActionEvent, MessageEvent)"
74
+ ),
75
+ ] = 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,
84
+ sort_order: Annotated[
85
+ EventSortOrder,
86
+ Query(title="Sort order for events"),
87
+ ] = EventSortOrder.TIMESTAMP,
88
+ timestamp__gte: Annotated[
89
+ datetime | None,
90
+ Query(title="Filter: event timestamp >= this datetime"),
91
+ ] = None,
92
+ timestamp__lt: Annotated[
93
+ datetime | None,
94
+ Query(title="Filter: event timestamp < this datetime"),
95
+ ] = None,
96
+ event_service: EventService = Depends(get_event_service),
97
+ ) -> EventPage:
98
+ """Search / List local events"""
99
+ assert limit > 0
100
+ assert limit <= 100
101
+
102
+ # Normalize timezone-aware datetimes to server timezone
103
+ normalized_gte = (
104
+ normalize_datetime_to_server_timezone(timestamp__gte)
105
+ if timestamp__gte
106
+ else None
107
+ )
108
+ normalized_lt = (
109
+ normalize_datetime_to_server_timezone(timestamp__lt) if timestamp__lt else None
110
+ )
111
+
112
+ return await event_service.search_events(
113
+ page_id, limit, kind, source, body, sort_order, normalized_gte, normalized_lt
114
+ )
115
+
116
+
117
+ @event_router.get("/count", responses={404: {"description": "Conversation not found"}})
118
+ async def count_conversation_events(
119
+ kind: Annotated[
120
+ str | None,
121
+ Query(
122
+ title="Optional filter by event kind/type (e.g., ActionEvent, MessageEvent)"
123
+ ),
124
+ ] = 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,
133
+ timestamp__gte: Annotated[
134
+ datetime | None,
135
+ Query(title="Filter: event timestamp >= this datetime"),
136
+ ] = None,
137
+ timestamp__lt: Annotated[
138
+ datetime | None,
139
+ Query(title="Filter: event timestamp < this datetime"),
140
+ ] = None,
141
+ event_service: EventService = Depends(get_event_service),
142
+ ) -> int:
143
+ """Count local events matching the given filters"""
144
+ # Normalize timezone-aware datetimes to server timezone
145
+ normalized_gte = (
146
+ normalize_datetime_to_server_timezone(timestamp__gte)
147
+ if timestamp__gte
148
+ else None
149
+ )
150
+ normalized_lt = (
151
+ normalize_datetime_to_server_timezone(timestamp__lt) if timestamp__lt else None
152
+ )
153
+
154
+ count = await event_service.count_events(
155
+ kind, source, body, normalized_gte, normalized_lt
156
+ )
157
+
158
+ return count
159
+
160
+
161
+ @event_router.get("/{event_id}", responses={404: {"description": "Item not found"}})
162
+ async def get_conversation_event(
163
+ event_id: str,
164
+ event_service: EventService = Depends(get_event_service),
165
+ ) -> Event:
166
+ """Get a local event given an id"""
167
+ event = await event_service.get_event(event_id)
168
+ if event is None:
169
+ raise HTTPException(status.HTTP_404_NOT_FOUND)
170
+ return event
171
+
172
+
173
+ @event_router.get("")
174
+ async def batch_get_conversation_events(
175
+ event_ids: list[str],
176
+ event_service: EventService = Depends(get_event_service),
177
+ ) -> list[Event | None]:
178
+ """Get a batch of local events given their ids, returning null for any
179
+ missing item."""
180
+ events = await event_service.batch_get_events(event_ids)
181
+ return events
182
+
183
+
184
+ @event_router.post("")
185
+ async def send_message(
186
+ request: SendMessageRequest,
187
+ event_service: EventService = Depends(get_event_service),
188
+ ) -> Success:
189
+ """Send a message to a conversation"""
190
+ message = Message(role=request.role, content=request.content)
191
+ await event_service.send_message(message, request.run)
192
+ return Success()
193
+
194
+
195
+ @event_router.post(
196
+ "/respond_to_confirmation", responses={404: {"description": "Item not found"}}
197
+ )
198
+ async def respond_to_confirmation(
199
+ request: ConfirmationResponseRequest,
200
+ event_service: EventService = Depends(get_event_service),
201
+ ) -> Success:
202
+ """Accept or reject a pending action in confirmation mode."""
203
+ await event_service.respond_to_confirmation(request)
204
+ return Success()