mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
20 Commits
fix-github
...
refresh-ru
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e02e3bb42 | ||
|
|
675d0d8997 | ||
|
|
d680a2fe82 | ||
|
|
43dfcf0a34 | ||
|
|
842d716e49 | ||
|
|
6c2f020dec | ||
|
|
a072aa099e | ||
|
|
664f7991f4 | ||
|
|
5dab465dd0 | ||
|
|
dd3c5dc6af | ||
|
|
0e4ae562a4 | ||
|
|
47013f8d58 | ||
|
|
0c1dd28775 | ||
|
|
e9c1312243 | ||
|
|
84766aaba1 | ||
|
|
8ad6e547b8 | ||
|
|
8a7bd9645f | ||
|
|
0378aefab8 | ||
|
|
ab5d8391e4 | ||
|
|
a36360311f |
@@ -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
|
||||
)
|
||||
|
||||
21
openhands/server/config_init.py
Normal file
21
openhands/server/config_init.py
Normal 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
|
||||
@@ -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."""
|
||||
|
||||
@@ -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}')
|
||||
|
||||
@@ -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/']
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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'})
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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}',
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user