dashboard-nanobot/backend/bootstrap/app_runtime.py

483 lines
23 KiB
Python

from dataclasses import dataclass
from typing import Any, Dict
from clients.edge.errors import is_expected_edge_offline_error, log_edge_failure, summarize_edge_exception
from core.cache import cache
from core.database import engine, init_database
from core.settings import (
AGENT_MD_TEMPLATES_FILE,
DATABASE_ECHO,
DATABASE_ENGINE,
DATABASE_URL_DISPLAY,
DEFAULT_AGENTS_MD,
DEFAULT_BOT_SYSTEM_TIMEZONE,
DEFAULT_IDENTITY_MD,
DEFAULT_SOUL_MD,
DEFAULT_TOOLS_MD,
DEFAULT_USER_MD,
PROJECT_ROOT,
REDIS_ENABLED,
REDIS_PREFIX,
REDIS_URL,
TOPIC_PRESET_TEMPLATES,
TOPIC_PRESETS_TEMPLATES_FILE,
load_agent_md_templates,
load_topic_presets_template,
)
from providers.provision.edge import EdgeProvisionProvider
from providers.provision.local import LocalProvisionProvider
from providers.registry import ProviderRegistry
from providers.runtime.edge import EdgeRuntimeProvider
from providers.runtime.local import LocalRuntimeProvider
from providers.selector import get_provision_provider, get_runtime_provider
from providers.target import ProviderTarget, normalize_provider_target, provider_target_from_config, provider_target_to_dict
from providers.workspace.edge import EdgeWorkspaceProvider
from providers.workspace.local import LocalWorkspaceProvider
from services.app_lifecycle_service import AppLifecycleService
from services.bot_channel_service import BotChannelService
from services.bot_command_service import BotCommandService
from services.bot_config_state_service import BotConfigStateService
from services.bot_infra_service import BotInfraService
from services.bot_lifecycle_service import BotLifecycleService
from services.bot_message_service import BotMessageService
from services.bot_query_service import BotQueryService
from services.bot_runtime_snapshot_service import BotRuntimeSnapshotService
from services.dashboard_auth_service import DashboardAuthService
from services.image_service import ImageService
from services.node_registry_service import NodeRegistryService
from services.platform_activity_service import (
prune_expired_activity_events,
record_activity_event,
)
from services.platform_settings_service import (
get_chat_pull_page_size,
get_platform_settings_snapshot,
get_speech_runtime_settings,
)
from services.platform_usage_service import (
bind_usage_message,
create_usage_request,
fail_latest_usage,
finalize_usage_from_packet,
)
from services.provider_test_service import ProviderTestService
from services.runtime_event_service import RuntimeEventService
from services.runtime_service import RuntimeService
from services.skill_service import SkillService
from services.system_service import SystemService
from services.topic_runtime import publish_runtime_topic_packet
from services.workspace_service import WorkspaceService
from bootstrap.app_runtime_support import (
attach_runtime_services,
build_image_runtime_service,
build_speech_transcription_runtime_service,
build_system_runtime_service,
include_dashboard_api,
reconcile_image_registry,
register_provider_runtime,
)
@dataclass
class AppRuntimeAssembly:
dashboard_auth_service: DashboardAuthService
system_service: SystemService
app_lifecycle_service: AppLifecycleService
def assemble_app_runtime(
*,
app: Any,
logger: Any,
bots_workspace_root: str,
data_root: str,
docker_manager: Any,
config_manager: Any,
speech_service: Any,
bot_id_pattern: Any,
) -> AppRuntimeAssembly:
node_registry_service = NodeRegistryService()
skill_service = SkillService()
dashboard_auth_service = DashboardAuthService(engine=engine)
provider_registry = ProviderRegistry()
bot_infra_service = BotInfraService(
app=app,
engine=engine,
config_manager=config_manager,
node_registry_service=node_registry_service,
logger=logger,
bots_workspace_root=bots_workspace_root,
default_soul_md=DEFAULT_SOUL_MD,
default_agents_md=DEFAULT_AGENTS_MD,
default_user_md=DEFAULT_USER_MD,
default_tools_md=DEFAULT_TOOLS_MD,
default_identity_md=DEFAULT_IDENTITY_MD,
default_bot_system_timezone=DEFAULT_BOT_SYSTEM_TIMEZONE,
normalize_provider_target=normalize_provider_target,
provider_target_from_config=provider_target_from_config,
provider_target_to_dict=provider_target_to_dict,
resolve_provider_bundle_key=lambda target: provider_registry.resolve_bundle_key(target),
get_provision_provider=get_provision_provider,
read_env_store=lambda bot_id: bot_config_state_service.read_env_store(bot_id),
read_bot_runtime_snapshot=lambda bot: _read_bot_runtime_snapshot(bot),
normalize_media_list=lambda raw, bot_id: _normalize_media_list(raw, bot_id),
)
node_registry_service.register_node(bot_infra_service.local_managed_node())
app.state.node_registry_service = node_registry_service
_read_bot_config = bot_infra_service.read_bot_config
_write_bot_config = bot_infra_service.write_bot_config
_default_provider_target = bot_infra_service.default_provider_target
_read_bot_provider_target = bot_infra_service.read_bot_provider_target
_resolve_bot_provider_target_for_instance = bot_infra_service.resolve_bot_provider_target_for_instance
_clear_provider_target_override = bot_infra_service.clear_provider_target_override
_apply_provider_target_to_bot = bot_infra_service.apply_provider_target_to_bot
_local_managed_node = bot_infra_service.local_managed_node
_provider_target_from_node = bot_infra_service.provider_target_from_node
_node_display_name = bot_infra_service.node_display_name
_node_metadata = bot_infra_service.node_metadata
_serialize_provider_target_summary = bot_infra_service.serialize_provider_target_summary
_resolve_edge_client = bot_infra_service.resolve_edge_client
_resolve_edge_state_context = bot_infra_service.resolve_edge_state_context
_read_edge_state_data = bot_infra_service.read_edge_state_data
_write_edge_state_data = bot_infra_service.write_edge_state_data
_read_bot_resources = bot_infra_service.read_bot_resources
_migrate_bot_resources_store = bot_infra_service.migrate_bot_resources_store
_normalize_channel_extra = bot_infra_service.normalize_channel_extra
_read_global_delivery_flags = bot_infra_service.read_global_delivery_flags
_channel_api_to_cfg = bot_infra_service.channel_api_to_cfg
_get_bot_channels_from_config = bot_infra_service.get_bot_channels_from_config
_normalize_initial_channels = bot_infra_service.normalize_initial_channels
_parse_message_media = bot_infra_service.parse_message_media
_normalize_env_params = bot_infra_service.normalize_env_params
_get_default_system_timezone = bot_infra_service.get_default_system_timezone
_normalize_system_timezone = bot_infra_service.normalize_system_timezone
_resolve_bot_env_params = bot_infra_service.resolve_bot_env_params
_safe_float = bot_infra_service.safe_float
_safe_int = bot_infra_service.safe_int
_normalize_resource_limits = bot_infra_service.normalize_resource_limits
_sync_workspace_channels = bot_infra_service.sync_workspace_channels
_set_bot_provider_target = bot_infra_service.set_bot_provider_target
_sync_bot_workspace_via_provider = bot_infra_service.sync_bot_workspace_via_provider
_workspace_root = bot_infra_service.workspace_root
_cron_store_path = bot_infra_service.cron_store_path
_env_store_path = bot_infra_service.env_store_path
_clear_bot_sessions = bot_infra_service.clear_bot_sessions
_clear_bot_dashboard_direct_session = bot_infra_service.clear_bot_dashboard_direct_session
_ensure_provider_target_supported = bot_infra_service.ensure_provider_target_supported
_resolve_workspace_path = bot_infra_service.resolve_workspace_path
_calc_dir_size_bytes = bot_infra_service.calc_dir_size_bytes
_is_video_attachment_path = bot_infra_service.is_video_attachment_path
_is_visual_attachment_path = bot_infra_service.is_visual_attachment_path
bot_config_state_service = BotConfigStateService(
read_edge_state_data=_read_edge_state_data,
write_edge_state_data=_write_edge_state_data,
read_bot_config=_read_bot_config,
write_bot_config=_write_bot_config,
invalidate_bot_detail_cache=lambda *args, **kwargs: _invalidate_bot_detail_cache(*args, **kwargs),
env_store_path=_env_store_path,
cron_store_path=_cron_store_path,
normalize_env_params=_normalize_env_params,
)
def _write_env_store(bot_id: str, env_params: Dict[str, str]) -> None:
bot_config_state_service.write_env_store(bot_id, env_params)
local_provision_provider = LocalProvisionProvider(sync_workspace_func=_sync_workspace_channels)
local_runtime_provider = LocalRuntimeProvider(
docker_manager=docker_manager,
on_state_change=lambda *args, **kwargs: docker_callback(*args, **kwargs),
provision_provider=local_provision_provider,
read_runtime_snapshot=lambda *args, **kwargs: _read_bot_runtime_snapshot(*args, **kwargs),
resolve_env_params=_resolve_bot_env_params,
write_env_store=_write_env_store,
invalidate_bot_cache=lambda *args, **kwargs: _invalidate_bot_detail_cache(*args, **kwargs),
record_agent_loop_ready_warning=lambda *args, **kwargs: _record_agent_loop_ready_warning(*args, **kwargs),
safe_float=_safe_float,
safe_int=_safe_int,
)
local_workspace_provider = LocalWorkspaceProvider()
edge_provision_provider = EdgeProvisionProvider(
read_provider_target=_read_bot_provider_target,
resolve_edge_client=_resolve_edge_client,
read_runtime_snapshot=lambda *args, **kwargs: _read_bot_runtime_snapshot(*args, **kwargs),
read_bot_channels=_get_bot_channels_from_config,
read_node_metadata=_node_metadata,
)
edge_runtime_provider = EdgeRuntimeProvider(
read_provider_target=_read_bot_provider_target,
resolve_edge_client=_resolve_edge_client,
read_runtime_snapshot=lambda *args, **kwargs: _read_bot_runtime_snapshot(*args, **kwargs),
resolve_env_params=_resolve_bot_env_params,
read_bot_channels=_get_bot_channels_from_config,
read_node_metadata=_node_metadata,
)
edge_workspace_provider = EdgeWorkspaceProvider(
read_provider_target=_read_bot_provider_target,
resolve_edge_client=_resolve_edge_client,
read_node_metadata=_node_metadata,
)
local_provider_target = ProviderTarget(
node_id="local",
transport_kind="edge",
runtime_kind="docker",
core_adapter="nanobot",
)
register_provider_runtime(
app=app,
provider_registry=provider_registry,
local_provider_target=local_provider_target,
local_provision_provider=local_provision_provider,
local_runtime_provider=local_runtime_provider,
local_workspace_provider=local_workspace_provider,
edge_provision_provider=edge_provision_provider,
edge_runtime_provider=edge_runtime_provider,
edge_workspace_provider=edge_workspace_provider,
resolve_bot_provider_target_for_instance=_resolve_bot_provider_target_for_instance,
resolve_edge_client=_resolve_edge_client,
)
bot_runtime_snapshot_service = BotRuntimeSnapshotService(
engine=engine,
logger=logger,
docker_manager=docker_manager,
default_soul_md=DEFAULT_SOUL_MD,
default_agents_md=DEFAULT_AGENTS_MD,
default_user_md=DEFAULT_USER_MD,
default_tools_md=DEFAULT_TOOLS_MD,
default_identity_md=DEFAULT_IDENTITY_MD,
workspace_root=_workspace_root,
resolve_edge_state_context=_resolve_edge_state_context,
read_bot_config=_read_bot_config,
resolve_bot_env_params=_resolve_bot_env_params,
resolve_bot_provider_target_for_instance=_resolve_bot_provider_target_for_instance,
read_global_delivery_flags=_read_global_delivery_flags,
safe_float=_safe_float,
safe_int=_safe_int,
get_default_system_timezone=_get_default_system_timezone,
read_bot_resources=_read_bot_resources,
node_display_name=_node_display_name,
get_runtime_provider=get_runtime_provider,
invalidate_bot_detail_cache=lambda *args, **kwargs: _invalidate_bot_detail_cache(*args, **kwargs),
record_activity_event=record_activity_event,
)
_read_bot_runtime_snapshot = bot_runtime_snapshot_service.read_bot_runtime_snapshot
_serialize_bot = bot_runtime_snapshot_service.serialize_bot
_serialize_bot_list_item = bot_runtime_snapshot_service.serialize_bot_list_item
_refresh_bot_runtime_status = bot_runtime_snapshot_service.refresh_bot_runtime_status
_record_agent_loop_ready_warning = bot_runtime_snapshot_service.record_agent_loop_ready_warning
runtime_event_service = RuntimeEventService(
app=app,
engine=engine,
cache=cache,
logger=logger,
publish_runtime_topic_packet=publish_runtime_topic_packet,
bind_usage_message=bind_usage_message,
finalize_usage_from_packet=finalize_usage_from_packet,
workspace_root=_workspace_root,
parse_message_media=_parse_message_media,
)
_normalize_media_list = runtime_event_service.normalize_media_list
_persist_runtime_packet = runtime_event_service.persist_runtime_packet
_broadcast_runtime_packet = runtime_event_service.broadcast_runtime_packet
docker_callback = runtime_event_service.docker_callback
_cache_key_bots_list = runtime_event_service.cache_key_bots_list
_cache_key_bot_detail = runtime_event_service.cache_key_bot_detail
_cache_key_bot_messages = runtime_event_service.cache_key_bot_messages
_cache_key_bot_messages_page = runtime_event_service.cache_key_bot_messages_page
_serialize_bot_message_row = runtime_event_service.serialize_bot_message_row
_resolve_local_day_range = runtime_event_service.resolve_local_day_range
_cache_key_images = runtime_event_service.cache_key_images
_invalidate_bot_detail_cache = runtime_event_service.invalidate_bot_detail_cache
_invalidate_bot_messages_cache = runtime_event_service.invalidate_bot_messages_cache
_invalidate_images_cache = runtime_event_service.invalidate_images_cache
bot_command_service = BotCommandService(
read_runtime_snapshot=_read_bot_runtime_snapshot,
normalize_media_list=_normalize_media_list,
resolve_workspace_path=_resolve_workspace_path,
is_visual_attachment_path=_is_visual_attachment_path,
is_video_attachment_path=_is_video_attachment_path,
create_usage_request=create_usage_request,
record_activity_event=record_activity_event,
fail_latest_usage=fail_latest_usage,
persist_runtime_packet=_persist_runtime_packet,
get_main_loop=lambda app_state: getattr(app_state, "main_loop", None),
broadcast_packet=_broadcast_runtime_packet,
)
workspace_service = WorkspaceService()
runtime_service = RuntimeService(
command_service=bot_command_service,
resolve_runtime_provider=get_runtime_provider,
clear_bot_sessions=_clear_bot_sessions,
clear_dashboard_direct_session_file=_clear_bot_dashboard_direct_session,
invalidate_bot_detail_cache=_invalidate_bot_detail_cache,
invalidate_bot_messages_cache=_invalidate_bot_messages_cache,
record_activity_event=record_activity_event,
)
app_lifecycle_service = AppLifecycleService(
app=app,
engine=engine,
cache=cache,
logger=logger,
project_root=PROJECT_ROOT,
database_engine=DATABASE_ENGINE,
database_echo=DATABASE_ECHO,
database_url_display=DATABASE_URL_DISPLAY,
redis_enabled=REDIS_ENABLED,
init_database=init_database,
node_registry_service=node_registry_service,
local_managed_node=_local_managed_node,
prune_expired_activity_events=prune_expired_activity_events,
migrate_bot_resources_store=_migrate_bot_resources_store,
resolve_bot_provider_target_for_instance=_resolve_bot_provider_target_for_instance,
default_provider_target=_default_provider_target,
set_bot_provider_target=_set_bot_provider_target,
apply_provider_target_to_bot=_apply_provider_target_to_bot,
normalize_provider_target=normalize_provider_target,
runtime_service=runtime_service,
runtime_event_service=runtime_event_service,
clear_provider_target_overrides=bot_infra_service.clear_provider_target_overrides,
)
bot_query_service = BotQueryService(
cache=cache,
cache_key_bots_list=_cache_key_bots_list,
cache_key_bot_detail=_cache_key_bot_detail,
refresh_bot_runtime_status=_refresh_bot_runtime_status,
serialize_bot=_serialize_bot,
serialize_bot_list_item=_serialize_bot_list_item,
read_bot_resources=_read_bot_resources,
resolve_bot_provider_target=_resolve_bot_provider_target_for_instance,
get_runtime_provider=get_runtime_provider,
workspace_root=_workspace_root,
calc_dir_size_bytes=_calc_dir_size_bytes,
logger=logger,
)
bot_channel_service = BotChannelService(
read_bot_config=_read_bot_config,
write_bot_config=_write_bot_config,
sync_bot_workspace_via_provider=_sync_bot_workspace_via_provider,
invalidate_bot_detail_cache=_invalidate_bot_detail_cache,
get_bot_channels_from_config=_get_bot_channels_from_config,
normalize_channel_extra=_normalize_channel_extra,
channel_api_to_cfg=_channel_api_to_cfg,
read_global_delivery_flags=_read_global_delivery_flags,
)
bot_message_service = BotMessageService(
cache=cache,
cache_key_bot_messages=_cache_key_bot_messages,
cache_key_bot_messages_page=_cache_key_bot_messages_page,
serialize_bot_message_row=_serialize_bot_message_row,
resolve_local_day_range=_resolve_local_day_range,
invalidate_bot_messages_cache=_invalidate_bot_messages_cache,
get_chat_pull_page_size=get_chat_pull_page_size,
)
speech_transcription_service = build_speech_transcription_runtime_service(
data_root=data_root,
speech_service=speech_service,
get_speech_runtime_settings=get_speech_runtime_settings,
logger=logger,
)
image_service = build_image_runtime_service(
cache=cache,
cache_key_images=_cache_key_images,
invalidate_images_cache=_invalidate_images_cache,
docker_manager=docker_manager,
reconcile_image_registry_fn=lambda session: reconcile_image_registry(session, docker_manager=docker_manager),
)
provider_test_service = ProviderTestService()
system_service = build_system_runtime_service(
engine=engine,
cache=cache,
database_engine=DATABASE_ENGINE,
redis_enabled=REDIS_ENABLED,
redis_url=REDIS_URL,
redis_prefix=REDIS_PREFIX,
agent_md_templates_file=str(AGENT_MD_TEMPLATES_FILE),
topic_presets_templates_file=str(TOPIC_PRESETS_TEMPLATES_FILE),
default_soul_md=DEFAULT_SOUL_MD,
default_agents_md=DEFAULT_AGENTS_MD,
default_user_md=DEFAULT_USER_MD,
default_tools_md=DEFAULT_TOOLS_MD,
default_identity_md=DEFAULT_IDENTITY_MD,
topic_preset_templates=TOPIC_PRESET_TEMPLATES,
get_default_system_timezone=_get_default_system_timezone,
load_agent_md_templates=load_agent_md_templates,
load_topic_presets_template=load_topic_presets_template,
get_platform_settings_snapshot=get_platform_settings_snapshot,
get_speech_runtime_settings=get_speech_runtime_settings,
)
bot_lifecycle_service = BotLifecycleService(
bot_id_pattern=bot_id_pattern,
runtime_service=runtime_service,
refresh_bot_runtime_status=_refresh_bot_runtime_status,
resolve_bot_provider_target=_resolve_bot_provider_target_for_instance,
provider_target_from_node=_provider_target_from_node,
default_provider_target=_default_provider_target,
ensure_provider_target_supported=_ensure_provider_target_supported,
require_ready_image=image_service.require_ready_image,
sync_bot_workspace_via_provider=_sync_bot_workspace_via_provider,
apply_provider_target_to_bot=_apply_provider_target_to_bot,
serialize_provider_target_summary=_serialize_provider_target_summary,
serialize_bot=_serialize_bot,
node_display_name=_node_display_name,
invalidate_bot_detail_cache=_invalidate_bot_detail_cache,
record_activity_event=record_activity_event,
normalize_env_params=_normalize_env_params,
normalize_system_timezone=_normalize_system_timezone,
normalize_resource_limits=_normalize_resource_limits,
write_env_store=_write_env_store,
resolve_bot_env_params=_resolve_bot_env_params,
clear_provider_target_override=_clear_provider_target_override,
normalize_initial_channels=_normalize_initial_channels,
is_expected_edge_offline_error=is_expected_edge_offline_error,
summarize_edge_exception=summarize_edge_exception,
resolve_edge_client=_resolve_edge_client,
node_metadata=_node_metadata,
log_edge_failure=log_edge_failure,
invalidate_bot_messages_cache=_invalidate_bot_messages_cache,
logger=logger,
)
attach_runtime_services(
app=app,
bot_command_service=bot_command_service,
bot_lifecycle_service=bot_lifecycle_service,
app_lifecycle_service=app_lifecycle_service,
bot_query_service=bot_query_service,
bot_channel_service=bot_channel_service,
bot_message_service=bot_message_service,
bot_runtime_snapshot_service=bot_runtime_snapshot_service,
image_service=image_service,
provider_test_service=provider_test_service,
runtime_event_service=runtime_event_service,
speech_transcription_service=speech_transcription_service,
system_service=system_service,
workspace_service=workspace_service,
runtime_service=runtime_service,
)
include_dashboard_api(
app=app,
image_service=image_service,
provider_test_service=provider_test_service,
bot_lifecycle_service=bot_lifecycle_service,
bot_query_service=bot_query_service,
bot_channel_service=bot_channel_service,
skill_service=skill_service,
bot_config_state_service=bot_config_state_service,
runtime_service=runtime_service,
bot_message_service=bot_message_service,
workspace_service=workspace_service,
speech_transcription_service=speech_transcription_service,
app_lifecycle_service=app_lifecycle_service,
resolve_edge_state_context=_resolve_edge_state_context,
logger=logger,
)
return AppRuntimeAssembly(
dashboard_auth_service=dashboard_auth_service,
system_service=system_service,
app_lifecycle_service=app_lifecycle_service,
)