Hooks
通过Hook(钩子),您可以在华为云码道IDE及插件的关键生命周期节点中插入自定义逻辑,实现功能扩展,而无需更改任何现有代码。
与依赖模型理解的Prompt指令不同,Hooks的执行是确定性的,即一旦事件被触发,绑定的脚本将稳定、可靠地执行,不受模型理解偏差的影响。
约束与限制
| 限制类别 | 具体限制 |
|---|---|
| 功能限制 | 当前Hook脚本不支持实时加载,需要重新加载才可使用。 |
| 语言限制 | Hook脚本仅支持JavaScript/TypeScript语言。 |
| 外部依赖 | 如果使用外部依赖包,您需要在Hook所在的配置目录创建package.json文件,并声明需要使用的依赖。 |
支持的Hook事件
| 事件分类 | 事件名称 | 触发时机 | 可阻断 | 典型使用场景 | 说明 |
|---|---|---|---|---|---|
| 聊天消息 | chat.message | 用户发送消息后、进入处理流程前 | 是 | 聊天消息处理 | 收到新消息时触发,可修改消息内容和parts |
| 聊天参数 | chat.params | 每次调用LLM前,修改请求参数(temperature、topP、topK等) | 是 | 聊天参数修改 | 调用LLM前修改参数(temperature、topP、topK等) |
| 聊天请求头 | chat.headers | 每次调用LLM前,修改HTTP请求头 | 是 | 聊天请求头修改 | 调用LLM前修改请求头 |
| 聊天响应 | chat.response | LLM返回响应后,记录响应详情(tokens、cost、duration等) | 否 | 聊天压缩记录 | 记录聊天压缩事件,记录压缩前后的tokens |
| 聊天错误 | chat.error | LLM调用发生错误时 | 否 | 聊天结束记录 | 记录聊天结束事件,包含运行ID、tokens消耗等 |
| 聊天压缩 | chat.compression | 上下文压缩时,记录压缩前后的tokens数量 | 否 | 用户审批记录 | 记录用户是否同意某操作 |
| 聊天结束 | chat.finished | 聊天会话结束时,记录本次运行的tokens消耗和运行信息 | 否 | Turn结束记录 | 记录每个turn结束日志,包含持续时间、终止原因等 |
| 用户审批 | user.approval | 需要用户确认/审批操作时 | 是 | 聊天响应记录 | 记录LLM响应日志,包含finishReason、tokens、cost等 |
| Turn结束 | turn.end | 每个对话轮次结束时,记录持续时间、终止原因等 | 否 | 聊天错误记录 | 记录调用LLM错误日志 |
| 命令执行 | command.execute.before | 命令执行前,可修改命令的parts | 是 | 权限请求处理 | 处理权限请求,可设置允许、拒绝、询问 |
| 工具执行 | tool.execute.before | 工具执行前,可修改工具参数args | 是 | 命令执行前 | 命令执行前触发,可修改parts |
| 工具执行 | tool.execute.after | 工具执行后,可修改返回的title、output、metadata | 是 | 工具执行前 | 工具执行前触发,可修改参数args |
| 工具定义 | tool.definition | 工具定义发送给LLM前,可修改描述和参数 | 是 | 工具执行后 | 工具执行后触发,可修改返回的title、output、metadata |
| Shell环境 | shell.env | 获取Shell环境变量时,可添加或修改环境变量 | 是 | Shell环境变量 | 获取或修改Shell环境变量 |
| 权限请求 | permission.ask | 需要权限验证时,可设置allow/deny/ask | 是 | 消息转换 | 转换聊天消息列表 |
| 消息转换 | experimental.chat.messages.transform | 消息列表发送给LLM前,可自定义转换 | 是 | 系统消息转换 | 转换系统消息 |
| 系统消息转换 | experimental.chat.system.transform | 系统提示词发送前,可自定义转换 | 是 | 会话压缩 | 会话压缩前触发,可自定义压缩提示词 |
| 会话压缩 | experimental.session.compacting | 上下文压缩开始前,可自定义压缩提示词 | 是 | 文本完成 | 文本补全完成时触发 |
| 文本补全 | experimental.text.complete | 文本补全完成时,可修改输出文本 | 是 | 工具定义修改 | 修改工具定义(描述和参数) |
| 配置加载 | config | 应用启动配置加载时 | 否 | 配置加载 | 配置加载时触发 |
| 通用事件 | event | 订阅所有Bus事件时触发 | 否 | 事件处理 | 通用事件处理 |
插件及依赖文件存放路径
插件是一个JavaScript/TypeScript模块,通过导出插件函数来工作。每个函数接收一个上下文对象,并返回对应的Hook对象。如果插件文件中需要使用外部包,必须在配置目录中创建package.json文件并配置所需的依赖项。华为云码道在启动时会自动安装这些依赖项。
| 插件类型 | 插件文件存放路径 | 插件依赖文件存放路径 | 说明 |
|---|---|---|---|
| 项目级 | 当前项目根目录./.codeartsdoer/plugin或./.codeartsdoer/plugins | 当前项目根目录./.codeartsdoer/ | 仅对当前项目有效。 |
| 个人级 | 本地~/.codeartsdoer/plugin或 ./.codeartsdoer/plugins | 本地~/.codeartsdoer/ | 对当前用户下所有项目均有效。 |
快速入门
以下通过一个简单示例,演示如何对用户输入中的密码、密钥等敏感信息进行过滤,有效防止数据泄露。
- 创建插件文件存放目录。在项目根目录“./.codeartsdoer”下新建一个目录plugin。 图1 新建plugin目录
- 创建脚本。在plugin目录下,新建文件sensitiveInfoFilteringPlugin.ts,在插件文件中编写Hook逻辑代码,并保存文件。
//导入Plugin类型,用于定义插件 import type { Plugin } from "@opencode-ai/plugin" //导出插件实现,Plugin是一个异步函数,接收插件输入参数并返回Hooks对象 export const SensitiveInfoFilteringPlugin: Plugin = async ({}) => { //返回一个Hooks对象,包含各种钩子函数 return { //监听工具执行后的钩子,在工具执行完成后触发 "tool.execute.after": async ( //输入参数:包含工具名称、会话ID、调用ID、推理ID等信息 input: { tool: string; sessionID: string; callID: string; args: any }, //输出参数:包含工具执行结果的标题、输出内容、元数据 output: { title: string output: string metadata: any }, ) => { //判断是否为读取文件操作(tool 名称为 "read")且输出内容存在 if (input.tool === "read" && output.output) { //敏感信息匹配模式(支持JSON和其他常见格式) //匹配"password": "value"或password: value格式 //第一个正则:匹配JSON 格式,如 "password": "123456" //第二个正则:匹配普通键值对格式,如password=123456或password:123456 const sensitivePatterns = [ //匹配JSON格式的敏感字段:双引号包裹的键名 + 冒号 + 双引号包裹的值 /"(password|passwd|pwd|api[_-]?key|secret|token|密钥|密码)"\s*:\s*"([^"]+)"/g, //匹配普通格式的敏感字段:键名 + 冒号或等号 + 不带引号的值 /(password|passwd|pwd|api[_-]?key|secret|token)\s*[:=]\s*([^"'\\\s]+)/gi, ] //将工具输出的内容赋值给局部变量content,方便后续处理 let content = output.output //遍历所有敏感信息匹配模式,逐一进行替换处理 for (const pattern of sensitivePatterns) { //使用replace方法进行替换,传入回调函数处理匹配结果 //match:完整匹配的字符串 //key:捕获组1,即敏感字段名(password、api_key等) //value:捕获组2,即敏感字段的值(如密码、密钥的實際内容) content = content.replace(pattern, (match, key, value) => { //如果匹配到敏感信息,将实际值替换为[敏感信息]占位符 return match.replace(value, "[敏感信息]") }) } //更新output对象中的output字段,将过滤后的内容写回 output.output = content } } } } - 重启IDE。
在华为云码道IDE工具左上角,单击“文件(F)”,选择“重启IDE”。因为当前Hook脚本不支持实时加载,所以需要重新启动IDE,脚本才可以正常使用。
- 验证效果。
在输入框中输入指令后,模型返回的结果中敏感信息已自动脱敏。
图2 模型返回效果
创建Hooks
- 选择对应的事件。请根据实际业务需求,从支持的Hook事件中匹配并选择对应的事件名称。
- 编写Hook脚本。
- 创建插件文件。
在插件及依赖文件存放路径目录下,新建“.ts”或“.js”文件。
- 编写Hook逻辑。
在Hook事件参考内容中,找到“// Hook implementations go here”注释位置,并在此处根据实际需求编写具体的Hook实现代码。
- 创建插件文件。
- (可选)配置依赖。如果插件文件中需要使用外部包,必须在插件及依赖文件存放路径的配置目录中创建package.json文件并配置所需的依赖项。
{ "dependencies": { "依赖包名1": "版本号", "依赖包名2": "版本号" } }package.json文件具体示例如下:
{ "dependencies": { "@opencode-ai/plugin": "*", "@aws-sdk/client-s3": "3.933.0", "@babel/core": "7.29.0", "@langfuse/otel": "4.5.1" } } - 重启IDE验证效果。
场景示例
以“删除文件前弹窗进行高危操作提示”为例,向您详细介绍如何使用Hook。本示例以创建级,plugin目录为例。
- 根据支持的Hook事件中场景描述,选择对应的事件。
删除文件会调用工具,一般是deleteFile工具,要在删除文件前弹窗,所以此处选择事件“tool.execute.before”。
- 编写hook脚本。
- 在项目根目录“./.codeartsdoer”下,新建一个目录plugin。 图3 创建plugin目录
- 在plugin目录下,新建文件deleteFileHintPlugin.ts。 图4 新建deleteFileHintPlugin.ts文件
- 编写脚本,并保存文件。
//导入OpenCode插件类型定义 import type {Plugin} from "@opencode-ai/plugin"; //导入Node.js子进程模块,用于执行系统命令 import { exec } from "child_process"; //导入util模块的promisify函数,用于将回调式函数转为Promise import { promisify } from "util"; //将exec回调函数转换为Promise形式的异步函数 const execAsync = promisify(exec); //定义并导出插件,接收client参数(用于与OpenCode交互) export const DeleteFileHintPlugin: Plugin = async ({ client }) => { //返回插件的hook集合 return { //监听工具执行前的钩子 "tool.execute.before": async ( //输入参数:工具名称、会话ID、调用ID、推理ID input: { tool: string; sessionID: string; callID: string; inferenceID?: string }, //输出参数:工具调用时的参数 output: { args: any } ) => { // 判断是否为删除文件操作 if (input.tool === "deleteFile") { //定义弹出警告框的消息内容 const message = `删除文件是高危操作,请谨慎执行!` //根据不同操作系统执行不同的命令 if (process.platform === "win32") { //Windows系统:使用PowerShell调用系统MessageBox const cmd = `powershell -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show('${message}', '警告', 'OK', 'Warning')"` await execAsync(cmd) } else if (process.platform === "darwin") { //macOS系统:使用osascript调用系统弹窗 const cmd = `osascript -e 'display alert "警告" message "${message}"'` await execAsync(cmd) } else if (process.platform === "linux") { //Linux系统:使用notify-send发送桌面通知 const cmd = `notify-send "警告" "${message}"` await execAsync(cmd) } } } } }
- 在项目根目录“./.codeartsdoer”下,新建一个目录plugin。
- 重启IDE验证效果。
- 在华为云码道IDE工具左上角,单击“文件(F)”,选择“重启IDE”。因为当前Hook脚本不支持实时加载,所以需要重新启动IDE,脚本才可以正常使用。
- 在输入框中输入“删除文件***”,客户端会弹出预警窗口。 其中,***为待删除文件的名称。图5 出现预警窗口