DevEco Code verify_ui 工具深度分析
基于 deveco-code 仓库源码分析(GitCode: openharmony-sig/deveco-code)
分析范围:packages/opencode/src/plugin/harmony-napi-dynamic-tools.ts、packages/opencode/src/tool/lib/harmony_napi.ts、packages/opencode/src/tool/lib/emulator_tools.json、packages/opencode/src/agent/agent.ts、packages/opencode/src/plugin/deveco-models.ts
目录
1. 概述
verify_ui 是 DevEco Code 中唯一一个使用多模态视觉模型的工具。它不是通过 OpenCode 标准 Tool.define() 机制注册的 TypeScript 工具,而是通过 NAPI Bridge 原生插件动态注册的工具。
它的核心职责是:接受自然语言描述的测试用例,在模拟器或真机上自动执行 UI 操作,并通过截图 + 多模态视觉模型验证界面是否符合预期。
2. 注册机制
verify_ui 的注册链路与其他 OpenCode 工具完全不同,它运行在 OpenCode 的 Plugin 系统之上,而非 Tool 系统。
2.1 注册链路
emulator_tools.json ← 工具定义层(名称、描述、JSON Schema)
│
▼
harmony-napi-dynamic-tools.ts ← Plugin 注册层(转换为 OpenCode Plugin tool)
│
▼
harmony_napi.ts ← NAPI Bridge 调用层(callHarmonyNapiTool)
│
▼
@deveco-codegenie/mcp-bridge ← 原生 addon(与 DevEco Studio 交互)
2.2 定义层:emulator_tools.json
文件:packages/opencode/src/tool/lib/emulator_tools.json
这个 JSON 文件包含 6 个 NAPI Bridge 工具的定义元数据。verify_ui 的定义片段:
{
"name": "verify_ui",
"description": "在提供自然语言描述的功能步骤之后...",
"inputSchema": {
"properties": {
"testPlan": { "type": "string" },
"bundleName": { "type": "string", "nullable": true },
"device": { "type": "string", "nullable": true },
"freshStart": { "type": "boolean", "default": false }
},
"required": ["testPlan"]
}
}
包含三部分:
- name:工具 ID,LLM 通过此 ID 调用
- description:工具描述,LLM 理解工具用途的入口
- inputSchema:JSON Schema 格式的参数规范,用于 LLM 生成参数和 Plugin 层校验
2.3 Plugin 注册层:harmony-napi-dynamic-tools.ts
文件:packages/opencode/src/plugin/harmony-napi-dynamic-tools.ts
这是核心的注册代码。它通过 OpenCode Plugin API 将 JSON 定义批量转换为可调用的工具。
关键代码(L337-376):
const HarmonyNapiDynamicToolsPlugin: Plugin = async (_input) => {
// 1. 从 emulator_tools.json 批量加载工具定义
const listed = normalizeToolList(emulatorTools);
// 2. 遍历每个工具,注册为 OpenCode Plugin tool
const tools = Object.fromEntries(
listed.map(({ name, description, inputSchema }) => {
const t = tool({
description: buildProxiedToolDescription(name, description),
args: {},
jsonSchema: buildToolJsonSchema(inputSchema),
async execute(args, ctx) {
// 对 verify_ui 做专属处理
if (name === 'verify_ui') {
// 解析多模态模型参数
const params = await resolveUIVerifyParams(worktree);
if (!params.baseURL || !params.apiKey || !params.modelName) {
return "工具调用失败。请将以下内容原文告知用户...";
}
}
// 路径遍历防护
validatePathParameters(payload, worktree);
// 注入 sessionId
if (name === 'verify_ui') payload.sessionId = ctx.sessionID;
// 调用原生桥接
const result = await callHarmonyNapiTool({ worktree, toolName: name, args: payload });
return textFromCallResult(result);
},
});
return [name, t];
})
);
return { tool: tools };
};
关键设计点:
| 设计 | 说明 |
|---|---|
| 批量注册 | 所有 6 个 NAPI 工具共享同一套注册逻辑 |
| verify_ui 专属分支 | execute 函数中有两处 if (name === 'verify_ui') 特殊处理 |
| Plugin API | 使用 @opencode-ai/plugin 的 tool() 函数,非 Tool.define() |
| JSON Schema 桥接 | buildToolJsonSchema() + jsonSchemaToEffectSchema() 将 JSON Schema 转为 Effect Schema 做运行时校验 |
2.4 与其他工具注册方式的对比
| 维度 | TypeScript 工具 | NAPI Bridge 工具 |
|---|---|---|
| 注册方式 | Tool.define("id", Effect.gen(...)) |
tool({ description, jsonSchema, execute }) |
| 注册位置 | registry.ts 的 builtin 列表 |
harmony-napi-dynamic-tools.ts 的 Plugin |
| 参数校验 | Effect Schema(编译时类型安全) | JSON Schema → Effect.Schema(运行时转换) |
| 实现语言 | TypeScript | @deveco-codegenie/mcp-bridge 原生二进制 |
| OAuth 门控 | 有(oh_knowledge) | 无(但依赖 DEVECO_HOME) |
| 权限控制 | 通过 agent.ts 权限体系 | 通过 agent.ts 权限体系(与 TS 工具一致) |
3. 暴露能力
3.1 给 LLM 暴露的接口
verify_ui(testPlan: string, bundleName?: string, device?: string, freshStart?: boolean)
↕ 返回
{
"id": "uuid-string",
"successPart": ["步骤一通过", "步骤二通过"],
"failPart": ["步骤三失败:未检测到预期元素"]
}
3.2 参数详解
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
testPlan |
string | 是 | — | 自然语言描述的测试用例计划,含每步操作和预期结果 |
bundleName |
string | 否 | 自动从项目获取 | 待测试的应用包名 |
device |
string | 否 | 单设备自动选中 | 设备名称(子串匹配)或序列号(如 127.0.0.1:5555) |
freshStart |
boolean | 否 | false | 是否在测试前重启应用 |
3.3 返回数据结构
返回数据由原生 Bridge 定义,包含三个核心字段:
- id:本次校验的唯一标识符。后续可用于
save_ui_screenshot保存截图、get_ui_verification_log查询日志 - successPart[]:通过的验证步骤描述列表
- failPart[]:失败的验证步骤描述列表(含失败原因)
3.4 使用场景
工具描述中声明了两个主场景:
- 开发时验证:通过自然语言描述测试用例,自动执行 UI 操作,验证应用功能
- 获取运行时日志:在执行 UI 操作的同时,收集设备日志,帮助定位问题
4. 工作原理
4.1 完整执行流程
┌─────────────────────────────────────────────────────────────┐
│ 阶段一:多模态模型路由(resolveUIVerifyParams) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ ① 读取 deveco.jsonc 的 agent.ui_verification.model │ │
│ │ ② 解析 providerID/modelID → baseURL/apiKey/modelName │ │
│ │ ③ 失败 → 尝试环境变量 UI_VERIFY_* │ │
│ │ ④ 失败 → 尝试华为内置通道(OAuth 登录态) │ │
│ │ ⑤ 全部失败 → 返回不可用提示 │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ 阶段二:NAPI Bridge 初始化(ensureInitialized) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ ① 验证 DEVECO_HOME 存在 │ │
│ │ ② 创建日志目录 ~/.local/share/deveco/log/deveco-mcp/│ │
│ │ ③ 传入 worktree / devEcoHome / VL 模型参数 │ │
│ │ ④ bridge.init() 建立 HDC 连接通道 │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ 阶段三:设备操作执行(Native Bridge 内部) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ ① 解析自然语言 testPlan → 设备操作序列 │ │
│ │ ② 通过 HDC 部署/启动 HAP 到模拟器/真机 │ │
│ │ ③ 逐步骤执行: │ │
│ │ ├─ tap(x, y) — 基于 UI 树定位目标元素坐标并点击 │ │
│ │ ├─ swipe(x1,y1,x2,y2) — 滑动操作 │ │
│ │ ├─ keyevent(KEYCODE) — 按键操作 │ │
│ │ └─ screenshot() — 每步后截图 │ │
│ │ ④ 收集设备运行日志(hilog) │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ 阶段四:截图 → VL 模型验证 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ ① 将截图 + 预期步骤描述发送到 VL 模型:(multimodal) │ │
│ │ { image: screenshot, prompt: "检查登录按钮是否显示" } │ │
│ │ ② VL 模型返回判断结果 │ │
│ │ ③ 记录 successPart / failPart │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ 阶段五:聚合返回 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 返回 { id, successPart[], failPart[] } 到 LLM │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
4.2 阶段一详解:多模态模型路由
这是整个工具最复杂的部分(harmony_napi.ts:49-128)。模型参数解析有三层 fallback:
/**
* resolveUIVerifyParams 函数本身是 async function,
* 内部通过 AppRuntime.runPromise 运行 Effect.gen 区块。
* 为清晰起见,下面的代码省略了外层 runPromise 包装,
* 只展示各层的核心逻辑。
*/
async function resolveUIVerifyParams(worktree: string) {
// Layer 1: deveco.jsonc 用户配置
// 在 AppRuntime.runPromise(Effect.gen(function* () { ... })) 内部执行:
// const config = yield* Config.Service
// const cfg = yield* config.get()
// const modelStr = cfg.agent?.["ui_verification"]?.model
// → 解析为 { providerID: "myprovider", modelID: "qwen3-vl-plus" }
// → 查找 Provider 获取 baseURL + apiKey
// Layer 2: 环境变量(快速配置)
if (process.env.UI_VERIFY_BASE_URL && ...) {
return { baseURL, apiKey, modelName }
}
// Layer 3: 华为内置通道(登录后直接可用)
// 通过 Auth.Service 获取 deveco OAuth token
// → 请求 /sse/codeGenie/maas/v2/no-stream
// → getTaskDefaultModelMap()["ui_verification"] ?? "Qwen3_VL_235B_A22B_Instruct"
// 全部失败 → 返回 null
return { baseURL: null, apiKey: null, modelName: null }
}
4.3 阶段二详解:NAPI Bridge 初始化
async function ensureInitialized(worktree: string) {
// 门控:避免重复初始化
gate = gate.then(async () => {
if (bound === worktree) return // 相同目录跳过
await runInit(worktree)
})
await gate
}
async function runInit(worktree: string) {
const devecoHome = await findDevEcoHome()
// 1. 创建日志目录
const logDir = path.join(XDG_DATA_HOME, 'deveco', 'log', 'deveco-mcp')
// 2. 解析 VL 模型参数
const { baseURL, apiKey, modelName } = await resolveUIVerifyParams(worktree)
// 3. 初始化原生桥接(建立 HDC 连接)
await bridge.init(logDir, worktree, devecoHome, baseURL, apiKey, modelName)
}
关键设计:
- Gate 互斥:串行化初始化,避免并发竞争
- Worktree 绑定:缓存已初始化的 worktree,切换项目时自动重初始化
- Token 过期检测:OAuth token 过期时自动刷新并重新初始化
4.4 阶段三详解:设备操作(Native Bridge 内部)
这部分逻辑在 @deveco-codegenie/mcp-bridge 原生二进制中(闭源)。从其行为可推断内部实现:
- 自然语言解析:将
"点击登录按钮 → 输入 admin → 点击确定"分解为步骤序列 - 元素定位:通过
get_app_ui_tree获取当前 UI 树(XML 结构),通过 UI 分析找到目标元素坐标 - 操作注入:通过 HDC 向设备注入触摸事件
- 截图采集:通过 HDC shell 调用
/system/bin/snapshot_display或类似接口截屏
4.5 阶段四详解:VL 模型验证
验证通过 HTTP 请求实现,请求格式(基于 DEVECO_DEFAULTS.provider.api 推断):
POST /sse/codeGenie/maas/v2/no-stream
Authorization: Bearer {OAuth_access_token}
Content-Type: application/json
{
"model": "Qwen2.5-VL-72B",
"messages": [
{
"role": "user",
"content": [
{ "type": "image_url", "image_url": { "url": "data:image/png;base64,..." } },
{ "type": "text", "text": "此截图中是否显示了预期的错误提示信息?" }
]
}
]
}
VL 模型的判断结果是二元的——通过/不通过——附带原因说明。
注:上例中的
"Qwen2.5-VL-72B"是静态回退模型名。登录用户的实际调用可能使用华为云端动态下发的"Qwen3_VL_235B_A22B_Instruct"(详见5.2 三层模型来源详解)。
5. 依赖的模型分析
5.1 模型来源全貌
verify_ui 依赖的多模态模型有 三层来源,并非只有一个静态默认值:
┌──────────────────────────────┐
│ 用户配置 (deveco.jsonc) │
│ agent.ui_verification.model │ ← 最高优先级
└──────────┬───────────────────┘
│ 未配置
┌──────────▼───────────────────┐
│ 华为云端动态下发 │
│ GET /codeGenie/modelConfig │
│ → 服务器返回可用模型列表 │
│ → 含 task_default_model_map │ ← 次高优先级
│ → 可指定比静态默认更新的模型 │
└──────────┬───────────────────┘
│ API 失败或无网络
┌──────────▼───────────────────┐
│ 静态回退 (deveco-models.ts) │
│ Qwen2.5-VL-72B (硬编码) │ ← 兜底
└──────────────────────────────┘
5.2 三层模型来源详解
第一层:用户配置(优先级最高)
用户可在 deveco.jsonc 中指定任意兼容 OpenAI API 的多模态模型:
{
"agent": {
"ui_verification": {
"mode": "subagent",
"model": "myprovider/qwen3-vl-plus",
"hidden": true,
}
}
}
第二层:华为云端动态下发
登录后,DevEco Code 启动时调用 API 获取可用模型列表:
const API_ENDPOINT = `https://cn.devecostudio.huawei.com/codeGenie/modelConfig?localVersion=0&pluginVersion=CLI.${InstallationVersion}`
async function fetchModelsFromAPI(accessToken: string) {
// Bearer token 认证
// 返回模型列表 + task_default_model_map
}
API 返回的 task_default_model_map 中可能指定更新的 VL 模型用于 ui_verification。如果返回了,则登录用户的 verify_ui 实际使用云端指定的模型,而非静态默认。
第三层:静态回退(deveco-models.ts)
静态默认配置中只定义了一个 VL 模型:
export const DEVECO_DEFAULTS = {
provider: {
api: "https://cn.devecostudio.huawei.com/sse/codeGenie/maas/v2",
models: {
"glm-5": {
// 对话模型,纯文本,用于日常代码生成
modalities: { input: ["text"], output: ["text"] },
},
"Qwen2.5-VL-72B": {
// 唯一的默认 VL 模型
limit: { context: 32768, output: 8192 },
modalities: { input: ["text", "image"], output: ["text"] },
},
},
},
taskDefaultModelMap: {
ui_verification: "Qwen2.5-VL-72B", // ← verify_ui 的静态默认
blacklist: "Qwen2.5-VL-72B", // ← 同时被列入黑名单
},
}
注意:
Qwen2.5-VL-72B同时出现在ui_verification任务映射和blacklist中。blacklist的作用是从 API 下发的模型列表中排除被屏蔽的模型。这意味着如果云端 API 返回了包含 Qwen2.5-VL-72B 的模型列表,它会被过滤掉——静态默认只在 API 失败时生效。
5.3 模型对比
| 模型 | 参数规模 | 架构 | 来源 | 角色 | 何时生效 |
|---|---|---|---|---|---|
| Qwen2.5-VL-72B | 72B | Dense | 静态默认(deveco-models.ts) |
兜底 VL 模型 | API 失败 + 未用户配置 |
| Qwen3_VL_235B_A22B_Instruct | 235B (22B active) | MoE | 华为云端 API 动态下发 | 默认活跃 VL 模型 | API 返回且未用户配置 |
| 用户自定义 | 不限 | 不限 | 用户 deveco.jsonc 配置 | 用户自选 | 用户显式配置时 |
关键差异:
- Qwen2.5-VL-72B:Dense 架构,72B 全参数激活,推理成本较高,
context=32768 - Qwen3_VL_235B_A22B_Instruct:MoE 架构,235B 总参数但每次只激活 ~22B(A22B = Active 22 Billion),推理效率更高,且云端可能已做推理优化
5.4 模型能力要求
verify_ui 对多模态模型的核心要求:
| 能力 | 重要性 | 说明 |
|---|---|---|
| 截图理解 | 必须 | 能识别 UI 界面中的按钮、文本框、列表等元素 |
| 状态判断 | 必须 | 能判断某个元素是否显示/隐藏/可用/选中 |
| 文本 OCR | 必须 | 能识别截图中的文字(Toast、错误提示、文案) |
| 布局理解 | 推荐 | 能理解元素之间的空间关系("在标题下方"、"在右侧") |
| 动画理解 | 不要求 | 快照级别判断,不追踪动画过渡 |
5.5 第三方模型配置示例
// deveco.jsonc
{
"agent": {
"ui_verification": {
"mode": "subagent",
"model": "myprovider/qwen3-vl-plus", // 格式: provider-name/model-name
"hidden": true,
}
}
}
// 配套 Provider 配置
{
"provider": {
"myprovider": {
"npm": "@ai-sdk/openai-compatible",
"name": "alibaba",
"options": {
"baseURL": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"apiKey": "your-api-key",
},
"models": {
"qwen3-vl-plus": {
"modalities": {
"input": ["text", "image"],
"output": ["text"],
},
},
},
}
}
}
6. 安全性设计
verify_ui 的 Plugin 注册层包含三层安全防护:
6.1 路径遍历防护(sanitizeFilePath)
function sanitizeFilePath(filePath: string, worktree: string): string {
const resolved = path.resolve(worktree, filePath);
// 使用 fs.realpathSync 解析符号链接
const realResolved = fs.realpathSync(resolved);
const realWorktree = fs.realpathSync(worktree);
// 检查路径是否在 worktree 范围内
if (!normalizedResolved.startsWith(worktreePrefix)) {
throw new Error(`Path traversal detected: ${filePath}`);
}
return realResolved;
}
对 log_path、dirname、filePath 等已知路径参数做严格校验,防止 ../../../etc/passwd 类攻击。
6.2 DEVECO_HOME 前置检查
if (!process.env.DEVECO_HOME?.trim()) {
throw new Error('DEVECO_HOME environment variable is not configured.');
}
DEVECO_HOME 是必需的环境变量,未设置时直接拒绝执行——避免使用错误的 DevEco Studio 路径操作设备。
6.3 参数 Schema 校验
const decoded = Schema.decodeUnknownExit(effectSchema)(value, { errors: "all" });
if (Exit.isFailure(decoded)) {
throw new Error(`Args validation failed: ${formatSchemaError(error)}`);
}
使用 Effect.Schema 对 LLM 传入的参数做严格类型校验,防止类型错误导致 native bridge 崩溃。
7. 权限与控制
verify_ui 的权限在 agent.ts 中分四层控制:
defaults(全局基准) → verify_ui: deny, save_ui_screenshot: deny, get_ui_verification_log: deny
│
├── build agent → verify_ui: ask (默认模式,每次询问用户)
│
├── spec-verify subagent → verify_ui: allow (SDD 验证阶段,自动授权)
│
└── plan agent → 保持 deny (规划模式禁止调用)
权限设计的三层模型:
- deny:完全禁止,LLM 看不到此工具
- ask:每次调用前询问用户确认
- allow:自动授权,直接执行
8. 配套工具链
verify_ui 不是孤立存在的,它属于一个配套工具组:
| 工具 ID | 角色 | 与 verify_ui 的关系 |
|---|---|---|
verify_ui |
执行器 | 核心:执行测试计划并验证 |
save_ui_screenshot |
持久化 | 用 verify_ui 返回的 id 保存截图到磁盘 |
get_ui_verification_log |
诊断 | 用 verify_ui 返回的 id 查询设备日志 |
start_app |
前置 | 确保应用已启动(verify_ui 内部也可能自动调用) |
build_project |
前置 | 确保最新编译产物可用 |
在 spec-verify subagent 中,典型工作流是:
build_project (构建) → start_app (部署启动) → verify_ui (验证)
↕ 失败重试 ↕ 失败重试
fix code fix code
↕ ↕
rebuild → re-verify → 最多 3 次 re-verify → 最多 3 次
9. 限制与边界
| 限制 | 说明 |
|---|---|
| 操作类型 | 仅支持点击、滑动、按键。不支持长按、手势缩放、多点触控 |
| 动态内容 | 基于截图快照判断,无法检测动画过渡、帧率、滚动惯性等动态效果 |
| 设备依赖 | 需要 HDC 连接的模拟器或真机,无设备时不工作 |
| 模型依赖 | 需要多模态视觉模型。未配置模型(且未华为登录)时返回不可用提示 |
| 网络依赖 | 华为内置通道需要稳定的互联网连接(调用华为云 API) |
| DEVECO_HOME | 需要安装 DevEco Studio 并配置 DEVECO_HOME 环境变量 |
| 平台限制 | 仅支持 Windows 和 macOS(DevEco Studio 限制) |
9.1 工作区描述中的自述限制
工具 description 明确告知 LLM 这些限制:
注意:目前仅支持点击、滑动和按键操作;目前基于截图进行判断,无法判断动画等动态内容。
10. 总结
10.1 核心链路一句话
verify_ui 是一个 NAPI Bridge 动态注册的 UI 验证工具,将自然语言测试计划通过 DevEco Studio 工具链转化为设备操作,再由多模态视觉模型基于截图判断结果。
10.2 关键问题速查
| 问题 | 回答 |
|---|---|
| verify_ui 是 TypeScript 工具还是 Native 工具? | Native 工具——注册为 OpenCode Plugin,实现在 @deveco-codegenie/mcp-bridge 闭源二进制的 NAPI addon 中 |
| 它怎么注册的? | 通过 harmony-napi-dynamic-tools.ts Plugin,用 tool({...}) API 批量注册(与 registry.ts 的 Tool.define 不同体系) |
| 需要什么前置条件才能工作? | DEVECO_HOME 配置 + 多模态模型配置(或华为 OAuth 登录) + HDC 设备连接 |
| 默认使用什么多模态模型? | Qwen2.5-VL-72B(通过华为云通道,登录后免费使用) |
| 能否用第三方模型? | 能,在 deveco.jsonc 的 agent.ui_verification.model 配置 provider/model 名称 |
| 它依赖的设备操作能力来自哪里? | 来自 @deveco-codegenie/mcp-bridge 原生二进制,通过 HDC 与 HarmonyOS 设备交互 |
| 它与 verify_ui 配套工具有哪些? | save_ui_screenshot(保存截图)、get_ui_verification_log(获取日志) |
| 安全方面做了什么? | 路径遍历防护(sanitizeFilePath)、DEVECO_HOME 前置检查、Effect Schema 参数校验 |
| 有没有权限控制? | 有,四层 deny/ask/allow 模型——spec-verify subagent 自动授权,plan agent 禁止,build agent 询问用户 |
10.3 架构设计原则
- Native 隔离:设备交互逻辑封装在闭源 NAPI addon 中,TypeScript 层只做路由和校验——降低安全风险,也保护了华为的 DevEco Studio 工具链实现细节
- 多模型回退:三层 fallback 确保用户总有路径可用——登录用户零配置、高级用户可定制、开发者可私有部署
- 插件化注册:通过 OpenCode Plugin 体系注册而非 Tool 体系,未来其他原生工具的加入不需要修改 OpenCode 核心代码
- 权限分层:不同 Agent 模式对 verify_ui 的权限差异设计,确保规划阶段不误执行、开发阶段需确认、验证阶段全自动

浙公网安备 33010602011771号