关于 openhands-api
openhands-api 是面向使用 OpenHands Cloud 和 Agent Server API 的开发者技能,涵盖会话操作、执行监控、任务委派及本地 Agent Canvas 工作流,并提供可复制的客户端示例。
主要功能
- 01OpenHands Cloud V1 REST API 工作流
- 02创建与检查会话
- 03异步启动和执行监控
- 04并行及后台会话委派
- 05沙盒生命周期管理与事件访问
- 06本地 Agent Canvas 后端会话
适用场景
- 01通过代码启动并监控 Cloud 会话
- 02委派独立或并行任务
- 03访问沙盒中的 Agent Server 端点
- 04检查本地 Agent Canvas 后端会话
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ openhands/ extensions --skill openhands-api安装后在 Claude Code 中输入/openhands-api确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.md.plugin/plugin.jsonreferences/example_prompt.mdREADME.mdTROUBLESHOOTING.mdwindows.mdscripts/openhands_api.pyopenhands_api.ts详细介绍
此技能介绍 OpenHands Cloud API(V1)、常用的 agent-server API,以及小巧、易于复制的客户端。
此技能中 shell 示例对应的 Windows PowerShell 写法见 references/windows.md。
本技能有意聚焦于常见的 OpenHands API 工作流:
- 默认使用 OpenHands Cloud(
https://app.all-hands.dev)。 - 使用
/api/v1/...下的 V1 app server REST API。 - 包含少量使用
X-Session-API-Key的 agent server 端点(位于沙盒内)。 - 涵盖 多会话委派模式:需要全新的上下文窗口或后台工作时,启动单独的 Cloud 会话。
- 涵盖 本地 Agent Canvas 后端会话:直接调用本地 agent server,以启动或检查会话。
何时使用此技能
在以下情况下使用此技能:
- 通过代码启动或检查 OpenHands Cloud 会话
- 通过轮询启动任务来监控异步启动
- 监控长时间运行的作业的执行状态
- 创建单独的 Cloud 会话以并行工作或执行后台任务
- 会话运行后访问沙盒 agent-server 端点
- 在本地 Agent Canvas 后端或本地 agent server 上启动或检查会话
身份验证
App server(Cloud)
使用 Bearer 身份验证:
- 标头:
Authorization: Bearer <OPENHANDS_CLOUD_API_KEY> - 首选环境变量:
OPENHANDS_CLOUD_API_KEY - 向后兼容的环境变量:
OPENHANDS_API_KEY
Agent server(沙盒内)
使用会话身份验证:
- 标头:
X-Session-API-Key: <session_api_key>
如何获取 agent_server_url 和 session_api_key:
- 通过 app server(Bearer 身份验证)启动或获取 app 会话,例如:
POST /api/v1/app-conversations- 或
GET /api/v1/app-conversations?ids=<conversation_id>
- 在返回的 JSON 中查找沙盒/运行时连接字段(不同部署/版本中的字段名称略有差异)。常见模式:
- 包含
agent_server_url(或类似字段)的沙盒对象 - 类似
session_api_key的会话密钥
- 包含
- 使用这些值直接调用 agent server:
- 基础地址:
{agent_server_url}/api/... - 标头:
X-Session-API-Key: <session_api_key>
- 基础地址:
示例(常见字段名称;请根据你的部署调整):
# using the minimal Python client (`OpenHandsAPI`)
conv = api.app_conversation_get(app_conversation_id)
session_api_key = conv.get("session_api_key")
conversation_url = conv.get("conversation_url", "")
# `conversation_url` often looks like: https://<runtime-host>/api/conversations/<id>
agent_server_url = conversation_url.rsplit("/api/conversations", 1)[0]
如果会话记录中没有这些字段,请列出/搜索沙盒(GET /api/v1/sandboxes/search),并使用会话所引用的沙盒来查找 agent server URL 和会话密钥。
本地 Agent Canvas 后端
仅在本地开发 Agent Canvas / agent-server 时使用本地后端流程,例如 agent-canvas、agent-canvas --backend-only,或入口为 http://localhost:8000 的 npm run dev。此流程会通过 X-Session-API-Key 直接调用 Agent 服务。这不是自动化流程,与通过 POST /api/v1/app-conversations 委派给 OpenHands Cloud 的方式不同;后者使用 Bearer 身份验证调用 Cloud 应用 API,并可能返回异步启动任务记录。
Agent Canvas 在本地运行时,如果设置了 LOCAL_BACKEND_API_KEY,启动器会使用该密钥;否则会在 ~/.openhands/agent-canvas/api-key.txt 生成并持久化会话 API 密钥。设置 OH_SESSION_API_KEY_PATH 可覆盖持久化密钥的路径。切勿打印、记录或粘贴实际密钥;在示例和脚本中使用命令替换或环境变量。
LOCAL_AGENT_SERVER_URL="${LOCAL_AGENT_SERVER_URL:-http://localhost:8000}"
SESSION_API_KEY="${LOCAL_BACKEND_API_KEY:-$(cat "${OH_SESSION_API_KEY_PATH:-$HOME/.openhands/agent-canvas/api-key.txt}")}"
创建后端对话前,检查本地服务:
curl -sS "${LOCAL_AGENT_SERVER_URL}/server_info" \
-H "X-Session-API-Key: ${SESSION_API_KEY}"
使用 POST /api/conversations 启动后端对话。请包含该后端所需的 Agent 设置和工作区。本地 Agent 服务调用需要显式指定 workspace,例如 {"kind": "LocalWorkspace", "working_dir": "/workspace"};而委派到 Cloud 应用对话时,使用的是 selected_repository 和 selected_branch 等 app-server 字段。如果从现有的 Agent Canvas 会话启动对话,请传递当前配置的设置或加密后的设置,而不要在脚本中硬编码机密信息。
CONVERSATION_JSON=$(curl -sS -X POST "${LOCAL_AGENT_SERVER_URL}/api/conversations" \
-H "X-Session-API-Key: ${SESSION_API_KEY}" \
-H "Content-Type: application/json" \
-d @- <<'JSON'
{
"agent": {
"kind": "Agent",
"llm": {
"model": "your-model-provider/your-model-name",
"api_key": "**********"
},
"tools": [
{"name": "terminal"},
{"name": "file_editor"},
{"name": "task_tracker"}
]
},
"workspace": {"kind": "LocalWorkspace", "working_dir": "/workspace"},
"initial_message": {
"content": [{"text": "Summarize the current workspace."}],
"run": true
}
}
JSON
)
CONVERSATION_ID=$(python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' <<<"${CONVERSATION_JSON}")
printf 'Conversation: %s/api/conversations/%s\n' "${LOCAL_AGENT_SERVER_URL}" "${CONVERSATION_ID}"
轮询状态并检查近期事件:
curl -sS "${LOCAL_AGENT_SERVER_URL}/api/conversations/${CONVERSATION_ID}" \
-H "X-Session-API-Key: ${SESSION_API_KEY}"
curl -sS "${LOCAL_AGENT_SERVER_URL}/api/conversations/${CONVERSATION_ID}/events/search?limit=20&sort_order=TIMESTAMP_DESC" \
-H "X-Session-API-Key: ${SESSION_API_KEY}"
如果 Agent Canvas UI 使用相同的基础 URL,浏览器路由为:
printf '%s/conversations/%s\n' "${LOCAL_AGENT_SERVER_URL}" "${CONVERSATION_ID}"
常见的 V1 应用服务端点
以下是最简客户端实现的主要端点:
GET /api/v1/users/me— 验证身份并查看当前账户GET /api/v1/app-conversations/search?limit=...— 列出近期对话GET /api/v1/app-conversations?ids=...— 按 id 获取对话记录(批量)GET /api/v1/app-conversations/count— 统计对话数量POST /api/v1/app-conversations— 启动新对话(创建沙箱)GET /api/v1/app-conversations/start-tasks?ids=...— 检查异步启动任务状态GET /api/v1/conversation/{app_conversation_id}/events/search?limit=...— 读取对话事件GET /api/v1/conversation/{app_conversation_id}/events/count— 统计事件数量GET /api/v1/sandboxes/search?limit=...— 列出沙箱POST /api/v1/sandboxes/{sandbox_id}/pause/.../resume— 管理沙箱生命周期GET /api/v1/app-conversations/{app_conversation_id}/download— 下载轨迹 zip
使用其他 Cloud 对话委派工作
如果你想要一个拥有全新上下文窗口的独立 OpenHands 对话,请使用 Cloud API。 以下情况适合使用此方式:
- 可独立运行的后台任务
- 并行调查或实现任务
- 需要长时间运行,且希望让当前对话保持专注的工作
- 特定于任务的上下文,例如一个对话构建组件,另一个对话运行测试
委派清单
开始委派的 Cloud 对话时:
- 编写自包含的任务说明。不要假设新对话了解当前对话的任何上下文。
- 包含仓库、分支、相关文件路径、约束条件和预期输出。
- 使用
POST /api/v1/app-conversations开始新对话。 - 轮询启动任务,直到
status为READY,并获得app_conversation_id。 - 通过
GET /api/v1/app-conversations?ids=...监控委派的对话。 - 分享或保存响应中的
conversation_ui_url。该链接会在 Agent Canvas 中打开对话。如果服务端未返回此链接,请使用<base_url>/canvas/conversations/<app_conversation_id>,其中<base_url>是你调用 API 的服务地址。切勿分享conversation_url;它是沙盒的 agent-server API,并非用户可以打开的页面。
最简 cURL 流程
curl -X POST "https://app.all-hands.dev/api/v1/app-conversations" \
-H "Authorization: Bearer ${OPENHANDS_CLOUD_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"initial_message": {
"content": [{"type": "text", "text": "Investigate flaky tests in tests/test_api.py. Report the root cause and propose a fix."}]
},
"selected_repository": "owner/repo"
}'
如果响应中尚未包含 app_conversation_id,请轮询启动任务:
curl -s "https://app.all-hands.dev/api/v1/app-conversations/start-tasks?ids=${START_TASK_ID}" \
-H "Authorization: Bearer ${OPENHANDS_CLOUD_API_KEY}"
然后检查执行状态并获取要分享的链接(conversation_ui_url):
curl -s "https://app.all-hands.dev/api/v1/app-conversations?ids=${APP_CONVERSATION_ID}" \
-H "Authorization: Bearer ${OPENHANDS_CLOUD_API_KEY}"
最简 Python 流程
from openhands_api import OpenHandsAPI
api = OpenHandsAPI() # prefers OPENHANDS_CLOUD_API_KEY
start = api.app_conversation_start(
initial_message=(
"Implement the requested dashboard component in src/dashboard.tsx. "
"Update any related tests and summarize the changes."
),
selected_repository="owner/repo",
selected_branch="main",
title="Dashboard component task",
)
ready = start
if not ready.get("app_conversation_id"):
ready = api.poll_start_task_until_ready(start["id"])
conversation_id = ready["app_conversation_id"]
print(f"Delegated conversation: {api.app_conversation_ui_url(conversation_id)}")
status = api.app_conversation_get(conversation_id)
print(status.get("sandbox_status"), status.get("execution_status"))
api.close()
并行处理建议
- 最好同时运行 5 个或更少的委派对话。
- 开始更多对话前,检查近期对话,并统计仍处于
execution_status == "running"状态的对话数。 - 如果已知对话 ID,可使用
GET /api/v1/app-conversations?ids=...批量查询指定对话。
示例:
items = api.app_conversations_search(limit=50).get("items", [])
running = [item for item in items if item.get("execution_status") == "running"]
if len(running) >= 5:
print("Wait for some delegated conversations to finish before starting more.")
启动任务与 app_conversation_id(常见问题)
在许多部署中,POST /api/v1/app-conversations 是异步的,会返回一个启动任务对象:
id是 start_task_idapp_conversation_id是你应在对话操作中使用的 ID,例如:GET /api/v1/app-conversations/{app_conversation_id}/downloadGET /api/v1/conversation/{app_conversation_id}/events/...
如果初始响应中没有 app_conversation_id,请通过以下接口获取:
GET /api/v1/app-conversations/start-tasks?ids=<start_task_id>
如果将 start_task_id 传给 /download,你会收到 404 Not Found。
常见 Agent 服务端点
这些端点针对 agent_server_url 运行(而非应用服务):
POST {agent_server_url}/api/bash/execute_bash_commandGET {agent_server_url}/api/file/download/<absolute_path>POST {agent_server_url}/api/file/upload/<absolute_path>(multipart)GET {agent_server_url}/api/conversations/{conversation_id}/events/searchGET {agent_server_url}/api/conversations/{conversation_id}/events/count
统计事件数(建议方式)
如果需要了解某个对话包含多少个事件,可以:
- 应用服务计数(正常工作时最快)
GET /api/v1/conversation/{app_conversation_id}/events/count
- Agent 服务计数(可靠的备用方案)
GET {agent_server_url}/api/conversations/{app_conversation_id}/events/count
- 轨迹压缩包备用方案(开销更大,但只需调用一次且能获取完整载荷)
GET /api/v1/app-conversations/{app_conversation_id}/download- 解压并统计
event_*.json文件
不要依赖最后一个事件的 id 来推断事件总数。
在 agent-server API 中,事件 ID 是 UUID(而非单调递增的整数)。
故障排除
常见问题及解决方案请参阅 TROUBLESHOOTING.md。
事件结构(用于调试)
以下接口返回的事件:
- 应用服务器:
GET /api/v1/conversation/{id}/events/search - Agent 服务器:
GET {agent_server_url}/api/conversations/{id}/events/search
……具有相同的高层结构。
每个事件通常包含:
id(UUID)timestampkindsource
常见的 kind 值:
| kind | source(典型值) | 关键字段(常见) | 用途 |
|---|---|---|---|
ActionEvent | agent | tool_name、tool_call_id、action | Agent 请求调用工具 |
ObservationEvent | environment | tool_name、tool_call_id、action_id、observation | 沙盒/环境生成的工具结果 |
MessageEvent | user / assistant | message(或类似字段) | 用户/助手聊天消息 |
ConversationStateUpdateEvent | environment | key、value | 状态转换/元数据 |
工具调用的关联方式:
ActionEvent.tool_call_id==ObservationEvent.tool_call_idObservationEvent.action_id==ActionEvent.id
示例(简化版):
{
"id": "<action-event-uuid>",
"kind": "ActionEvent",
"source": "agent",
"tool_name": "terminal",
"tool_call_id": "toolu_...",
"action": {"command": "ls"}
}
{
"id": "<observation-event-uuid>",
"kind": "ObservationEvent",
"source": "environment",
"tool_name": "terminal",
"tool_call_id": "toolu_...",
"action_id": "<action-event-uuid>",
"observation": {"exit_code": 0, "stdout": "..."}
}
调试单行命令(事件)
以下命令假设你查询的是应用服务器接口。查询 Agent 服务器时,请替换 URL 基址并使用 X-Session-API-Key。
快速打印时间线
curl -s "${BASE_URL:-https://app.all-hands.dev}/api/v1/conversation/${APP_CONVERSATION_ID}/events/search?limit=100" \
-H "Authorization: Bearer ${OPENHANDS_CLOUD_API_KEY:-$OPENHANDS_API_KEY}" \
-H "Accept: application/json" | \
python3 - <<'PY'
import json, sys
items = (json.load(sys.stdin) or {}).get("items", [])
for i, e in enumerate(items):
print(f"{i:04d} {e.get('timestamp','')} {e.get('source','')} {e.get('kind','')}")
PY
查找类似错误的事件
curl -s "${BASE_URL:-https://app.all-hands.dev}/api/v1/conversation/${APP_CONVERSATION_ID}/events/search?limit=200" \
-H "Authorization: Bearer ${OPENHANDS_CLOUD_API_KEY:-$OPENHANDS_API_KEY}" \
-H "Accept: application/json" | \
python3 - <<'PY'
import json, sys
items = (json.load(sys.stdin) or {}).get("items", [])
for i, e in enumerate(items):
if e.get("kind") == "ErrorEvent" or ("code" in e and "detail" in e):
print(i, e.get("kind"), e.get("code"), str(e.get("detail", ""))[:400])
PY
检查工具调用匹配情况(未匹配的操作/重复的观察结果)
curl -s "${BASE_URL:-https://app.all-hands.dev}/api/v1/conversation/${APP_CONVERSATION_ID}/events/search?limit=200" \
-H "Authorization: Bearer ${OPENHANDS_CLOUD_API_KEY:-$OPENHANDS_API_KEY}" \
-H "Accept: application/json" | \
python3 - <<'PY'
import json, sys
from collections import Counter
items = (json.load(sys.stdin) or {}).get("items", [])
action_ids = {e.get("id") for e in items if e.get("kind") == "ActionEvent"}
obs_action_ids = [e.get("action_id") for e in items if e.get("kind") == "ObservationEvent" and e.get("action_id")]
observed = set(obs_action_ids)
print("actions:", len(action_ids))
print("observations:", len(observed))
unmatched = action_ids - observed
print("unmatched actions:", list(unmatched)[:20] if unmatched else "none")
dups = [aid for aid, c in Counter(obs_action_ids).items() if c > 1]
print("duplicate observation action_ids:", list(dups)[:20] if dups else "none")
PY
快速入门(Python)
# Copy `skills/openhands-api/scripts/openhands_api.py` into your project (e.g. as `openhands_api.py`),
# then import it normally:
from openhands_api import OpenHandsAPI
api = OpenHandsAPI() # prefers OPENHANDS_CLOUD_API_KEY
me = api.users_me()
print(me)
recent = api.app_conversations_search(limit=5)
print(recent)
api.close()
CLI 示例
搜索对话:
export OPENHANDS_CLOUD_API_KEY="..."
python skills/openhands-api/scripts/openhands_api.py search-conversations --limit 5
从提示词文件启动对话:
python skills/openhands-api/scripts/openhands_api.py start-conversation \
--prompt-file skills/openhands-api/references/example_prompt.md \
--repo owner/repo \
--branch main
面向扩展此客户端的 AI Agent 的注意事项
- 优先使用带有较小
limit的.../search接口。 - 避免可能生成大量 API 调用的循环。
- 仅在收到请求时启动对话:这可能会创建沙盒并产生费用。
- 对于沙盒文件操作和命令执行,请使用带有
X-Session-API-Key的 Agent 服务器接口。
另请参阅:
skills/openhands-api/scripts/openhands_api.py- 最初的灵感来源客户端:
enyst/llm-playground→openhands-api-client-v1/scripts/cloud_api_v1.py - 故障排除内容和真实使用反馈 →
https://github.com/jpshackelford/.openhands/tree/main/skills/openhands-cloud-api
信息来源
此技能与当前 OpenHands API 文档和实现保持一致:
OpenHands/docs/openhands/usage/cloud/cloud-api.mdxOpenHands/docs/openhands/usage/agent-canvas/backend-setup/local.mdxOpenHands/docs/sdk/arch/agent-server.mdxOpenHands/docs/openhands/usage/api/v1.mdxOpenHands/OpenHands/openhands/app_server/v1_router.pyOpenHands/OpenHands/openhands/app_server/app_conversation/app_conversation_router.pyOpenHands/OpenHands/openhands/app_server/app_conversation/app_conversation_models.py