Skip to content

Commit

Permalink
Merge branch 'main' into fix/llm-mypy-errors
Browse files Browse the repository at this point in the history
  • Loading branch information
xingyaoww authored Feb 19, 2025
2 parents 18543a2 + df8bbc2 commit 5b8db98
Show file tree
Hide file tree
Showing 14 changed files with 101 additions and 68 deletions.
6 changes: 6 additions & 0 deletions openhands/memory/condenser/condenser.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ def __init__(self) -> None:

@override
def condensed_history(self, state: State) -> list[Event]:
# The history should grow monotonically -- if it doesn't, something has
# truncated the history and we need to reset our tracking.
if len(state.history) < self._last_history_length:
self._condensation = []
self._last_history_length = 0

new_events = state.history[self._last_history_length :]

with self.metadata_batch(state):
Expand Down
20 changes: 8 additions & 12 deletions openhands/runtime/action_execution_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import argparse
import asyncio
import base64
import io
import mimetypes
import os
import shutil
Expand All @@ -21,12 +20,13 @@

from fastapi import Depends, FastAPI, HTTPException, Request, UploadFile
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse, StreamingResponse
from fastapi.responses import FileResponse, JSONResponse
from fastapi.security import APIKeyHeader
from openhands_aci.editor.editor import OHEditor
from openhands_aci.editor.exceptions import ToolError
from openhands_aci.editor.results import ToolResult
from pydantic import BaseModel
from starlette.background import BackgroundTask
from starlette.exceptions import HTTPException as StarletteHTTPException
from uvicorn import run

Expand Down Expand Up @@ -631,7 +631,7 @@ async def upload_file(
raise HTTPException(status_code=500, detail=str(e))

@app.get('/download_files')
async def download_file(path: str):
def download_file(path: str):
logger.debug('Downloading files')
try:
if not os.path.isabs(path):
Expand All @@ -642,23 +642,19 @@ async def download_file(path: str):
if not os.path.exists(path):
raise HTTPException(status_code=404, detail='File not found')

with tempfile.TemporaryFile() as temp_zip:
with tempfile.NamedTemporaryFile(suffix='.zip', delete=False) as temp_zip:
with ZipFile(temp_zip, 'w') as zipf:
for root, _, files in os.walk(path):
for file in files:
file_path = os.path.join(root, file)
zipf.write(
file_path, arcname=os.path.relpath(file_path, path)
)
temp_zip.seek(0) # Rewind the file to the beginning after writing
content = temp_zip.read()
# Good for small to medium-sized files. For very large files, streaming directly from the
# file chunks may be more memory-efficient.
zip_stream = io.BytesIO(content)
return StreamingResponse(
content=zip_stream,
return FileResponse(
path=temp_zip.name,
media_type='application/zip',
headers={'Content-Disposition': f'attachment; filename={path}.zip'},
filename=f'{os.path.basename(path)}.zip',
background=BackgroundTask(lambda: os.unlink(temp_zip.name)),
)

except Exception as e:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import shutil
import tempfile
import threading
from abc import abstractmethod
Expand Down Expand Up @@ -143,12 +144,10 @@ def copy_from(self, path: str) -> Path:
stream=True,
timeout=30,
) as response:
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
total_length = 0
for chunk in response.iter_content(chunk_size=8192):
if chunk: # filter out keep-alive new chunks
total_length += len(chunk)
temp_file.write(chunk)
with tempfile.NamedTemporaryFile(
suffix='.zip', delete=False
) as temp_file:
shutil.copyfileobj(response.raw, temp_file, length=16 * 1024)
return Path(temp_file.name)
except requests.Timeout:
raise TimeoutError('Copy operation timed out')
Expand Down
20 changes: 7 additions & 13 deletions openhands/server/routes/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

from fastapi import (
APIRouter,
BackgroundTasks,
HTTPException,
Request,
UploadFile,
Expand All @@ -12,6 +11,7 @@
from fastapi.responses import FileResponse, JSONResponse
from pathspec import PathSpec
from pathspec.patterns import GitWildMatchPattern
from starlette.background import BackgroundTask

from openhands.core.exceptions import AgentRuntimeUnavailableError
from openhands.core.logger import openhands_logger as logger
Expand Down Expand Up @@ -309,31 +309,25 @@ async def save_file(request: Request):


@app.get('/zip-directory')
async def zip_current_workspace(
request: Request, conversation_id: str, background_tasks: BackgroundTasks
):
def zip_current_workspace(request: Request, conversation_id: str):
try:
logger.debug('Zipping workspace')
runtime: Runtime = request.state.conversation.runtime
path = runtime.config.workspace_mount_path_in_sandbox
try:
zip_file = await call_sync_from_async(runtime.copy_from, path)
zip_file_path = runtime.copy_from(path)
except AgentRuntimeUnavailableError as e:
logger.error(f'Error zipping workspace: {e}')
return JSONResponse(
status_code=500,
content={'error': f'Error zipping workspace: {e}'},
)
response = FileResponse(
path=zip_file,
return FileResponse(
path=zip_file_path,
filename='workspace.zip',
media_type='application/x-zip-compressed',
media_type='application/zip',
background=BackgroundTask(lambda: os.unlink(zip_file_path)),
)

# This will execute after the response is sent (So the file is not deleted before being sent)
background_tasks.add_task(zip_file.unlink)

return response
except Exception as e:
logger.error(f'Error zipping workspace: {e}')
raise HTTPException(
Expand Down
2 changes: 1 addition & 1 deletion openhands/storage/conversation/conversation_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ConversationStore(ABC):
"""

@abstractmethod
async def save_metadata(self, metadata: ConversationMetadata):
async def save_metadata(self, metadata: ConversationMetadata) -> None:
"""Store conversation metadata"""

@abstractmethod
Expand Down
2 changes: 1 addition & 1 deletion openhands/storage/conversation/file_conversation_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
class FileConversationStore(ConversationStore):
file_store: FileStore

async def save_metadata(self, metadata: ConversationMetadata):
async def save_metadata(self, metadata: ConversationMetadata) -> None:
json_str = conversation_metadata_type_adapter.dump_json(metadata)
path = self.get_conversation_metadata_filename(metadata.conversation_id)
await call_sync_from_async(self.file_store.write, path, json_str)
Expand Down
2 changes: 1 addition & 1 deletion openhands/storage/google_cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def read(self, path: str) -> str:
blob = self.bucket.blob(path)
try:
with blob.open('r') as f:
return f.read()
return str(f.read())
except NotFound as err:
raise FileNotFoundError(err)

Expand Down
2 changes: 1 addition & 1 deletion openhands/storage/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def get_full_path(self, path: str) -> str:
path = path[1:]
return os.path.join(self.root, path)

def write(self, path: str, contents: str | bytes):
def write(self, path: str, contents: str | bytes) -> None:
full_path = self.get_full_path(path)
os.makedirs(os.path.dirname(full_path), exist_ok=True)
mode = 'w' if isinstance(contents, str) else 'wb'
Expand Down
2 changes: 1 addition & 1 deletion openhands/storage/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def read(self, path: str) -> str:
try:
response = self.client.get_object(Bucket=self.bucket, Key=path)
with response['Body'] as stream:
return stream.read().decode('utf-8')
return str(stream.read().decode('utf-8'))
except botocore.exceptions.ClientError as e:
# Catch all S3-related errors
if e.response['Error']['Code'] == 'NoSuchBucket':
Expand Down
2 changes: 1 addition & 1 deletion openhands/storage/settings/file_settings_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ async def load(self) -> Settings | None:
except FileNotFoundError:
return None

async def store(self, settings: Settings):
async def store(self, settings: Settings) -> None:
json_str = settings.model_dump_json(context={'expose_secrets': True})
await call_sync_from_async(self.file_store.write, self.path, json_str)

Expand Down
2 changes: 1 addition & 1 deletion openhands/storage/settings/settings_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async def load(self) -> Settings | None:
"""Load session init data"""

@abstractmethod
async def store(self, settings: Settings):
async def store(self, settings: Settings) -> None:
"""Store session init data"""

@classmethod
Expand Down
52 changes: 23 additions & 29 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ runloop-api-client = "0.23.0"
libtmux = ">=0.37,<0.40"
pygithub = "^2.5.0"
joblib = "*"
openhands-aci = "^0.2.4"
openhands-aci = "^0.2.5"
python-socketio = "^5.11.4"
redis = "^5.2.0"
sse-starlette = "^2.1.3"
Expand Down
Loading

0 comments on commit 5b8db98

Please sign in to comment.