OpenHands作为可扩展的AI驱动软件开发代理平台,不仅提供了丰富的内置功能,还允许开发者构建自定义应用和扩展。通过基于OpenHands的核心能力,开发者可以创建针对特定领域或工作流的专用AI代理应用。本笔记将探讨OpenHands的可扩展架构,分析自定义应用的设计模式,并通过实践构建一个专门的代码重构助手应用。
OpenHands的可扩展架构主要包括以下核心扩展点:
专用工具型应用:
工作流集成应用:
领域特化应用:
企业定制应用:
根据README_CN.md中的信息,OpenHands支持多种运行方式和配置选项,这为自定义应用提供了基础:
明确应用目标:
功能范围规划:
技术架构设计:
基于README_CN.md中描述的OpenHands部署选项,自定义应用可以通过以下方式与OpenHands集成:
Docker容器化集成:
docker run -it --rm --pull=always \
-e CUSTOM_APP_CONFIG=/app/config.json \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ~/.openhands:/.openhands \
-v $(pwd):/workspace \
-p 3000:3000 \
my-custom-openhands-app:latest
API驱动集成:
插件式扩展:
我们将构建一个专注于代码重构的自定义OpenHands应用,具有以下特点:
应用架构包括:
#!/usr/bin/env python3
# refactor_assistant.py
# 基于OpenHands的代码重构助手应用
import os
import sys
import json
import subprocess
import argparse
from pathlib import Path
# 配置
CONFIG_DIR = Path.home() / ".refactor-assistant"
CONFIG_FILE = CONFIG_DIR / "config.json"
OPENHANDS_CONFIG = {
"model": "anthropic/claude-sonnet-4-20250514",
"tools": ["codebase_search", "read_file", "edit_file", "run_terminal_cmd"],
"systemPrompt": """你是一位代码重构专家,擅长使用设计模式和现代编程实践改进代码质量。
你的任务是分析代码并提出重构建议,然后帮助实施这些改进。
关注以下几点:
1. 代码可读性和可维护性
2. 设计模式的恰当应用
3. 解耦和单一职责原则
4. 测试覆盖和可测试性
5. 性能优化机会
始终保留代码的功能等价性,确保重构前后行为一致。"""
}
def ensure_config():
"""确保配置目录和文件存在"""
CONFIG_DIR.mkdir(exist_ok=True)
if not CONFIG_FILE.exists():
default_config = {
"api_key": "",
"editor": "code", # 默认使用VS Code
"workspace": "",
"refactor_patterns": {
"extract_method": "将这段代码提取为一个独立的方法,提高可读性和复用性",
"replace_conditional": "使用多态替代这些条件判断",
"introduce_parameter": "引入参数替代硬编码值",
"extract_class": "将相关功能提取到专门的类中"
}
}
with open(CONFIG_FILE, "w") as f:
json.dump(default_config, f, indent=2)
return load_config()
def load_config():
"""加载配置"""
with open(CONFIG_FILE, "r") as f:
return json.load(f)
def start_openhands(config):
"""启动定制化的OpenHands实例"""
# 准备环境变量
env = os.environ.copy()
if config.get("api_key"):
env["ANTHROPIC_API_KEY"] = config["api_key"]
# 准备OpenHands配置
temp_config = Path(CONFIG_DIR) / "openhands_config.json"
with open(temp_config, "w") as f:
json.dump(OPENHANDS_CONFIG, f)
# 构建Docker命令
workspace = config.get("workspace") or os.getcwd()
cmd = [
"docker", "run", "-it", "--rm", "--pull=always",
"-e", f"SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik",
"-e", "LOG_ALL_EVENTS=true",
"-e", f"CONFIG_FILE=/.openhands/openhands_config.json",
"-v", "/var/run/docker.sock:/var/run/docker.sock",
"-v", f"{CONFIG_DIR}:/.openhands",
"-v", f"{workspace}:/workspace",
"-p", "3000:3000",
"--add-host", "host.docker.internal:host-gateway",
"--name", "refactor-assistant",
"docker.all-hands.dev/all-hands-ai/openhands:0.47"
]
print(f"启动重构助手...\n工作目录: {workspace}")
subprocess.run(cmd, env=env)
def show_refactor_patterns(config):
"""显示可用的重构模式"""
print("可用的重构模式:")
patterns = config.get("refactor_patterns", {})
for i, (name, desc) in enumerate(patterns.items(), 1):
print(f"{i}. {name}: {desc}")
def add_refactor_pattern(config, name, description):
"""添加新的重构模式"""
if "refactor_patterns" not in config:
config["refactor_patterns"] = {}
config["refactor_patterns"][name] = description
with open(CONFIG_FILE, "w") as f:
json.dump(config, f, indent=2)
print(f"已添加重构模式: {name}")
def main():
"""主函数"""
parser = argparse.ArgumentParser(description="代码重构助手 - 基于OpenHands")
subparsers = parser.add_subparsers(dest="command", help="可用命令")
# 开始命令
start_parser = subparsers.add_parser("start", help="启动重构助手")
# 配置命令
config_parser = subparsers.add_parser("config", help="配置应用")
config_parser.add_argument("key", help="配置键")
config_parser.add_argument("value", help="配置值")
# 显示重构模式
patterns_parser = subparsers.add_parser("patterns", help="显示可用的重构模式")
# 添加重构模式
add_pattern_parser = subparsers.add_parser("add-pattern", help="添加重构模式")
add_pattern_parser.add_argument("name", help="模式名称")
add_pattern_parser.add_argument("description", help="模式描述")
args = parser.parse_args()
config = ensure_config()
if args.command == "start":
start_openhands(config)
elif args.command == "config":
config[args.key] = args.value
with open(CONFIG_FILE, "w") as f:
json.dump(config, f, indent=2)
print(f"已更新配置: {args.key} = {args.value}")
elif args.command == "patterns":
show_refactor_patterns(config)
elif args.command == "add-pattern":
add_refactor_pattern(config, args.name, args.description)
else:
parser.print_help()
if __name__ == "__main__":
main()
为了提供专门的重构体验,我们可以创建一个简单的HTML界面,与OpenHands API集成:
DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>代码重构助手title>
<style>
:root {
--primary: #007ACC;
--bg-dark: #1E1E1E;
--bg-light: #252526;
--text: #E8E8E8;
--border: #3C3C3C;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
background-color: var(--bg-dark);
color: var(--text);
height: 100vh;
display: flex;
flex-direction: column;
}
header {
background-color: var(--bg-light);
padding: 10px 20px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
}
main {
display: flex;
flex-grow: 1;
overflow: hidden;
}
.sidebar {
width: 250px;
background-color: var(--bg-light);
border-right: 1px solid var(--border);
padding: 10px;
overflow-y: auto;
}
.content {
flex-grow: 1;
display: flex;
flex-direction: column;
padding: 10px;
overflow: hidden;
}
.editor-container {
display: flex;
flex-grow: 1;
overflow: hidden;
margin-bottom: 10px;
}
.editor {
flex: 1;
border: 1px solid var(--border);
margin: 0 5px;
overflow: auto;
background-color: var(--bg-light);
}
.chat-container {
height: 200px;
border: 1px solid var(--border);
background-color: var(--bg-light);
display: flex;
flex-direction: column;
}
.chat-messages {
flex-grow: 1;
padding: 10px;
overflow-y: auto;
}
.chat-input {
display: flex;
border-top: 1px solid var(--border);
padding: 10px;
}
input, select, button, textarea {
background-color: var(--bg-dark);
color: var(--text);
border: 1px solid var(--border);
padding: 8px;
font-family: inherit;
}
button {
background-color: var(--primary);
cursor: pointer;
border: none;
}
button:hover {
opacity: 0.9;
}
textarea {
flex-grow: 1;
resize: none;
}
h3 {
margin-top: 10px;
margin-bottom: 8px;
}
ul {
list-style: none;
padding: 0;
margin: 0;
}
li {
padding: 8px 10px;
cursor: pointer;
border-radius: 4px;
}
li:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.active {
background-color: rgba(0, 122, 204, 0.3);
}
.diff-added {
background-color: rgba(40, 167, 69, 0.2);
}
.diff-removed {
background-color: rgba(220, 53, 69, 0.2);
}
style>
head>
<body>
<header>
<h2>代码重构助手h2>
<div>
<select id="pattern-select">
<option value="">选择重构模式...option>
select>
<button id="run-tests">运行测试button>
div>
header>
<main>
<div class="sidebar">
<h3>项目文件h3>
<ul id="file-list">
ul>
<h3>重构历史h3>
<ul id="history-list">
ul>
div>
<div class="content">
<div class="editor-container">
<div class="editor" id="original-code">
<pre>// 选择文件查看代码pre>
div>
<div class="editor" id="refactored-code">
<pre>// 重构后的代码将显示在这里pre>
div>
div>
<div class="chat-container">
<div class="chat-messages" id="chat-messages">
<div class="message system">欢迎使用代码重构助手!请选择文件并描述您想要的重构。div>
div>
<div class="chat-input">
<textarea id="message-input" placeholder="描述要进行的重构...">textarea>
<button id="send-button">发送button>
div>
div>
div>
main>
<script>
// API基础URL
const API_BASE = 'http://localhost:3000/api';
let currentSession = null;
let currentFile = null;
// 初始化函数
async function init() {
// 加载重构模式
await loadRefactorPatterns();
// 加载项目文件
await loadProjectFiles();
// 创建会话
await createSession();
// 事件监听
document.getElementById('send-button').addEventListener('click', sendMessage);
document.getElementById('pattern-select').addEventListener('change', applyPattern);
document.getElementById('run-tests').addEventListener('click', runTests);
}
// 加载重构模式
async function loadRefactorPatterns() {
try {
const response = await fetch('/.openhands/config.json');
const config = await response.json();
const patterns = config.refactor_patterns || {};
const select = document.getElementById('pattern-select');
Object.entries(patterns).forEach(([name, desc]) => {
const option = document.createElement('option');
option.value = name;
option.textContent = name;
option.title = desc;
select.appendChild(option);
});
} catch (error) {
console.error('加载重构模式失败:', error);
}
}
// 加载项目文件
async function loadProjectFiles() {
try {
const response = await fetch(`${API_BASE}/files`);
const files = await response.json();
const fileList = document.getElementById('file-list');
files.forEach(file => {
if (file.type === 'file') {
const li = document.createElement('li');
li.textContent = file.name;
li.addEventListener('click', () => loadFile(file.path));
fileList.appendChild(li);
}
});
} catch (error) {
console.error('加载项目文件失败:', error);
addMessage('系统', '加载项目文件失败,请确保API服务正在运行。');
}
}
// 创建会话
async function createSession() {
try {
const response = await fetch(`${API_BASE}/sessions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'anthropic/claude-sonnet-4-20250514'
})
});
const data = await response.json();
currentSession = data.sessionId;
console.log('会话已创建:', currentSession);
} catch (error) {
console.error('创建会话失败:', error);
addMessage('系统', '创建会话失败,请检查OpenHands服务状态。');
}
}
// 加载文件
async function loadFile(path) {
try {
const response = await fetch(`${API_BASE}/files/content?path=${encodeURIComponent(path)}`);
const content = await response.text();
document.getElementById('original-code').innerHTML = `${escapeHtml(content)}
`;
currentFile = path;
// 更新UI状态
document.querySelectorAll('#file-list li').forEach(li => {
li.classList.remove('active');
if (li.textContent === path.split('/').pop()) {
li.classList.add('active');
}
});
addMessage('系统', `已加载文件: ${path}`);
} catch (error) {
console.error('加载文件失败:', error);
addMessage('系统', '加载文件失败,请重试。');
}
}
// 发送消息
async function sendMessage() {
const input = document.getElementById('message-input');
const message = input.value.trim();
if (!message || !currentSession) return;
addMessage('用户', message);
input.value = '';
try {
const fileContext = currentFile ? `当前文件: ${currentFile}\n` : '';
const fullMessage = `${fileContext}${message}`;
const response = await fetch(`${API_BASE}/sessions/${currentSession}/messages`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
content: fullMessage
})
});
const data = await response.json();
const messageId = data.messageId;
// 等待响应
await waitForResponse(messageId);
} catch (error) {
console.error('发送消息失败:', error);
addMessage('系统', '发送消息失败,请重试。');
}
}
// 等待响应
async function waitForResponse(messageId) {
try {
while (true) {
const response = await fetch(`${API_BASE}/sessions/${currentSession}/messages/${messageId}`);
const data = await response.json();
if (data.status === 'completed') {
addMessage('助手', data.response);
// 检查是否有文件更改
if (currentFile) {
loadFile(currentFile);
}
break;
} else if (data.status === 'error') {
addMessage('系统', `错误: ${data.error || '未知错误'}`);
break;
}
// 等待一段时间再查询
await new Promise(resolve => setTimeout(resolve, 1000));
}
} catch (error) {
console.error('等待响应失败:', error);
addMessage('系统', '等待响应失败,请重试。');
}
}
// 应用重构模式
function applyPattern() {
const select = document.getElementById('pattern-select');
const pattern = select.options[select.selectedIndex];
if (!pattern.value || !currentFile) return;
const message = `请使用"${pattern.value}"模式重构当前文件。
${pattern.title}`;
document.getElementById('message-input').value = message;
sendMessage();
}
// 运行测试
async function runTests() {
if (!currentFile) {
addMessage('系统', '请先选择一个文件。');
return;
}
addMessage('系统', '正在运行相关测试...');
try {
const message = `请运行与${currentFile}相关的测试,验证重构是否保持了功能等价性。`;
const response = await fetch(`${API_BASE}/sessions/${currentSession}/messages`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
content: message
})
});
const data = await response.json();
await waitForResponse(data.messageId);
} catch (error) {
console.error('运行测试失败:', error);
addMessage('系统', '运行测试失败,请重试。');
}
}
// 添加消息到聊天区域
function addMessage(sender, content) {
const messages = document.getElementById('chat-messages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${sender.toLowerCase()}`;
const senderSpan = document.createElement('strong');
senderSpan.textContent = sender + ': ';
const contentSpan = document.createElement('span');
contentSpan.textContent = content;
messageDiv.appendChild(senderSpan);
messageDiv.appendChild(contentSpan);
messages.appendChild(messageDiv);
// 滚动到底部
messages.scrollTop = messages.scrollHeight;
}
// HTML转义
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// 初始化应用
document.addEventListener('DOMContentLoaded', init);
script>
body>
html>
要部署并使用这个定制的代码重构助手:
设置环境:
refactor_assistant.py
并添加执行权限refactor_ui.html
配置应用:
# 设置API密钥
./refactor_assistant.py config api_key sk-your-api-key
# 设置工作空间
./refactor_assistant.py config workspace /path/to/your/project
# 查看可用重构模式
./refactor_assistant.py patterns
# 添加自定义重构模式
./refactor_assistant.py add-pattern state_pattern "将条件逻辑转换为状态模式,使状态转换更明确"
启动应用:
./refactor_assistant.py start
使用流程:
构建自定义OpenHands应用是扩展AI代理能力的有效方式,可以针对特定领域或工作流创建专门化的工具。通过OpenHands的灵活架构,开发者可以在保留核心AI能力的同时,定制用户体验和功能集合,打造更适合特定场景的智能助手。
无论是专注于代码重构、架构审查、自动化测试还是其他开发任务,构建在OpenHands基础上的自定义应用都能充分利用其强大的代理能力和灵活的扩展性。随着对特定领域知识和工作流的深入优化,这些自定义应用可以成为开发团队的专属工具,显著提升特定场景下的开发效率和代码质量。