Compare commits

...

20 Commits

Author SHA1 Message Date
rohitvinodmalhotra@gmail.com
4e02e3bb42 use correct property 2025-02-05 19:52:54 -05:00
rohitvinodmalhotra@gmail.com
675d0d8997 export token before action calls it 2025-02-05 19:39:22 -05:00
rohitvinodmalhotra@gmail.com
d680a2fe82 hide export data 2025-02-05 19:19:37 -05:00
rohitvinodmalhotra@gmail.com
43dfcf0a34 use event stream instead 2025-02-05 19:03:15 -05:00
rohitvinodmalhotra@gmail.com
842d716e49 only keep bg task 2025-02-05 17:49:41 -05:00
rohitvinodmalhotra@gmail.com
6c2f020dec rm connection requirement, go to session directly 2025-02-05 17:31:20 -05:00
rohitvinodmalhotra@gmail.com
a072aa099e Update standalone_conversation_manager.py 2025-02-05 17:10:44 -05:00
rohitvinodmalhotra@gmail.com
664f7991f4 Update standalone_conversation_manager.py 2025-02-05 17:09:55 -05:00
rohitvinodmalhotra@gmail.com
5dab465dd0 debug logs 2025-02-05 16:48:53 -05:00
rohitvinodmalhotra@gmail.com
dd3c5dc6af move to background task 2025-02-05 16:36:00 -05:00
rohitvinodmalhotra@gmail.com
0e4ae562a4 move to public route 2025-02-05 15:52:13 -05:00
rohitvinodmalhotra@gmail.com
47013f8d58 fix circular import 2025-02-05 15:43:18 -05:00
openhands
0c1dd28775 fix: update imports in listen_socket.py to resolve circular imports 2025-02-05 20:08:40 +00:00
openhands
e9c1312243 fix: update imports in settings.py to resolve circular imports 2025-02-05 20:07:54 +00:00
openhands
84766aaba1 fix: update imports in manage_conversations.py to resolve circular imports 2025-02-05 20:06:00 +00:00
openhands
8ad6e547b8 fix: move GithubServiceImpl to separate module to resolve circular imports 2025-02-05 20:05:06 +00:00
openhands
8a7bd9645f fix: move config initialization to separate module to resolve circular imports 2025-02-05 20:04:10 +00:00
openhands
0378aefab8 fix: resolve circular import by moving SettingsStoreImpl to separate module 2025-02-05 20:03:07 +00:00
rohitvinodmalhotra@gmail.com
ab5d8391e4 fetch user token from gh service 2025-02-05 15:00:44 -05:00
rohitvinodmalhotra@gmail.com
a36360311f plumb update token route 2025-02-05 14:54:59 -05:00
14 changed files with 148 additions and 39 deletions

View File

@@ -133,6 +133,14 @@ class Runtime(FileEditRuntimeMixin):
if self.config.sandbox.runtime_startup_env_vars:
self.add_env_vars(self.config.sandbox.runtime_startup_env_vars)
def attach_github_token(self, token) -> None:
print('attaching token via runtime')
cmd = f'export GITHUB_TOKEN={json.dumps(token)};'
obs = self.run(CmdRunAction(cmd))
if not isinstance(obs, CmdOutputObservation) or obs.exit_code != 0:
raise RuntimeError(f'Failed to update gh token: {obs.content}')
def close(self) -> None:
"""
This should only be called by conversation manager or closing the session.
@@ -212,6 +220,17 @@ class Runtime(FileEditRuntimeMixin):
event.set_hard_timeout(self.config.sandbox.timeout, blocking=False)
assert event.timeout is not None
try:
if isinstance(event, CmdRunAction):
print('found event action', event.command)
if '$GITHUB_TOKEN' in event.command:
print('token required by action', event.command)
await call_sync_from_async(
self.run,
CmdRunAction(
"export UNTESTED_GITHUB_TOKEN='this is a dummy token'"
),
)
observation: Observation = await call_sync_from_async(
self.run_action, event
)

View File

@@ -0,0 +1,21 @@
from dotenv import load_dotenv
from openhands.core.config import load_app_config
from openhands.server.config.server_config import load_server_config
from openhands.storage import get_file_store
from openhands.storage.conversation.conversation_store import ConversationStore
from openhands.storage.settings.settings_store import SettingsStore
from openhands.utils.import_utils import get_impl
load_dotenv()
config = load_app_config()
server_config = load_server_config()
file_store = get_file_store(config.file_store, config.file_store_path)
ConversationStoreImpl = get_impl(
ConversationStore, # type: ignore
server_config.conversation_store_class,
)
SettingsStoreImpl = get_impl(SettingsStore, server_config.settings_store_class) # type: ignore

View File

@@ -77,6 +77,10 @@ class ConversationManager(ABC):
async def send_to_event_stream(self, connection_id: str, data: dict):
"""Send data to an event stream."""
@abstractmethod
def update_token(self, connection_id: str):
"""Update/refresh the runtime gh token"""
@abstractmethod
async def disconnect_from_session(self, connection_id: str):
"""Disconnect from a session."""

View File

@@ -10,6 +10,8 @@ from openhands.core.exceptions import AgentRuntimeUnavailableError
from openhands.core.logger import openhands_logger as logger
from openhands.core.schema.agent import AgentState
from openhands.events.action import MessageAction
from openhands.events.action.commands import CmdRunAction
from openhands.events.event import EventSource
from openhands.events.stream import EventStream, session_exists
from openhands.server.session.conversation import Conversation
from openhands.server.session.session import ROOM_KEY, Session
@@ -87,13 +89,17 @@ class StandaloneConversationManager(ConversationManager):
return c
async def join_conversation(
self, sid: str, connection_id: str, settings: Settings, user_id: str | None
self,
sid: str,
connection_id: str,
settings: Settings | None,
user_id: str | None,
):
logger.info(f'join_conversation:{sid}:{connection_id}')
await self.sio.enter_room(connection_id, ROOM_KEY.format(sid=sid))
self._local_connection_id_to_session_id[connection_id] = sid
event_stream = await self._get_event_stream(sid)
if not event_stream:
if not event_stream and settings:
return await self.maybe_start_agent_loop(sid, settings, user_id)
return event_stream
@@ -149,8 +155,8 @@ class StandaloneConversationManager(ConversationManager):
self._close_session(sid) for sid in self._local_agent_loops_by_sid
)
return
except Exception as e:
logger.error(f'error_cleaning_stale')
except Exception:
logger.error('error_cleaning_stale')
await asyncio.sleep(_CLEANUP_INTERVAL)
async def get_running_agent_loops(
@@ -239,6 +245,30 @@ class StandaloneConversationManager(ConversationManager):
raise RuntimeError(f'no_connected_session:{connection_id}:{sid}')
async def update_token(self, connection_id: str):
await asyncio.sleep(1)
# print('updating token')
# session = self._local_agent_loops_by_sid.get(connection_id)
# print(f'found session for sid: {connection_id}')
# if session:
# try:
# await session.update_token()
# except Exception as e:
# print(f'error updating token: {str(e)}')
try:
event_stream = await self.join_conversation(
sid=connection_id,
connection_id=connection_id,
settings=None,
user_id=None,
)
cmd = 'export GITHUB_TOKEN="this is a dummy token";'
action = CmdRunAction(cmd, hidden=True)
event_stream.add_event(action, EventSource.ENVIRONMENT)
except Exception as e:
print(f'error updating token: {str(e)}')
async def disconnect_from_session(self, connection_id: str):
sid = self._local_connection_id_to_session_id.pop(connection_id, None)
logger.info(f'disconnect_from_session:{connection_id}:{sid}')

View File

@@ -3,7 +3,7 @@ import re
from openhands.core.config import AppConfig
from openhands.core.logger import openhands_logger as logger
from openhands.server.shared import config as shared_config
from openhands.server.config_init import config as shared_config
FILES_TO_IGNORE = ['.git/', '.DS_Store', 'node_modules/', '__pycache__/', 'lost+found/']

View File

@@ -14,14 +14,13 @@ from openhands.events.observation import (
from openhands.events.observation.agent import AgentStateChangedObservation
from openhands.events.serialization import event_to_dict
from openhands.events.stream import AsyncEventStreamWrapper
from openhands.server.shared import (
from openhands.server.config_init import (
ConversationStoreImpl,
SettingsStoreImpl,
config,
conversation_manager,
server_config,
sio,
)
from openhands.server.shared import conversation_manager, sio
from openhands.server.types import AppMode

View File

@@ -13,6 +13,7 @@ from starlette.types import ASGIApp
from openhands.server import shared
from openhands.server.auth import get_user_id
from openhands.server.config_init import SettingsStoreImpl
from openhands.server.types import SessionMiddlewareInterface
@@ -188,7 +189,7 @@ class GitHubTokenMiddleware(SessionMiddlewareInterface):
self.app = app
async def __call__(self, request: Request, call_next: Callable):
settings_store = await shared.SettingsStoreImpl.get_instance(
settings_store = await SettingsStoreImpl.get_instance(
shared.config, get_user_id(request)
)
settings = await settings_store.load()

View File

@@ -11,14 +11,14 @@ from openhands.events.action.message import MessageAction
from openhands.events.stream import EventStreamSubscriber
from openhands.runtime import get_runtime_cls
from openhands.server.auth import get_user_id
from openhands.server.routes.github import GithubServiceImpl
from openhands.server.session.conversation_init_data import ConversationInitData
from openhands.server.shared import (
from openhands.server.config_init import (
ConversationStoreImpl,
SettingsStoreImpl,
config,
conversation_manager,
)
from openhands.server.routes.github import GithubServiceImpl
from openhands.server.session.conversation_init_data import ConversationInitData
from openhands.server.shared import conversation_manager
from openhands.server.types import LLMAuthenticationError, MissingSettingsError
from openhands.storage.data_models.conversation_info import ConversationInfo
from openhands.storage.data_models.conversation_info_result_set import (

View File

@@ -1,8 +1,10 @@
import warnings
import requests
from fastapi.responses import JSONResponse
from openhands.security.options import SecurityAnalyzers
from openhands.server.shared import conversation_manager
with warnings.catch_warnings():
warnings.simplefilter('ignore')
@@ -10,6 +12,7 @@ with warnings.catch_warnings():
from fastapi import (
APIRouter,
BackgroundTasks,
)
from openhands.controller.agent import Agent
@@ -113,3 +116,15 @@ async def get_config():
"""
return server_config.get_config()
@app.post('/refresh-runtime')
async def refresh_gh_token_in_runtime(
connection_id: str,
background_tasks: BackgroundTasks,
):
try:
background_tasks.add_task(conversation_manager.update_token, connection_id)
return JSONResponse(status_code=200, content={'message': 'updating'})
except Exception:
return JSONResponse(status_code=400, content={'message': 'updating'})

View File

@@ -3,9 +3,9 @@ from fastapi.responses import JSONResponse
from openhands.core.logger import openhands_logger as logger
from openhands.server.auth import get_user_id
from openhands.server.config_init import SettingsStoreImpl, config
from openhands.server.services.github_service import GitHubService
from openhands.server.settings import GETSettingsModel, POSTSettingsModel, Settings
from openhands.server.shared import SettingsStoreImpl, config
app = APIRouter(prefix='/api')

View File

@@ -4,9 +4,13 @@ import httpx
from fastapi import Request
from openhands.server.auth import get_github_token
from openhands.server.config_init import config, server_config
from openhands.server.data_models.gh_types import GitHubRepository, GitHubUser
from openhands.server.shared import SettingsStoreImpl, config, server_config
from openhands.server.types import AppMode, GhAuthenticationError, GHUnknownException
from openhands.storage.settings.settings_store import SettingsStore
from openhands.utils.import_utils import get_impl
SettingsStoreImpl = get_impl(SettingsStore, server_config.settings_store_class) # type: ignore
class GitHubService:
@@ -16,15 +20,20 @@ class GitHubService:
def __init__(self, user_id: str | None):
self.user_id = user_id
async def get_user_token(self) -> str:
settings_store = await SettingsStoreImpl.get_instance(config, self.user_id)
settings = await settings_store.load()
if settings and settings.github_token:
return settings.github_token.get_secret_value()
return ''
async def _get_github_headers(self):
"""
Retrieve the GH Token from settings store to construct the headers
"""
settings_store = await SettingsStoreImpl.get_instance(config, self.user_id)
settings = await settings_store.load()
if settings and settings.github_token:
self.token = settings.github_token.get_secret_value()
self.token = await self.get_user_token()
return {
'Authorization': f'Bearer {self.token}',

View File

@@ -196,10 +196,14 @@ class AgentSession:
env_vars = (
{
'GITHUB_TOKEN': github_token,
'SESSION_ID': self.sid, # Ensure Session ID is always set
}
if github_token
else None
else {
'SESSION_ID': self.sid, # Ensure Session ID is always set
}
)
self.runtime = runtime_cls(
config=config,
event_stream=self.event_stream,
@@ -329,3 +333,13 @@ class AgentSession:
# If 5 minutes have elapsed and we still don't have a controller, something has gone wrong
return AgentState.ERROR
return None
def update_token(self, token):
print('agent session updating token')
if self.runtime:
self.runtime.attach_github_token(token)
self.event_stream.set_secrets(
{
'github_token': token,
}
)

View File

@@ -23,14 +23,21 @@ from openhands.events.observation.error import ErrorObservation
from openhands.events.serialization import event_from_dict, event_to_dict
from openhands.events.stream import EventStreamSubscriber
from openhands.llm.llm import LLM
from openhands.server.config.server_config import load_server_config
from openhands.server.services.github_service import GitHubService
from openhands.server.session.agent_session import AgentSession
from openhands.server.session.conversation_init_data import ConversationInitData
from openhands.server.settings import Settings
from openhands.storage.files import FileStore
from openhands.utils.import_utils import get_impl
ROOM_KEY = 'room:{sid}'
server_config = load_server_config()
GithubImpl = get_impl(GitHubService, server_config.github_service_class)
class Session:
sid: str
sio: socketio.AsyncServer | None
@@ -215,6 +222,14 @@ class Session:
return
await self._send(data)
async def update_token(self):
print('updating token in sessions')
gh_client = GithubImpl(self.user_id)
token = await gh_client.get_user_token()
if token:
print(f'retrieved user token {token[0:5]}')
self.agent_session.update_token('this is a dummy test token')
async def _send(self, data: dict[str, object]) -> bool:
try:
if not self.is_alive:

View File

@@ -1,24 +1,13 @@
import os
import socketio
from dotenv import load_dotenv
from openhands.core.config import load_app_config
from openhands.server.config.server_config import load_server_config
from openhands.server.config_init import config, file_store, server_config
from openhands.server.conversation_manager.conversation_manager import (
ConversationManager,
)
from openhands.storage import get_file_store
from openhands.storage.conversation.conversation_store import ConversationStore
from openhands.storage.settings.settings_store import SettingsStore
from openhands.utils.import_utils import get_impl
load_dotenv()
config = load_app_config()
server_config = load_server_config()
file_store = get_file_store(config.file_store, config.file_store_path)
client_manager = None
redis_host = os.environ.get('REDIS_HOST')
if redis_host:
@@ -37,10 +26,3 @@ ConversationManagerImpl = get_impl(
server_config.conversation_manager_class,
)
conversation_manager = ConversationManagerImpl.get_instance(sio, config, file_store)
SettingsStoreImpl = get_impl(SettingsStore, server_config.settings_store_class) # type: ignore
ConversationStoreImpl = get_impl(
ConversationStore, # type: ignore
server_config.conversation_store_class,
)