Hooks
通过Hook(钩子),您可以在华为云码道IDE及插件的关键生命周期节点中插入自定义逻辑,实现功能扩展,而无需更改任何现有代码。
与依赖模型理解的Prompt指令不同,Hooks的执行是确定性的,即一旦事件被触发,绑定的脚本将稳定、可靠地执行,不受模型理解偏差的影响。
约束与限制
| 限制类别 | 具体限制 |
|---|---|
| 功能限制 | 当前Hook脚本不支持实时加载,需要重新加载才可使用。 |
| 语言限制 | Hook脚本仅支持JavaScript/TypeScript语言。 |
| 外部依赖 | 如果使用外部依赖包,您需要在Hook所在的配置目录创建package.json文件,并声明需要使用的依赖。 |
支持的Hook事件
| 事件分类 | 事件名称 | 触发时机 | 可阻断 | 典型使用场景 | 说明 |
|---|---|---|---|---|---|
| 聊天消息 | chat.message | 用户发送消息后、进入处理流程前 | 是 | 聊天消息处理 | 新消息到达时自动触发,支持对消息内容及Parts进行编辑 |
| 聊天参数 | chat.params | 每次调用LLM前 | 是 | 聊天参数修改 | 支持动态调整大模型推理参数,包含temperature、topP、topK等,按需优化模型输出结果 |
| 聊天请求头 | chat.headers | 每次调用LLM前 | 是 | 聊天请求头修改 | 支持自定义修改HTTP请求头字段,适配网络代理、接口鉴权等复杂场景需求 |
| 聊天响应 | chat.response | LLM返回响应后 | 否 | 聊天响应记录 | 自动捕获模型响应的Tokens、Cost、Duration等核心指标,实现精准计费统计、高效故障排查与完整日志留存 |
| 聊天错误 | chat.error | LLM调用发生错误时 | 否 | 聊天错误记录 | 记录LLM调用失败时的错误日志 |
| 聊天压缩 | chat.compression | 上下文压缩时 | 否 | 聊天压缩记录 | 记录上下文压缩前后的Token数量,辅助存储与性能调优 |
| 聊天结束 | chat.finished | 聊天会话结束时 | 否 | 聊天结束记录 | 记录会话结束事件,包含本次运行的Token消耗和运行信息 |
| 用户审批 | user.approval | 需要用户确认/审批操作时 | 是 | 用户审批记录 | 拦截高风险操作,发起人工确认审批,管控敏感操作执行权限 |
| Turn结束 | turn.end | 每个对话轮次结束时 | 否 | Turn结束记录 | 记录每轮对话结束日志,包含处理耗时、终止原因等关键信息 |
| 命令执行 | command.execute.before | 命令执行前 | 是 | 命令执行前置处理 | 支持对Parts进行自定义配置,实现命令的前置处理与灵活适配 |
| 工具执行 | tool.execute.before | 工具执行前 | 是 | 工具执行前置参数调整 | 支持在工具执行前自定义修改参数args,实现校验、动态改写与业务适配 |
| tool.execute.after | 工具执行后 | 是 | 工具执行后置结果处理 | 支持自定义修改返回结果,包括title、output及metadata字段 | |
| 工具定义 | tool.definition | 工具定义发送给LLM前 | 是 | 工具定义修改 | 支持在工具推送模型前自定义配置描述与参数,确保精准适配业务场景 |
| 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": "***" //第二个正则:匹配普通键值对格式,如password=***或password:*** const sensitivePatterns = [ //匹配JSON格式的敏感字段:双引号包裹的键名 + 冒号 + 双引号包裹的值 new RegExp(`"(password|passwd|pwd|api[_-]?key|secret|token|密钥|密码)"\\s*:\\s*"([^"]+)"`, "g"), //匹配普通格式的敏感字段:键名 + 冒号或等号 + 不带引号的值 new RegExp(`(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,脚本才可以正常使用。
为确保脚本生效,建议在任务管理器中结束掉所有Bun进程,以彻底清除残留实例。
- 验证效果。
在输入框中输入指令后,模型返回的结果中敏感信息已自动脱敏。
图2 模型返回效果
创建Hooks
- 选择对应的事件。
请根据实际业务需求,从支持的Hook事件中匹配并选择对应的事件名称。
- 编写Hook脚本。
- 创建插件文件。
在插件及依赖文件存放路径目录下,新建“.ts”或“.js”文件。
- 编写Hook逻辑。
在Hook事件参考内容中,找到“// Hook implementations go here”注释位置,并在此处根据实际需求编写具体的Hook实现代码。
- 创建插件文件。
- (可选)配置依赖。 如果插件文件中需要使用外部包,必须在插件及依赖文件存放路径的配置目录中创建package.json文件并配置所需的依赖项。
{ "dependencies": { "依赖包名1": "版本号", "依赖包名2": "版本号" } }package.json文件具体示例如下:
{ "dependencies": { "@opencode-ai/plugin": "*", "@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,脚本才可以正常使用。
为确保脚本生效,建议在任务管理器中结束掉所有Bun进程,以彻底清除残留实例。
- 在输入框中输入“删除文件***”,客户端会弹出预警窗口。 其中,***为待删除文件的名称。图5 出现预警窗口
- 在华为云码道IDE工具左上角,单击“文件(F)”,选择“重启IDE”。因为当前Hook脚本不支持实时加载,所以需要重新启动IDE,脚本才可以正常使用。