# ./openhands/server/listen.py
# ./containers/app/Dockerfile
# 等同于 openhands-app:/app/openhands/frontend/build ./frontend/build
docker cp openhands-app:/app/openhands /home/mrh/program/openhands/testm
uvicorn openhands.server.listen:app --host 0.0.0.0 --port 3000
启动了 web app , websocket_endpoint 利用 websocket 与控制是否启动 runtime
按浏览器会话来决定runtime任务,可能是为了让不同的客户端会话决定对同一个项目做不同的任务发起 event_stream = await session_manager.init_or_join_session
OpenHands/openhands/server/listen_socket.py
启动 agent_session await self.start_local_session(sid, session_init_data) - await session.initialize_agent(session_init_data)
OpenHands/openhands/server/session/manager.py
await self.agent_session.startOpenHands/openhands/server/session/session.pyself._start_thread,-await self._create_runtimeOpenHands/openhands/server/session/agent_session.py
await self._create_runtime ,runtime_cls = get_runtime_cls(runtime_name)
./openhands/server/session/agent_session.py
尝试连接到之前的容器,如果之前保留了会话,可根据容器名连接: async def connect(self): , await call_sync_from_async(self._attach_to_container)
如果容器不存在,则初始化一个新容器: await call_sync_from_async(self._init_container)
容器名来自前缀+sid self.container_name = CONTAINER_NAME_PREFIX + sid ,sid 来自 listen.py 会话或者 jwt_token sid = get_sid_from_token(jwt_token, config.jwt_secret) sid = str(uuid.uuid4())
./openhands/runtime/impl/docker/docker_runtime.py ./openhands/server/listen.py
在 runtime 内部,python -u -m openhands.runtime.action_execution_server 用 api server 来操作文件
添加所有插件 plugins_to_load.append(ALL_PLUGINS[plugin]())
加载插件 (self._init_plugin(plugin) for plugin in self.plugins_to_load),
初始化插件 await plugin.initialize(self.username)
./openhands/runtime/action_execution_server.py
在 runtime 内部,初始化 vscode 插件。 f'exec /openhands/.openvscode-server/bin/openvscode-server --host 0.0.0.0 --connection-token {self.vscode_connection_token} --port {self.vscode_port}\n'
port 地址是 port_mapping 在文件 eventstream_runtime.py中 self.container = self.docker_client.containers.run 时传参数
./openhands/runtime/plugins/vscode/init.py
self.container = self.docker_client.containers.run
OpenHands/openhands/runtime/action_execution_server.py
前端通过 WebSocket 发送文件更新请求到 listen_socket.py
websocket_endpoint 接收在 session_manager.py 中处理请求
init_or_join_session 获取或创建对应会话handle_message 方法解析消息类型agent_sessionagent_session.py 处理文件操作
process_file_operation 方法处理具体操作write_file 方法delete_file 方法文件操作通过 runtime 执行
action_execution_server.py 的 API 接口FileOperationsPlugin 处理具体文件操作文件同步机制
FileWatcherPlugin 处理route: http://sv-v2:3000/api/vscode-url
# ./openhands/runtime/impl/docker/docker_runtime.py
# VSCODE_HOST=sv-v2
vscode_host = os.environ.get('VSCODE_HOST', "localhost")
self._vscode_url = f'http://{vscode_host}:{self._host_port + 1}/?tkn={response_json["token"]}&folder={self.config.workspace_mount_path_in_sandbox}'
vscode server ./openhands/runtime/utils/runtime_build.py ./openhands/runtime/utils/runtime_templates/Dockerfile.j2
cd ~/program/openhands/testm
docker run -it --init -p 9806:3000 -v "$(pwd):/home/workspace:cached" gitpod/openvscode-server
# 从 docker 容器 openhands-app 内复制到宿主机
docker cp openhands-app:/app/openhands/core/cli.py /home/mrh/program/openhands
docker cp openhands-app:/app/openhands/core/cli.py ./openhands/core/cli.py
# 切换到指定版本
git checkout 0.15.0
# 挂载
- ./openhands/core/cli.py:/app/openhands/core/cli.py
python -m openhands.core.cli
# ./openhands/server/listen.py
# ./containers/app/Dockerfile
uvicorn openhands.server.listen:app --host 0.0.0.0 --port 3000
# eventstream_runtime.py:234
🎈 手动构建
命令行构建说明: ./containers/runtime/README.md 模板: ./openhands/runtime/utils/runtime_templates/Dockerfile.j2 文档说明: ./docs/i18n/zh-Hans/docusaurus-plugin-content-docs/current/usage/architecture/runtime.md 有关文件: openhands/runtime/utils/runtime_build.py
build_runtime_image 使用了构建器 runtime_builder=DockerRuntimeBuilder(docker.from_env()),
_build_sandbox_image
openhands/runtime/utils/runtime_build.py 创建构建命令:
buildx_cmd./openhands/runtime/builder/docker.py
指定文件夹需要有 Dockerfile template 文件。在 buildx_cmd.append(path) # must be last! 中看到 path 是一个文件夹路径,构建时会自动在这个文件夹内查找 Dockerfile template 文件。
在 _generate_dockerfile 中看到会渲染 Dockerfile template 文件
根据已有 Dockerfile.j2 来渲染这个文件,也就是将文件中 {{base_image}} 等双花括号的内容通过 jijia 改成正常的 Dockerfile 文件。
渲染的结果 Dockerfile 文件会保存到 --build_folder 同等目录下,
./openhands/runtime/utils/runtime_templates/Dockerfile.j2
🎈 启动时,如不存在则自动构建
启动时,if self.runtime_container_image is None 会运行 EventStreamRuntime build_runtime_image 构建 runtime 镜像
./openhands/runtime/impl/docker/docker_runtime.py ./openhands/runtime/utils/runtime_build.py ./openhands/runtime/utils/runtime_templates/Dockerfile.j2
self.runtime_container_image = self.config.sandbox.runtime_container_image 取决于 config.toml 文件的配置
./openhands/core/config/app_config.py
config.toml 从这里来 load_app_config
./openhands/server/listen.py
当你运行 runtime_build.py 后,调用 build_runtime_image_in_folder 函数。
base_image 、 build_from 、extra_deps 需要作为函数传参
build_from 是根据你的基准镜像来决定构建方式,如果不是默认镜像 nikolaik/python-nodejs 则 Dockerfile.j2 会条件渲染,安装有关的 Python 环境,如果 build_from 使用了默认的 Python 环境,则 Dockerfile.j2 会跳过安装,进而检查版本。
extra_deps 似乎没有被用到,运行 py 脚本也没有这个传参。可能考虑未来如果需要额外安装别的软件,可以额外添加这个 shell 代码。
OpenHands/openhands/runtime/utils/runtime_build.py
{% macro setup_base_system() %} 代码块是 jijia 模板的宏定义,以 {% endmacro %} 结尾,宏定义内的代码可以由Python渲染。代码内实现了常用工具的安装:wget curl sudo apt-utils git
{% macro setup_vscode_server() %} 实现了 openvscode-server 的安装。
{% macro install_dependencies() %} 宏是实现了 openhands 项目中 Python requirement 等软件的安装
{% if build_from_scratch %} 代码内可以看到,它决定了是否调用 {{ setup_base_system() }} {{ setup_vscode_server() }} ,也就是 build_from_scratch 变量决定了根据本地镜像和版本,是否复用构建或强制重新构建。
这些镜像名称的不同区分主要是为了实现以下几个核心用途:
| 镜像名称变量 | 实际镜像示例 | 用途 |
|-----------------------|--------------------------------------------------------------------------|----------------------------------------------------------------------|
| base_image | nikolaik/python-nodejs:python3.12-nodejs22 | 基础镜像,包含操作系统和基础工具。 |
| hash_image_name | docker.all-hands.dev/all-hands-ai/runtime:oh_v0.15_image_nikolaik_tag_python3.12-nodejs22 | 最终目标镜像,包含源代码和所有依赖项。 |
| lock_image_name | docker.all-hands.dev/all-hands-ai/runtime:oh_v0.15_lock_nikolaik_python3.12-nodejs22 | 基于依赖锁定文件生成的镜像,包含依赖项。 |
| versioned_image_name| docker.all-hands.dev/all-hands-ai/runtime:oh_v0.15_nikolaik_python3.12-nodejs22 | 基于基础镜像和 OpenHands 版本生成的镜像,包含基础环境和部分依赖项。 |
默认情况下是 build_from == BuildFromImageType.SCRATCH ,即 build_from_scratch == true ,build_from_versioned == false