mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-01-09 14:57:59 -05:00
All Runtime Status Codes should be in the RuntimeStatus enum (#9601)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
@@ -5,4 +5,15 @@ export type RuntimeStatus =
|
|||||||
| "STATUS$RUNTIME_STARTED"
|
| "STATUS$RUNTIME_STARTED"
|
||||||
| "STATUS$SETTING_UP_WORKSPACE"
|
| "STATUS$SETTING_UP_WORKSPACE"
|
||||||
| "STATUS$SETTING_UP_GIT_HOOKS"
|
| "STATUS$SETTING_UP_GIT_HOOKS"
|
||||||
| "STATUS$READY";
|
| "STATUS$READY"
|
||||||
|
| "STATUS$ERROR"
|
||||||
|
| "STATUS$ERROR_RUNTIME_DISCONNECTED"
|
||||||
|
| "STATUS$ERROR_LLM_AUTHENTICATION"
|
||||||
|
| "STATUS$ERROR_LLM_SERVICE_UNAVAILABLE"
|
||||||
|
| "STATUS$ERROR_LLM_INTERNAL_SERVER_ERROR"
|
||||||
|
| "STATUS$ERROR_LLM_OUT_OF_CREDITS"
|
||||||
|
| "STATUS$ERROR_LLM_CONTENT_POLICY_VIOLATION"
|
||||||
|
| "CHAT_INTERFACE$AGENT_RATE_LIMITED_STOPPED_MESSAGE"
|
||||||
|
| "STATUS$GIT_PROVIDER_AUTHENTICATION_ERROR"
|
||||||
|
| "STATUS$LLM_RETRY"
|
||||||
|
| "STATUS$ERROR_MEMORY";
|
||||||
|
|||||||
@@ -88,11 +88,15 @@ export function getStatusCode(
|
|||||||
if (conversationStatus === "STOPPED" || runtimeStatus === "STATUS$STOPPED") {
|
if (conversationStatus === "STOPPED" || runtimeStatus === "STATUS$STOPPED") {
|
||||||
return I18nKey.CHAT_INTERFACE$STOPPED;
|
return I18nKey.CHAT_INTERFACE$STOPPED;
|
||||||
}
|
}
|
||||||
if (runtimeStatus === "STATUS$BUILDING_RUNTIME") {
|
if (
|
||||||
return I18nKey.STATUS$BUILDING_RUNTIME;
|
runtimeStatus &&
|
||||||
}
|
!["STATUS$READY", "STATUS$RUNTIME_STARTED"].includes(runtimeStatus)
|
||||||
if (runtimeStatus === "STATUS$STARTING_RUNTIME") {
|
) {
|
||||||
return I18nKey.STATUS$STARTING_RUNTIME;
|
const result = (I18nKey as { [key: string]: string })[runtimeStatus];
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return runtimeStatus;
|
||||||
}
|
}
|
||||||
if (webSocketStatus === "DISCONNECTED") {
|
if (webSocketStatus === "DISCONNECTED") {
|
||||||
return I18nKey.CHAT_INTERFACE$DISCONNECTED;
|
return I18nKey.CHAT_INTERFACE$DISCONNECTED;
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ from openhands.events.observation import (
|
|||||||
from openhands.events.serialization.event import truncate_content
|
from openhands.events.serialization.event import truncate_content
|
||||||
from openhands.llm.llm import LLM
|
from openhands.llm.llm import LLM
|
||||||
from openhands.llm.metrics import Metrics
|
from openhands.llm.metrics import Metrics
|
||||||
|
from openhands.runtime.runtime_status import RuntimeStatus
|
||||||
from openhands.storage.files import FileStore
|
from openhands.storage.files import FileStore
|
||||||
|
|
||||||
# note: RESUME is only available on web GUI
|
# note: RESUME is only available on web GUI
|
||||||
@@ -248,10 +249,10 @@ class AgentController:
|
|||||||
self.state.last_error = f'{type(e).__name__}: {str(e)}'
|
self.state.last_error = f'{type(e).__name__}: {str(e)}'
|
||||||
|
|
||||||
if self.status_callback is not None:
|
if self.status_callback is not None:
|
||||||
err_id = ''
|
runtime_status = RuntimeStatus.ERROR
|
||||||
if isinstance(e, AuthenticationError):
|
if isinstance(e, AuthenticationError):
|
||||||
err_id = 'STATUS$ERROR_LLM_AUTHENTICATION'
|
runtime_status = RuntimeStatus.ERROR_LLM_AUTHENTICATION
|
||||||
self.state.last_error = err_id
|
self.state.last_error = runtime_status.value
|
||||||
elif isinstance(
|
elif isinstance(
|
||||||
e,
|
e,
|
||||||
(
|
(
|
||||||
@@ -260,20 +261,20 @@ class AgentController:
|
|||||||
APIError,
|
APIError,
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
err_id = 'STATUS$ERROR_LLM_SERVICE_UNAVAILABLE'
|
runtime_status = RuntimeStatus.ERROR_LLM_SERVICE_UNAVAILABLE
|
||||||
self.state.last_error = err_id
|
self.state.last_error = runtime_status.value
|
||||||
elif isinstance(e, InternalServerError):
|
elif isinstance(e, InternalServerError):
|
||||||
err_id = 'STATUS$ERROR_LLM_INTERNAL_SERVER_ERROR'
|
runtime_status = RuntimeStatus.ERROR_LLM_INTERNAL_SERVER_ERROR
|
||||||
self.state.last_error = err_id
|
self.state.last_error = runtime_status.value
|
||||||
elif isinstance(e, BadRequestError) and 'ExceededBudget' in str(e):
|
elif isinstance(e, BadRequestError) and 'ExceededBudget' in str(e):
|
||||||
err_id = 'STATUS$ERROR_LLM_OUT_OF_CREDITS'
|
runtime_status = RuntimeStatus.ERROR_LLM_OUT_OF_CREDITS
|
||||||
self.state.last_error = err_id
|
self.state.last_error = runtime_status.value
|
||||||
elif isinstance(e, ContentPolicyViolationError) or (
|
elif isinstance(e, ContentPolicyViolationError) or (
|
||||||
isinstance(e, BadRequestError)
|
isinstance(e, BadRequestError)
|
||||||
and 'ContentPolicyViolationError' in str(e)
|
and 'ContentPolicyViolationError' in str(e)
|
||||||
):
|
):
|
||||||
err_id = 'STATUS$ERROR_LLM_CONTENT_POLICY_VIOLATION'
|
runtime_status = RuntimeStatus.ERROR_LLM_CONTENT_POLICY_VIOLATION
|
||||||
self.state.last_error = err_id
|
self.state.last_error = runtime_status.value
|
||||||
elif isinstance(e, RateLimitError):
|
elif isinstance(e, RateLimitError):
|
||||||
# Check if this is the final retry attempt
|
# Check if this is the final retry attempt
|
||||||
if (
|
if (
|
||||||
@@ -283,14 +284,14 @@ class AgentController:
|
|||||||
):
|
):
|
||||||
# All retries exhausted, set to ERROR state with a special message
|
# All retries exhausted, set to ERROR state with a special message
|
||||||
self.state.last_error = (
|
self.state.last_error = (
|
||||||
'CHAT_INTERFACE$AGENT_RATE_LIMITED_STOPPED_MESSAGE'
|
RuntimeStatus.AGENT_RATE_LIMITED_STOPPED_MESSAGE.value
|
||||||
)
|
)
|
||||||
await self.set_agent_state_to(AgentState.ERROR)
|
await self.set_agent_state_to(AgentState.ERROR)
|
||||||
else:
|
else:
|
||||||
# Still retrying, set to RATE_LIMITED state
|
# Still retrying, set to RATE_LIMITED state
|
||||||
await self.set_agent_state_to(AgentState.RATE_LIMITED)
|
await self.set_agent_state_to(AgentState.RATE_LIMITED)
|
||||||
return
|
return
|
||||||
self.status_callback('error', err_id, self.state.last_error)
|
self.status_callback('error', runtime_status, self.state.last_error)
|
||||||
|
|
||||||
# Set the agent state to ERROR after storing the reason
|
# Set the agent state to ERROR after storing the reason
|
||||||
await self.set_agent_state_to(AgentState.ERROR)
|
await self.set_agent_state_to(AgentState.ERROR)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from openhands.core.logger import openhands_logger as logger
|
|||||||
from openhands.core.schema import AgentState
|
from openhands.core.schema import AgentState
|
||||||
from openhands.memory.memory import Memory
|
from openhands.memory.memory import Memory
|
||||||
from openhands.runtime.base import Runtime
|
from openhands.runtime.base import Runtime
|
||||||
|
from openhands.runtime.runtime_status import RuntimeStatus
|
||||||
|
|
||||||
|
|
||||||
async def run_agent_until_done(
|
async def run_agent_until_done(
|
||||||
@@ -19,7 +20,7 @@ async def run_agent_until_done(
|
|||||||
Note that runtime must be connected before being passed in here.
|
Note that runtime must be connected before being passed in here.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def status_callback(msg_type: str, msg_id: str, msg: str) -> None:
|
def status_callback(msg_type: str, runtime_status: RuntimeStatus, msg: str) -> None:
|
||||||
if msg_type == 'error':
|
if msg_type == 'error':
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
if controller:
|
if controller:
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from openhands.microagent import (
|
|||||||
load_microagents_from_dir,
|
load_microagents_from_dir,
|
||||||
)
|
)
|
||||||
from openhands.runtime.base import Runtime
|
from openhands.runtime.base import Runtime
|
||||||
|
from openhands.runtime.runtime_status import RuntimeStatus
|
||||||
from openhands.utils.prompt import (
|
from openhands.utils.prompt import (
|
||||||
ConversationInstructions,
|
ConversationInstructions,
|
||||||
RepositoryInfo,
|
RepositoryInfo,
|
||||||
@@ -133,7 +134,7 @@ class Memory:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_str = f'Error: {str(e.__class__.__name__)}'
|
error_str = f'Error: {str(e.__class__.__name__)}'
|
||||||
logger.error(error_str)
|
logger.error(error_str)
|
||||||
self.send_error_message('STATUS$ERROR_MEMORY', error_str)
|
self.set_runtime_status(RuntimeStatus.ERROR_MEMORY, error_str)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _on_workspace_context_recall(
|
def _on_workspace_context_recall(
|
||||||
@@ -361,22 +362,24 @@ class Memory:
|
|||||||
content=conversation_instructions or ''
|
content=conversation_instructions or ''
|
||||||
)
|
)
|
||||||
|
|
||||||
def send_error_message(self, message_id: str, message: str):
|
def set_runtime_status(self, status: RuntimeStatus, message: str):
|
||||||
"""Sends an error message if the callback function was provided."""
|
"""Sends an error message if the callback function was provided."""
|
||||||
if self.status_callback:
|
if self.status_callback:
|
||||||
try:
|
try:
|
||||||
if self.loop is None:
|
if self.loop is None:
|
||||||
self.loop = asyncio.get_running_loop()
|
self.loop = asyncio.get_running_loop()
|
||||||
asyncio.run_coroutine_threadsafe(
|
asyncio.run_coroutine_threadsafe(
|
||||||
self._send_status_message('error', message_id, message), self.loop
|
self._set_runtime_status('error', status, message), self.loop
|
||||||
)
|
)
|
||||||
except RuntimeError as e:
|
except (RuntimeError, KeyError) as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
f'Error sending status message: {e.__class__.__name__}',
|
f'Error sending status message: {e.__class__.__name__}',
|
||||||
stack_info=False,
|
stack_info=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _send_status_message(self, msg_type: str, id: str, message: str):
|
async def _set_runtime_status(
|
||||||
|
self, msg_type: str, runtime_status: RuntimeStatus, message: str
|
||||||
|
):
|
||||||
"""Sends a status message to the client."""
|
"""Sends a status message to the client."""
|
||||||
if self.status_callback:
|
if self.status_callback:
|
||||||
self.status_callback(msg_type, id, message)
|
self.status_callback(msg_type, runtime_status, message)
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class Runtime(FileEditRuntimeMixin):
|
|||||||
config: OpenHandsConfig
|
config: OpenHandsConfig
|
||||||
initial_env_vars: dict[str, str]
|
initial_env_vars: dict[str, str]
|
||||||
attach_to_existing: bool
|
attach_to_existing: bool
|
||||||
status_callback: Callable[[str, str, str], None] | None
|
status_callback: Callable[[str, RuntimeStatus, str], None] | None
|
||||||
runtime_status: RuntimeStatus | None
|
runtime_status: RuntimeStatus | None
|
||||||
_runtime_initialized: bool = False
|
_runtime_initialized: bool = False
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ class Runtime(FileEditRuntimeMixin):
|
|||||||
sid: str = 'default',
|
sid: str = 'default',
|
||||||
plugins: list[PluginRequirement] | None = None,
|
plugins: list[PluginRequirement] | None = None,
|
||||||
env_vars: dict[str, str] | None = None,
|
env_vars: dict[str, str] | None = None,
|
||||||
status_callback: Callable[[str, str, str], None] | None = None,
|
status_callback: Callable[[str, RuntimeStatus, str], None] | None = None,
|
||||||
attach_to_existing: bool = False,
|
attach_to_existing: bool = False,
|
||||||
headless_mode: bool = False,
|
headless_mode: bool = False,
|
||||||
user_id: str | None = None,
|
user_id: str | None = None,
|
||||||
@@ -207,16 +207,13 @@ class Runtime(FileEditRuntimeMixin):
|
|||||||
message = f'[runtime {self.sid}] {message}'
|
message = f'[runtime {self.sid}] {message}'
|
||||||
getattr(logger, level)(message, stacklevel=2)
|
getattr(logger, level)(message, stacklevel=2)
|
||||||
|
|
||||||
def set_runtime_status(self, runtime_status: RuntimeStatus):
|
def set_runtime_status(
|
||||||
|
self, runtime_status: RuntimeStatus, msg: str = '', level: str = 'info'
|
||||||
|
):
|
||||||
"""Sends a status message if the callback function was provided."""
|
"""Sends a status message if the callback function was provided."""
|
||||||
self.runtime_status = runtime_status
|
self.runtime_status = runtime_status
|
||||||
if self.status_callback:
|
if self.status_callback:
|
||||||
msg_id: str = runtime_status.value # type: ignore
|
self.status_callback(level, runtime_status, msg)
|
||||||
self.status_callback('info', msg_id, runtime_status.message)
|
|
||||||
|
|
||||||
def send_error_message(self, message_id: str, message: str):
|
|
||||||
if self.status_callback:
|
|
||||||
self.status_callback('error', message_id, message)
|
|
||||||
|
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
|
|
||||||
@@ -344,15 +341,13 @@ class Runtime(FileEditRuntimeMixin):
|
|||||||
else:
|
else:
|
||||||
observation = await call_sync_from_async(self.run_action, event)
|
observation = await call_sync_from_async(self.run_action, event)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
err_id = ''
|
runtime_status = RuntimeStatus.ERROR
|
||||||
if isinstance(e, httpx.NetworkError) or isinstance(
|
if isinstance(e, (httpx.NetworkError, AgentRuntimeDisconnectedError)):
|
||||||
e, AgentRuntimeDisconnectedError
|
runtime_status = RuntimeStatus.ERROR_RUNTIME_DISCONNECTED
|
||||||
):
|
|
||||||
err_id = 'STATUS$ERROR_RUNTIME_DISCONNECTED'
|
|
||||||
error_message = f'{type(e).__name__}: {str(e)}'
|
error_message = f'{type(e).__name__}: {str(e)}'
|
||||||
self.log('error', f'Unexpected error while running action: {error_message}')
|
self.log('error', f'Unexpected error while running action: {error_message}')
|
||||||
self.log('error', f'Problematic action: {str(event)}')
|
self.log('error', f'Problematic action: {str(event)}')
|
||||||
self.send_error_message(err_id, error_message)
|
self.set_runtime_status(runtime_status, error_message)
|
||||||
return
|
return
|
||||||
|
|
||||||
observation._cause = event.id # type: ignore[attr-defined]
|
observation._cause = event.id # type: ignore[attr-defined]
|
||||||
@@ -397,7 +392,7 @@ class Runtime(FileEditRuntimeMixin):
|
|||||||
|
|
||||||
if self.status_callback:
|
if self.status_callback:
|
||||||
self.status_callback(
|
self.status_callback(
|
||||||
'info', 'STATUS$SETTING_UP_WORKSPACE', 'Setting up workspace...'
|
'info', RuntimeStatus.SETTING_UP_WORKSPACE, 'Setting up workspace...'
|
||||||
)
|
)
|
||||||
|
|
||||||
dir_name = selected_repository.split('/')[-1]
|
dir_name = selected_repository.split('/')[-1]
|
||||||
@@ -438,7 +433,7 @@ class Runtime(FileEditRuntimeMixin):
|
|||||||
|
|
||||||
if self.status_callback:
|
if self.status_callback:
|
||||||
self.status_callback(
|
self.status_callback(
|
||||||
'info', 'STATUS$SETTING_UP_WORKSPACE', 'Setting up workspace...'
|
'info', RuntimeStatus.SETTING_UP_WORKSPACE, 'Setting up workspace...'
|
||||||
)
|
)
|
||||||
|
|
||||||
# setup scripts time out after 10 minutes
|
# setup scripts time out after 10 minutes
|
||||||
@@ -470,7 +465,7 @@ class Runtime(FileEditRuntimeMixin):
|
|||||||
|
|
||||||
if self.status_callback:
|
if self.status_callback:
|
||||||
self.status_callback(
|
self.status_callback(
|
||||||
'info', 'STATUS$SETTING_UP_GIT_HOOKS', 'Setting up git hooks...'
|
'info', RuntimeStatus.SETTING_UP_GIT_HOOKS, 'Setting up git hooks...'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ensure the git hooks directory exists
|
# Ensure the git hooks directory exists
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class CLIRuntime(Runtime):
|
|||||||
sid: str = 'default',
|
sid: str = 'default',
|
||||||
plugins: list[PluginRequirement] | None = None,
|
plugins: list[PluginRequirement] | None = None,
|
||||||
env_vars: dict[str, str] | None = None,
|
env_vars: dict[str, str] | None = None,
|
||||||
status_callback: Callable[[str, str, str], None] | None = None,
|
status_callback: Callable[[str, RuntimeStatus, str], None] | None = None,
|
||||||
attach_to_existing: bool = False,
|
attach_to_existing: bool = False,
|
||||||
headless_mode: bool = False,
|
headless_mode: bool = False,
|
||||||
user_id: str | None = None,
|
user_id: str | None = None,
|
||||||
|
|||||||
@@ -252,8 +252,8 @@ class KubernetesRuntime(ActionExecutionClient):
|
|||||||
await call_sync_from_async(self._wait_until_ready)
|
await call_sync_from_async(self._wait_until_ready)
|
||||||
except Exception as alive_error:
|
except Exception as alive_error:
|
||||||
self.log('error', f'Failed to connect to runtime: {alive_error}')
|
self.log('error', f'Failed to connect to runtime: {alive_error}')
|
||||||
self.send_error_message(
|
self.set_runtime_status(
|
||||||
'ERROR$RUNTIME_CONNECTION',
|
RuntimeStatus.ERROR_RUNTIME_DISCONNECTED,
|
||||||
f'Failed to connect to runtime: {alive_error}',
|
f'Failed to connect to runtime: {alive_error}',
|
||||||
)
|
)
|
||||||
raise AgentRuntimeDisconnectedError(
|
raise AgentRuntimeDisconnectedError(
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ class LocalRuntime(ActionExecutionClient):
|
|||||||
sid: str = 'default',
|
sid: str = 'default',
|
||||||
plugins: list[PluginRequirement] | None = None,
|
plugins: list[PluginRequirement] | None = None,
|
||||||
env_vars: dict[str, str] | None = None,
|
env_vars: dict[str, str] | None = None,
|
||||||
status_callback: Callable[[str, str, str], None] | None = None,
|
status_callback: Callable[[str, RuntimeStatus, str], None] | None = None,
|
||||||
attach_to_existing: bool = False,
|
attach_to_existing: bool = False,
|
||||||
headless_mode: bool = True,
|
headless_mode: bool = True,
|
||||||
user_id: str | None = None,
|
user_id: str | None = None,
|
||||||
|
|||||||
@@ -2,14 +2,23 @@ from enum import Enum
|
|||||||
|
|
||||||
|
|
||||||
class RuntimeStatus(Enum):
|
class RuntimeStatus(Enum):
|
||||||
def __init__(self, value: str, message: str):
|
STOPPED = 'STATUS$STOPPED'
|
||||||
self._value_ = value
|
BUILDING_RUNTIME = 'STATUS$BUILDING_RUNTIME'
|
||||||
self.message = message
|
STARTING_RUNTIME = 'STATUS$STARTING_RUNTIME'
|
||||||
|
RUNTIME_STARTED = 'STATUS$RUNTIME_STARTED'
|
||||||
STOPPED = 'STATUS$STOPPED', 'Stopped'
|
SETTING_UP_WORKSPACE = 'STATUS$SETTING_UP_WORKSPACE'
|
||||||
BUILDING_RUNTIME = 'STATUS$BUILDING_RUNTIME', 'Building runtime...'
|
SETTING_UP_GIT_HOOKS = 'STATUS$SETTING_UP_GIT_HOOKS'
|
||||||
STARTING_RUNTIME = 'STATUS$STARTING_RUNTIME', 'Starting runtime...'
|
READY = 'STATUS$READY'
|
||||||
RUNTIME_STARTED = 'STATUS$RUNTIME_STARTED', 'Runtime started...'
|
ERROR = 'STATUS$ERROR'
|
||||||
SETTING_UP_WORKSPACE = 'STATUS$SETTING_UP_WORKSPACE', 'Setting up workspace...'
|
ERROR_RUNTIME_DISCONNECTED = 'STATUS$ERROR_RUNTIME_DISCONNECTED'
|
||||||
SETTING_UP_GIT_HOOKS = 'STATUS$SETTING_UP_GIT_HOOKS', 'Setting up git hooks...'
|
ERROR_LLM_AUTHENTICATION = 'STATUS$ERROR_LLM_AUTHENTICATION'
|
||||||
READY = 'STATUS$READY', 'Ready...'
|
ERROR_LLM_SERVICE_UNAVAILABLE = 'STATUS$ERROR_LLM_SERVICE_UNAVAILABLE'
|
||||||
|
ERROR_LLM_INTERNAL_SERVER_ERROR = 'STATUS$ERROR_LLM_INTERNAL_SERVER_ERROR'
|
||||||
|
ERROR_LLM_OUT_OF_CREDITS = 'STATUS$ERROR_LLM_OUT_OF_CREDITS'
|
||||||
|
ERROR_LLM_CONTENT_POLICY_VIOLATION = 'STATUS$ERROR_LLM_CONTENT_POLICY_VIOLATION'
|
||||||
|
AGENT_RATE_LIMITED_STOPPED_MESSAGE = (
|
||||||
|
'CHAT_INTERFACE$AGENT_RATE_LIMITED_STOPPED_MESSAGE'
|
||||||
|
)
|
||||||
|
GIT_PROVIDER_AUTHENTICATION_ERROR = 'STATUS$GIT_PROVIDER_AUTHENTICATION_ERROR'
|
||||||
|
LLM_RETRY = 'STATUS$LLM_RETRY'
|
||||||
|
ERROR_MEMORY = 'STATUS$ERROR_MEMORY'
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ from openhands.integrations.service_types import (
|
|||||||
)
|
)
|
||||||
from openhands.llm.llm import LLM
|
from openhands.llm.llm import LLM
|
||||||
from openhands.runtime import get_runtime_cls
|
from openhands.runtime import get_runtime_cls
|
||||||
|
from openhands.runtime.runtime_status import RuntimeStatus
|
||||||
from openhands.server.data_models.agent_loop_info import AgentLoopInfo
|
from openhands.server.data_models.agent_loop_info import AgentLoopInfo
|
||||||
from openhands.server.data_models.conversation_info import ConversationInfo
|
from openhands.server.data_models.conversation_info import ConversationInfo
|
||||||
from openhands.server.data_models.conversation_info_result_set import (
|
from openhands.server.data_models.conversation_info_result_set import (
|
||||||
@@ -189,7 +190,7 @@ async def new_conversation(
|
|||||||
content={
|
content={
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'message': str(e),
|
'message': str(e),
|
||||||
'msg_id': 'STATUS$ERROR_LLM_AUTHENTICATION',
|
'msg_id': RuntimeStatus.ERROR_LLM_AUTHENTICATION.value,
|
||||||
},
|
},
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
@@ -199,7 +200,7 @@ async def new_conversation(
|
|||||||
content={
|
content={
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'message': str(e),
|
'message': str(e),
|
||||||
'msg_id': 'STATUS$GIT_PROVIDER_AUTHENTICATION_ERROR',
|
'msg_id': RuntimeStatus.GIT_PROVIDER_AUTHENTICATION_ERROR.value,
|
||||||
},
|
},
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from openhands.microagent.microagent import BaseMicroagent
|
|||||||
from openhands.runtime import get_runtime_cls
|
from openhands.runtime import get_runtime_cls
|
||||||
from openhands.runtime.base import Runtime
|
from openhands.runtime.base import Runtime
|
||||||
from openhands.runtime.impl.remote.remote_runtime import RemoteRuntime
|
from openhands.runtime.impl.remote.remote_runtime import RemoteRuntime
|
||||||
|
from openhands.runtime.runtime_status import RuntimeStatus
|
||||||
from openhands.security import SecurityAnalyzer, options
|
from openhands.security import SecurityAnalyzer, options
|
||||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||||
from openhands.storage.files import FileStore
|
from openhands.storage.files import FileStore
|
||||||
@@ -377,7 +378,7 @@ class AgentSession:
|
|||||||
self.logger.error(f'Runtime initialization failed: {e}')
|
self.logger.error(f'Runtime initialization failed: {e}')
|
||||||
if self._status_callback:
|
if self._status_callback:
|
||||||
self._status_callback(
|
self._status_callback(
|
||||||
'error', 'STATUS$ERROR_RUNTIME_DISCONNECTED', str(e)
|
'error', RuntimeStatus.ERROR_RUNTIME_DISCONNECTED, str(e)
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ from openhands.events.observation.error import ErrorObservation
|
|||||||
from openhands.events.serialization import event_from_dict, event_to_dict
|
from openhands.events.serialization import event_from_dict, event_to_dict
|
||||||
from openhands.events.stream import EventStreamSubscriber
|
from openhands.events.stream import EventStreamSubscriber
|
||||||
from openhands.llm.llm import LLM
|
from openhands.llm.llm import LLM
|
||||||
|
from openhands.runtime.runtime_status import RuntimeStatus
|
||||||
from openhands.server.session.agent_session import AgentSession
|
from openhands.server.session.agent_session import AgentSession
|
||||||
from openhands.server.session.conversation_init_data import ConversationInitData
|
from openhands.server.session.conversation_init_data import ConversationInitData
|
||||||
from openhands.storage.data_models.settings import Settings
|
from openhands.storage.data_models.settings import Settings
|
||||||
@@ -249,9 +250,8 @@ class Session:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _notify_on_llm_retry(self, retries: int, max: int) -> None:
|
def _notify_on_llm_retry(self, retries: int, max: int) -> None:
|
||||||
msg_id = 'STATUS$LLM_RETRY'
|
|
||||||
self.queue_status_message(
|
self.queue_status_message(
|
||||||
'info', msg_id, f'Retrying LLM request, {retries} / {max}'
|
'info', RuntimeStatus.LLM_RETRY, f'Retrying LLM request, {retries} / {max}'
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_event(self, event: Event) -> None:
|
def on_event(self, event: Event) -> None:
|
||||||
@@ -337,7 +337,9 @@ class Session:
|
|||||||
"""Sends an error message to the client."""
|
"""Sends an error message to the client."""
|
||||||
await self.send({'error': True, 'message': message})
|
await self.send({'error': True, 'message': message})
|
||||||
|
|
||||||
async def _send_status_message(self, msg_type: str, id: str, message: str) -> None:
|
async def _send_status_message(
|
||||||
|
self, msg_type: str, runtime_status: RuntimeStatus, message: str
|
||||||
|
) -> None:
|
||||||
"""Sends a status message to the client."""
|
"""Sends a status message to the client."""
|
||||||
if msg_type == 'error':
|
if msg_type == 'error':
|
||||||
agent_session = self.agent_session
|
agent_session = self.agent_session
|
||||||
@@ -349,11 +351,18 @@ class Session:
|
|||||||
extra={'signal': 'agent_status_error'},
|
extra={'signal': 'agent_status_error'},
|
||||||
)
|
)
|
||||||
await self.send(
|
await self.send(
|
||||||
{'status_update': True, 'type': msg_type, 'id': id, 'message': message}
|
{
|
||||||
|
'status_update': True,
|
||||||
|
'type': msg_type,
|
||||||
|
'id': runtime_status.value,
|
||||||
|
'message': message,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def queue_status_message(self, msg_type: str, id: str, message: str) -> None:
|
def queue_status_message(
|
||||||
|
self, msg_type: str, runtime_status: RuntimeStatus, message: str
|
||||||
|
) -> None:
|
||||||
"""Queues a status message to be sent asynchronously."""
|
"""Queues a status message to be sent asynchronously."""
|
||||||
asyncio.run_coroutine_threadsafe(
|
asyncio.run_coroutine_threadsafe(
|
||||||
self._send_status_message(msg_type, id, message), self.loop
|
self._send_status_message(msg_type, runtime_status, message), self.loop
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ from openhands.runtime.base import Runtime
|
|||||||
from openhands.runtime.impl.action_execution.action_execution_client import (
|
from openhands.runtime.impl.action_execution.action_execution_client import (
|
||||||
ActionExecutionClient,
|
ActionExecutionClient,
|
||||||
)
|
)
|
||||||
|
from openhands.runtime.runtime_status import RuntimeStatus
|
||||||
from openhands.storage.memory import InMemoryFileStore
|
from openhands.storage.memory import InMemoryFileStore
|
||||||
|
|
||||||
|
|
||||||
@@ -229,12 +230,15 @@ async def test_react_to_content_policy_violation(
|
|||||||
# Verify the status callback was called with correct parameters
|
# Verify the status callback was called with correct parameters
|
||||||
mock_status_callback.assert_called_once_with(
|
mock_status_callback.assert_called_once_with(
|
||||||
'error',
|
'error',
|
||||||
'STATUS$ERROR_LLM_CONTENT_POLICY_VIOLATION',
|
RuntimeStatus.ERROR_LLM_CONTENT_POLICY_VIOLATION,
|
||||||
'STATUS$ERROR_LLM_CONTENT_POLICY_VIOLATION',
|
RuntimeStatus.ERROR_LLM_CONTENT_POLICY_VIOLATION.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verify the state was updated correctly
|
# Verify the state was updated correctly
|
||||||
assert controller.state.last_error == 'STATUS$ERROR_LLM_CONTENT_POLICY_VIOLATION'
|
assert (
|
||||||
|
controller.state.last_error
|
||||||
|
== RuntimeStatus.ERROR_LLM_CONTENT_POLICY_VIOLATION.value
|
||||||
|
)
|
||||||
assert controller.state.agent_state == AgentState.ERROR
|
assert controller.state.agent_state == AgentState.ERROR
|
||||||
|
|
||||||
await controller.close()
|
await controller.close()
|
||||||
@@ -829,13 +833,15 @@ async def test_notify_on_llm_retry(mock_agent, mock_event_stream, mock_status_ca
|
|||||||
)
|
)
|
||||||
|
|
||||||
def notify_on_llm_retry(attempt, max_attempts):
|
def notify_on_llm_retry(attempt, max_attempts):
|
||||||
controller.status_callback('info', 'STATUS$LLM_RETRY', ANY)
|
controller.status_callback('info', RuntimeStatus.LLM_RETRY, ANY)
|
||||||
|
|
||||||
# Attach the retry listener to the agent's LLM
|
# Attach the retry listener to the agent's LLM
|
||||||
controller.agent.llm.retry_listener = notify_on_llm_retry
|
controller.agent.llm.retry_listener = notify_on_llm_retry
|
||||||
|
|
||||||
controller.agent.llm.retry_listener(1, 2)
|
controller.agent.llm.retry_listener(1, 2)
|
||||||
controller.status_callback.assert_called_once_with('info', 'STATUS$LLM_RETRY', ANY)
|
controller.status_callback.assert_called_once_with(
|
||||||
|
'info', RuntimeStatus.LLM_RETRY, ANY
|
||||||
|
)
|
||||||
await controller.close()
|
await controller.close()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from openhands.integrations.service_types import (
|
|||||||
SuggestedTask,
|
SuggestedTask,
|
||||||
TaskType,
|
TaskType,
|
||||||
)
|
)
|
||||||
|
from openhands.runtime.runtime_status import RuntimeStatus
|
||||||
from openhands.server.data_models.conversation_info import ConversationInfo
|
from openhands.server.data_models.conversation_info import ConversationInfo
|
||||||
from openhands.server.data_models.conversation_info_result_set import (
|
from openhands.server.data_models.conversation_info_result_set import (
|
||||||
ConversationInfoResultSet,
|
ConversationInfoResultSet,
|
||||||
@@ -405,7 +406,9 @@ async def test_new_conversation_invalid_session_api_key(provider_handler_mock):
|
|||||||
assert 'Error authenticating with the LLM provider' in response.body.decode(
|
assert 'Error authenticating with the LLM provider' in response.body.decode(
|
||||||
'utf-8'
|
'utf-8'
|
||||||
)
|
)
|
||||||
assert 'STATUS$ERROR_LLM_AUTHENTICATION' in response.body.decode('utf-8')
|
assert RuntimeStatus.ERROR_LLM_AUTHENTICATION.value in response.body.decode(
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -575,7 +578,7 @@ async def test_new_conversation_with_provider_authentication_error(
|
|||||||
assert json.loads(response.body.decode('utf-8')) == {
|
assert json.loads(response.body.decode('utf-8')) == {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'message': 'auth error',
|
'message': 'auth error',
|
||||||
'msg_id': 'STATUS$GIT_PROVIDER_AUTHENTICATION_ERROR',
|
'msg_id': RuntimeStatus.GIT_PROVIDER_AUTHENTICATION_ERROR.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Verify that verify_repo_provider was called with the repository
|
# Verify that verify_repo_provider was called with the repository
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from litellm.exceptions import (
|
|||||||
|
|
||||||
from openhands.core.config.llm_config import LLMConfig
|
from openhands.core.config.llm_config import LLMConfig
|
||||||
from openhands.core.config.openhands_config import OpenHandsConfig
|
from openhands.core.config.openhands_config import OpenHandsConfig
|
||||||
|
from openhands.runtime.runtime_status import RuntimeStatus
|
||||||
from openhands.server.session.session import Session
|
from openhands.server.session.session import Session
|
||||||
from openhands.storage.memory import InMemoryFileStore
|
from openhands.storage.memory import InMemoryFileStore
|
||||||
|
|
||||||
@@ -64,6 +65,6 @@ async def test_notify_on_llm_retry(
|
|||||||
|
|
||||||
assert mock_litellm_completion.call_count == 2
|
assert mock_litellm_completion.call_count == 2
|
||||||
session.queue_status_message.assert_called_once_with(
|
session.queue_status_message.assert_called_once_with(
|
||||||
'info', 'STATUS$LLM_RETRY', ANY
|
'info', RuntimeStatus.LLM_RETRY, ANY
|
||||||
)
|
)
|
||||||
await session.close()
|
await session.close()
|
||||||
|
|||||||
Reference in New Issue
Block a user