Skip to content

Commit a472380

Browse files
committed
Merge branch 'feat/pr-mcp-upstream' of https://github.com/oraichain/OpenHands into feat/pr-mcp-upstream
2 parents 2ccb24b + 908bb0c commit a472380

File tree

5 files changed

+317
-44
lines changed

5 files changed

+317
-44
lines changed

openhands/core/setup.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,11 @@ def initialize_repository_for_runtime(
119119
if selected_repository and provider_tokens:
120120
logger.debug(f'Selected repository {selected_repository}.')
121121
repo_directory = call_async_from_sync(
122-
runtime.clone_repo, GENERAL_TIMEOUT, github_token, selected_repository, None
122+
runtime.clone_repo,
123+
GENERAL_TIMEOUT,
124+
provider_tokens,
125+
selected_repository,
126+
None,
123127
)
124128
# Run setup script if it exists
125129
runtime.maybe_run_setup_script()

openhands/events/event_store.py

+68-20
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,37 @@
77
from openhands.events.serialization.event import event_from_dict, event_to_dict
88
from openhands.storage.files import FileStore
99
from openhands.storage.locations import (
10+
get_conversation_dir,
1011
get_conversation_event_filename,
1112
get_conversation_events_dir,
1213
)
1314
from openhands.utils.shutdown_listener import should_continue
1415

1516

17+
@dataclass(frozen=True)
18+
class _CachePage:
19+
events: list[dict] | None
20+
start: int
21+
end: int
22+
23+
def covers(self, global_index: int) -> bool:
24+
if global_index < self.start:
25+
return False
26+
if global_index >= self.end:
27+
return False
28+
return True
29+
30+
def get_event(self, global_index: int) -> Event | None:
31+
# If there was not actually a cached page, return None
32+
if not self.events:
33+
return None
34+
local_index = global_index - self.start
35+
return event_from_dict(self.events[local_index])
36+
37+
38+
_DUMMY_PAGE = _CachePage(None, 1, -1)
39+
40+
1641
@dataclass
1742
class EventStore:
1843
"""
@@ -23,6 +48,7 @@ class EventStore:
2348
file_store: FileStore
2449
user_id: str | None
2550
cur_id: int = -1 # We fix this in post init if it is not specified
51+
cache_size: int = 25
2652

2753
def __post_init__(self) -> None:
2854
if self.cur_id >= 0:
@@ -83,30 +109,33 @@ def should_filter(event: Event) -> bool:
83109
return True
84110
return False
85111

112+
if end_id is None:
113+
end_id = self.cur_id
114+
else:
115+
end_id += 1 # From inclusive to exclusive
116+
86117
if reverse:
87-
if end_id is None:
88-
end_id = self.cur_id - 1
89-
event_id = end_id
90-
while event_id >= start_id:
91-
try:
92-
event = self.get_event(event_id)
93-
if not should_filter(event):
94-
yield event
95-
except FileNotFoundError:
96-
logger.debug(f'No event found for ID {event_id}')
97-
event_id -= 1
118+
step = -1
119+
start_id, end_id = end_id, start_id
120+
start_id -= 1
121+
end_id -= 1
98122
else:
99-
event_id = start_id
100-
while should_continue():
101-
if end_id is not None and event_id > end_id:
102-
break
123+
step = 1
124+
125+
cache_page = _DUMMY_PAGE
126+
for index in range(start_id, end_id, step):
127+
if not should_continue():
128+
return
129+
if not cache_page.covers(index):
130+
cache_page = self._load_cache_page_for_index(index)
131+
event = cache_page.get_event(index)
132+
if event is None:
103133
try:
104-
event = self.get_event(event_id)
105-
if not should_filter(event):
106-
yield event
134+
event = self.get_event(index)
107135
except FileNotFoundError:
108-
break
109-
event_id += 1
136+
event = None
137+
if event and not should_filter(event):
138+
yield event
110139

111140
def get_event(self, id: int) -> Event:
112141
filename = self._get_filename_for_id(id, self.user_id)
@@ -230,6 +259,25 @@ def get_matching_events(
230259
def _get_filename_for_id(self, id: int, user_id: str | None) -> str:
231260
return get_conversation_event_filename(self.sid, id, user_id)
232261

262+
def _get_filename_for_cache(self, start: int, end: int) -> str:
263+
return f'{get_conversation_dir(self.sid, self.user_id)}event_cache/{start}-{end}.json'
264+
265+
def _load_cache_page(self, start: int, end: int) -> _CachePage:
266+
"""Read a page from the cache. Reading individual events is slow when there are a lot of them, so we use pages."""
267+
cache_filename = self._get_filename_for_cache(start, end)
268+
try:
269+
content = self.file_store.read(cache_filename)
270+
events = json.loads(content)
271+
except FileNotFoundError:
272+
events = None
273+
page = _CachePage(events, start, end)
274+
return page
275+
276+
def _load_cache_page_for_index(self, index: int) -> _CachePage:
277+
offset = index % self.cache_size
278+
index -= offset
279+
return self._load_cache_page(index, index + self.cache_size)
280+
233281
@staticmethod
234282
def _get_id_from_filename(filename: str) -> int:
235283
try:

openhands/events/stream.py

+16
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class EventStream(EventStore):
5252
_queue_loop: asyncio.AbstractEventLoop | None
5353
_thread_pools: dict[str, dict[str, ThreadPoolExecutor]]
5454
_thread_loops: dict[str, dict[str, asyncio.AbstractEventLoop]]
55+
_write_page_cache: list[dict]
5556

5657
def __init__(self, sid: str, file_store: FileStore, user_id: str | None = None):
5758
super().__init__(sid, file_store, user_id)
@@ -66,6 +67,7 @@ def __init__(self, sid: str, file_store: FileStore, user_id: str | None = None):
6667
self._subscribers = {}
6768
self._lock = threading.Lock()
6869
self.secrets = {}
70+
self._write_page_cache = []
6971

7072
def _init_thread_loop(self, subscriber_id: str, callback_id: str) -> None:
7173
loop = asyncio.new_event_loop()
@@ -172,8 +174,22 @@ def add_event(self, event: Event, source: EventSource) -> None:
172174
self.file_store.write(
173175
self._get_filename_for_id(event.id, self.user_id), json.dumps(data)
174176
)
177+
self._write_page_cache.append(data)
178+
self._store_cache_page()
175179
self._queue.put(event)
176180

181+
def _store_cache_page(self):
182+
"""Store a page in the cache. Reading individual events is slow when there are a lot of them, so we use pages."""
183+
current_write_page = self._write_page_cache
184+
if len(current_write_page) < self.cache_size:
185+
return
186+
self._write_page_cache = []
187+
start = current_write_page[0]['id']
188+
end = start + self.cache_size
189+
contents = json.dumps(current_write_page)
190+
cache_filename = self._get_filename_for_cache(start, end)
191+
self.file_store.write(cache_filename, contents)
192+
177193
def set_secrets(self, secrets: dict[str, str]) -> None:
178194
self.secrets = secrets.copy()
179195

poetry.lock

+21-21
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)