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.
- openhands/agent_server/__init__.py +0 -0
- openhands/agent_server/__main__.py +118 -0
- openhands/agent_server/api.py +331 -0
- openhands/agent_server/bash_router.py +105 -0
- openhands/agent_server/bash_service.py +379 -0
- openhands/agent_server/config.py +187 -0
- openhands/agent_server/conversation_router.py +321 -0
- openhands/agent_server/conversation_service.py +692 -0
- openhands/agent_server/dependencies.py +72 -0
- openhands/agent_server/desktop_router.py +47 -0
- openhands/agent_server/desktop_service.py +212 -0
- openhands/agent_server/docker/Dockerfile +244 -0
- openhands/agent_server/docker/build.py +825 -0
- openhands/agent_server/docker/wallpaper.svg +22 -0
- openhands/agent_server/env_parser.py +460 -0
- openhands/agent_server/event_router.py +204 -0
- openhands/agent_server/event_service.py +648 -0
- openhands/agent_server/file_router.py +121 -0
- openhands/agent_server/git_router.py +34 -0
- openhands/agent_server/logging_config.py +56 -0
- openhands/agent_server/middleware.py +32 -0
- openhands/agent_server/models.py +307 -0
- openhands/agent_server/openapi.py +21 -0
- openhands/agent_server/pub_sub.py +80 -0
- openhands/agent_server/py.typed +0 -0
- openhands/agent_server/server_details_router.py +43 -0
- openhands/agent_server/sockets.py +173 -0
- openhands/agent_server/tool_preload_service.py +76 -0
- openhands/agent_server/tool_router.py +22 -0
- openhands/agent_server/utils.py +63 -0
- openhands/agent_server/vscode_extensions/openhands-settings/extension.js +22 -0
- openhands/agent_server/vscode_extensions/openhands-settings/package.json +12 -0
- openhands/agent_server/vscode_router.py +70 -0
- openhands/agent_server/vscode_service.py +232 -0
- openhands_agent_server-1.8.2.dist-info/METADATA +15 -0
- openhands_agent_server-1.8.2.dist-info/RECORD +39 -0
- openhands_agent_server-1.8.2.dist-info/WHEEL +5 -0
- openhands_agent_server-1.8.2.dist-info/entry_points.txt +2 -0
- 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()
|