与任意界面集成
你可以使用 Midscene 的 Agent 来控制任意界面,比如 IoT 设备、内部应用、车载显示器等,只需要实现一个符合 AbstractInterface 定义的 UI 操作类。
在实现了 UI 操作类之后,你可以获得 Midscene Agent 的全部特性:
TypeScript 的 GUI 自动化 Agent SDK,支持与任意界面集成
用于调试的 Playground
通过 yaml 脚本控制界面
通过 CLI 命令接入 Skills
演示和社区项目
我们已经为你准备了一个演示项目,帮助你学习如何定义自己的界面类。强烈建议你查看一下。
还有一些使用此功能的社区项目:
配置 AI 模型服务
下面以豆包 Seed 2.1 Turbo 为例:
export MIDSCENE_MODEL_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3"
export MIDSCENE_MODEL_API_KEY = "your-api-key"
export MIDSCENE_MODEL_NAME = "doubao-seed-2-1-turbo-260628"
export MIDSCENE_MODEL_FAMILY = "doubao-seed"
将 your-api-key 替换为你的 API Key。
如需使用千问(Qwen)、GLM、Gemini 或 GPT-5 等其他模型,请参考使用模型 API Key 。
全部配置项请参考模型配置 。
实现你自己的界面类
关键概念
AbstractInterface 类:一个预定义的抽象类,可以连接到 Midscene 智能体
动作空间 :描述可以在界面上执行的动作集合。这将影响 AI 模型如何规划和执行动作
步骤 1. 从 demo 项目开始
我们提供了一个演示项目,运行了本文档中的所有功能。这是最快的启动方式。
# 准备项目
git clone https://github.com/web-infra-dev/midscene-example.git
cd midscene-example/custom-interface
npm install
npm run build
# 运行演示
npm run demo
步骤 2. 实现你的界面类
定义一个继承 AbstractInterface 类的类,并实现所需的方法。
你可以从 ./src/sample-device.ts 文件中获取示例实现。让我们快速浏览一下。
import type { DeviceAction , Size } from '@midscene/core' ;
import { getMidsceneLocationSchema , z } from '@midscene/core' ;
import {
type AbstractInterface ,
defineAction ,
defineActionTap ,
defineActionInput ,
// ... 其他动作导入
} from '@midscene/core/device' ;
export interface SampleDeviceConfig {
deviceName ?: string ;
width ?: number ;
height ?: number ;
}
/**
* SampleDevice - AbstractInterface 的模板实现
*/
export class SampleDevice implements AbstractInterface {
interfaceType = 'sample-device' ;
private config : Required < SampleDeviceConfig >;
constructor (config : SampleDeviceConfig = {}) {
this .config = {
deviceName : config .deviceName || 'Sample Device' ,
width : config .width || 1920 ,
height : config .height || 1080 ,
};
}
/**
* 必需:截取屏幕截图并返回 base64 字符串
*/
async screenshotBase64 () : Promise < string > {
// TODO:实现实际的屏幕截图捕获
console .log ( '📸 Taking screenshot...' );
return 'data:image/png;base64,...' ; // 你的屏幕截图实现
}
/**
* 必需:获取界面尺寸
* 这里的宽高是指界面的逻辑尺寸(logical size),不需要考虑设备像素比(dpr)。defineActionTap 等动作得到的坐标也是基于这个逻辑尺寸的坐标系。你可以在动作实现中根据需要将逻辑坐标转换为物理坐标。
*/
async size () : Promise < Size > {
return {
width : this . config .width ,
height : this . config .height ,
};
}
/**
* 必需:定义 AI 模型的可用动作
*/
actionSpace () : DeviceAction [] {
return [
// 基础点击动作
defineActionTap ( async (param) => {
// TODO:实现在 param.locate.center 坐标的点击
await this .performTap ( param . locate .center[ 0 ] , param . locate .center[ 1 ]);
}) ,
// 文本输入动作
defineActionInput ( async (param) => {
// TODO:实现文本输入
await this .performInput ( param . locate .center[ 0 ] , param . locate .center[ 1 ] , param .value);
}) ,
// 自定义动作示例
defineAction ({
name : 'CustomAction' ,
description : '你的自定义设备特定动作' ,
paramSchema : z .object ({
locate : getMidsceneLocationSchema () ,
// ... 自定义参数
}) ,
call : async (param) => {
// TODO:实现自定义动作
} ,
}) ,
];
}
async destroy () : Promise < void > {
// TODO:清理资源
}
// 私有实现方法
private async performTap (x : number , y : number ) : Promise < void > {
// TODO:你的实际点击实现
}
private async performInput (x : number , y : number , text : string ) : Promise < void > {
// TODO:你的实际输入实现
}
}
需要实现的关键方法有:
screenshotBase64()、size():帮助 AI 模型获取界面上下文
actionSpace():一个由 DeviceAction 组成的数组,定义了在界面上可以执行的动作。AI 模型将使用这些动作来执行操作。Midscene 已为常见界面与设备提供了预定义动作空间,同时也支持定义任何自定义动作。
使用这些命令运行 Agent:
npm run build 重新编译 Agent 代码
npm run demo 使用 JavaScript 运行智能体
npm run demo:yaml 使用 yaml 脚本运行智能体
步骤 3. 使用 Playground 测试 Agent
为 Agent 附加一个 Playground 服务,即可在浏览器中测试你的 Agent。
import 'dotenv/config' ; // 从 .env 文件里读取 Midscene 环境变量
import { playgroundForAgent } from '@midscene/playground' ;
const sleep = (ms) => new Promise ((r) => setTimeout (r , ms));
// 实例化 device 和 agent
const device = new SampleDevice ();
await device .launch ();
const agent = new Agent (device);
// 启动 playground
const server = await playgroundForAgent (agent) .launch ();
// 关闭 Playground
await sleep ( 10 * 60 * 1000 );
await server .close ();
console .log ( 'Playground 已关闭!' );
步骤 4. 添加 Skill 支持(可选)
Agent Skills 让 AI 编程助手(如 Claude Code、Cline 等)可以通过 CLI 命令驱动你的自定义界面。了解更多请参考 Skills 文档 。
在你的 npm 包中添加一个 CLI 入口文件(如 ./src/cli.ts):
#!/usr/bin/env node
import { runSkillCLI } from '@midscene/core/skill' ;
import { SampleDevice } from './sample-device' ;
runSkillCLI ({
DeviceClass : SampleDevice ,
scriptName : 'my-device' ,
});
然后在 package.json 中配置 bin 字段:
{
"bin" : {
"my-device" : "./dist/cli.js"
}
}
发布后,AI 编程助手即可通过 npx my-device 命令来控制你的自定义界面。
关于 Skill 的编写规范和更多示例,请参考 midscene-skills 仓库。
步骤 5. 发布 npm 包,让你的用户使用它
./index.ts 文件已经导出了你的 Agent 与界面类。现在可以发布到 npm。
在 package.json 文件中填写 name 和 version,然后运行以下命令:
你的 npm 包的典型用法如下:
import 'dotenv/config' ; // 从 .env 文件里读取 Midscene 环境变量
import { playgroundForAgent } from '@midscene/playground' ;
const sleep = (ms) => new Promise ((r) => setTimeout (r , ms));
// 实例化 device 和 agent
const device = new SampleDevice ();
await device .launch ();
const agent = new Agent (device);
await agent .aiAct ( 'click the button' );
步骤 6. 在 Midscene CLI 和 YAML 脚本中调用你的类
编写一个包含 interface 字段的 yaml 脚本来调用你的类:
interface :
module : 'my-pkg-name'
# export: 'MyDeviceClass' # 如果是具名导出,使用该字段
config :
output : './data.json'
该配置等价于:
import MyDeviceClass from 'my-pkg-name' ;
const device = new MyDeviceClass ();
const agent = new Agent (device , {
output : './data.json' ,
customActions : [] , // optional additional custom DeviceAction list
});
YAML 的其他字段与自动化脚本 文档一致。
API 参考
AbstractInterface 类
import { AbstractInterface } from '@midscene/core' ;
AbstractInterface 是智能体控制界面的关键类。
以下是你需要实现的必需方法:
interfaceType: string:为界面定义一个名称,这不会提供给 AI 模型
screenshotBase64(): Promise<string>:截取界面的屏幕截图并返回带有 'data:image/ 前缀的 base64 字符串
size(): Promise<Size>:界面的大小,它是一个具有 width 和 height 属性的对 象
actionSpace(): DeviceAction[] | Promise<DeviceAction[]>:界面的动作空间,它是一个 DeviceAction 对象数组。在这里你可以使用预定义动作,或是自定义交互操作。
类型签名:
import type { DeviceAction , Size , UIContext } from '@midscene/core' ;
import type { ElementNode } from '@midscene/shared/extractor' ;
abstract class AbstractInterface {
// 必选
abstract interfaceType : string ;
abstract screenshotBase64 () : Promise < string >;
abstract size () : Promise < Size >;
abstract actionSpace () : DeviceAction [] | Promise < DeviceAction []>;
// 可选:生命周期/钩子
abstract destroy ? () : Promise < void >;
abstract describe ? () : string ;
abstract beforeInvokeAction ? (actionName : string , param : any ) : Promise < void >;
abstract afterInvokeAction ? (actionName : string , param : any ) : Promise < void >;
}
以下是你可以实现的可选方法:
destroy?(): Promise<void>:销毁
describe?(): string:界面描述,这可能会用于报告和 Playground,但不会提供给 AI 模型
beforeInvokeAction?(actionName: string, param: any): Promise<void>:在动作空间中调用动作之前的钩子函数
afterInvokeAction?(actionName: string, param: any): Promise<void>:在调用动作之后的钩子函数
动作空间(Action Space)
动作空间是界面上可执行动作的集合。AI 模型将使用这些动作来执行操作。所有动作的描述和参数模式都会提供给 AI 模型。
为了帮助你轻松定义动作空间,Midscene 为最常见的界面和设备提供了一组预定义的动作,同时也支持定义任意自定义动作。
以下是如何导入工具来定义动作空间:
import {
type ActionTapParam ,
defineAction ,
defineActionTap ,
} from "@midscene/core/device" ;
预定义的动作
这些是最常见界面和设备的预定义动作空间。你可以通过实现动作的调用方法将它们暴露给定制化界面。
你可以在这些函数的类型定义中找到动作的参数。
defineActionTap():定义点击动作。这也是 aiTap 方法的调用函数。
defineActionDoubleClick():定义双击动作
defineActionInput():定义输入动作。这也是 aiInput 方法的调用函数。这也是 aiInput 方法的调用函数。
defineActionKeyboardPress():定义键盘按下动作。这也是 aiKeyboardPress 方法的调用函数。
defineActionScroll():定义滚动动作。这也是 aiScroll 方法的调用函数。
defineActionDragAndDrop():定义拖放动作
defineActionLongPress():定义长按动作
defineActionSwipe():定义滑动动作
定义一个自定义动作
你可以使用 defineAction() 函数定义自己的动作。你也可以使用这种方式为 PuppeteerAgent 、AgentOverChromeBridge 和 AndroidAgent 定义更多动作。
API 签名:
import type { ExecutorContext } from "@midscene/core" ;
import { defineAction } from "@midscene/core/device" ;
defineAction (
{
name : string ,
description : string ,
paramSchema : z .ZodType < T > ;
planningFeedbackMaxLength ?: number | 'unlimited' ;
call: (
param : z . infer < z . ZodType < T >> ,
context ?: ExecutorContext ,
) => Promise <void> ;
}
)
name:动作的名称,AI 模型将使用此名称调用动作
description:动作 的描述,AI 模型将使用此描述来理解动作的作用。对于复杂动作,你可以在这里给出更详细的示例说明
paramSchema:动作参数的 Zod 模式,AI 模型将根据此模式帮助填充参数
planningFeedbackMaxLength:可选的 context.task.planningFeedback 字符上限。不填时使用 core 默认值 500;填数字时使用该 action 的上限;设为 'unlimited' 时,该 action 的反馈不做截断。
call:调用动作的函数,你可以从符合 paramSchema 的 param 参数中获取参数
context:可选的执行上下文。动作的返回值会写入 task.output(供你的 API 调用方和报告使用),不会 发给规划器。若想给下一轮规划传递反馈,请设置 context.task.planningFeedback;是否截断及字符上限由 planningFeedbackMaxLength 控制。
示例:
defineAction ({
name : 'MyAction' ,
description : 'My action' ,
paramSchema : z .object ({
name : z .string () ,
}) ,
call : async (param) => {
console .log ( param .name);
} ,
});