
# Hooks
通过Hook（钩子），您可以在华为云码道代码智能体IDE及插件的关键生命周期节点中插入自定义逻辑，实现功能扩展，而无需更改任何现有代码。
与依赖模型理解的Prompt指令不同，Hooks的执行是确定性的，即一旦事件被触发，绑定的脚本将稳定、可靠地执行，不受模型理解偏差的影响。
#### 约束与限制
表1约束限制 
| 限制类别 | 具体限制                                                  |
|:---|:---|
| 功能限制 | 当前Hook脚本不支持实时加载，需要重新加载才可使用。                           |
| 语言限制 | Hook脚本仅支持JavaScript/TypeScript语言。                     |
| 外部依赖 | 如果使用外部依赖包，您需要在Hook所在的配置目录创建package.json文件，并声明需要使用的依赖。 |
   
 #### 支持的Hook事件
表2支持的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文件并配置所需的依赖项。华为云码道代码智能体在启动时会自动安装这些依赖项。
表3插件文件加载路径 
| 插件类型 | 插件文件存放路径                                                                                                           | 插件依赖文件存放路径                     | 说明             |
|:---|:---|:---|:---|
| 项目级  | 当前项目根目录./.codeartsdoer/plugin或./.codeartsdoer/plugins                                                              | 当前项目根目录./.codeartsdoer/        | 仅对当前项目有效。      |
| 个人级  | 本地%USERPROFILE%/.codeartsdoer/plugin或 %USERPROFILE%/.codeartsdoer/plugins | 本地%USERPROFILE%/.codeartsdoer/ | 对当前用户下所有项目均有效。 |
   
#### 快速入门
以下通过一个简单示例，演示如何对用户输入中的密码、密钥等敏感信息进行过滤，有效防止数据泄露。
1. 创建插件文件存放目录。 
   在项目根目录"./.codeartsdoer"下新建一个目录plugin。
   图1新建plugin目录   
   ![](https://support.huaweicloud.com/usermanual-codeartsagent/zh-cn_image_0000002631256436.png)
   
   
2. 创建脚本。 
   在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
         }
       }
     }
   }
   ```
   
   
3. 重启IDE。 
   在华为云码道代码智能体IDE工具左上角，单击"文件(F)"，选择"重启IDE"。因为当前Hook脚本不支持实时加载，所以需要重新启动IDE，脚本才可以正常使用。
   ![](https://support.huaweicloud.com/usermanual-codeartsagent/public_sys-resources/caution_3.0-zh-cn.png)
   为确保脚本生效，建议在任务管理器中结束掉所有Bun进程，以彻底清除残留实例。
   
   
4. 验证效果。 
   在输入框中输入指令后，模型返回的结果中敏感信息已自动脱敏。
   
   
 
#### 创建Hooks
1. 选择对应的事件。 
   请根据实际业务需求，从[支持的Hook事件]中匹配并选择对应的事件名称。
   
   
2. 编写Hook脚本。 
   1. 创建插件文件。 在[插件及依赖文件存放路径]目录下，新建".ts"或".js"文件。
      
   
   2. 编写Hook逻辑。 在[Hook事件参考]内容中，找到"// Hook implementations go here"注释位置，并在此处根据实际需求编写具体的Hook实现代码。
      
   
   
   
   
3. （可选）配置依赖。 
   如果插件文件中需要使用外部包，必须在[插件及依赖文件存放路径]的配置目录中创建package.json文件并配置所需的依赖项。
   ```
   {
     "dependencies": {
       "依赖包名1": "版本号",
       "依赖包名2": "版本号"
     }
   }
   ```
   package.json文件具体示例如下：
   ```
   {
     "dependencies": {
       "@opencode-ai/plugin": "*",
       "@babel/core": "7.29.0",
       "@langfuse/otel": "4.5.1"
     }
   }
   ```
   
   
4. 重启IDE验证效果。
 
 #### Hook事件参考
支持的Hook事件示例如下。请在"// Hook implementations go here"注释处编写具体的实现逻辑，代码编写完成后即可直接使用。
#### chat.message
```
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "chat.message": async (
        input: { sessionID: string; agent?: string; model?: { providerID: string; modelID: string }; messageID?: string; variant?: string },
        output: { message: any; parts: any[] }
    ) => {
        // Hook implementations go here
    }
  }
}
```
#### chat.params
```
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "chat.params": async (
       input: { sessionID: string; agent: string; model: any; provider: any; message: any; inferenceID?: string; isEnsureTitle?: boolean },
       output: { temperature: number; topP: number; topK: number; options: Record<string, any> }
    ) => {
        // Hook implementations go here
    }
  }
}
```
#### chat.headers
```
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "chat.headers": async (
       input: { sessionID: string; agent: string; model: any; provider: any; message: any },
       output: { headers: Record<string, string>}
    ) => {
        // Hook implementations go here
    }
  }
}
```
#### chat.response
```
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "chat.response": async (
      input: {
        sessionID: string
        parentID: string
        messageID: string
        userMessageID: string
        model: { id: string; providerID: string }
        agent: string
      },
      output: {
        finishReason: string
        tokens?: { input: number; output: number; reasoning: number; cache?: {write:number; read:number} }
        cost?: number
        duration: number
        responseText: string
        firstTokenTime: number
      },
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### chat.error
```
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "chat.error": async (
      input: { sessionID: string; messageID: string; userMessageID: string; model: { id: string; providerID: string }; agent: string },
      output: { error: Error; duration: number },
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### chat.compression
```
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "chat.compression": async (
      input: { sessionID: string,  tokensBefore: string, tokensAfter: string},
      output: {  },
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### chat.finished
```
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "chat.finished": async (
      input: { sessionID: string, agentRunID: string, agentID: string, loopStartTime: string, tokensInput: string, tokensOutput: string },
      output: {  },
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### user.approval
```
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "user.approval": async (
      input: {requestID: string, reply: string, sessionID: string},
      output: {}
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### turn.end
```
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    /** 触发钩子用于记录turn end日志 **/
    "turn.end": async (
      input: {sessionID: string, agentID: string, turnCount: string, durationMs: string, terminateReason: string},
      output: {}
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### command.execute.before
```
import type { Part } from "@opencode-ai/sdk"
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "command.execute.before": async (
      input: { command: string; sessionID: string; arguments: string },
      output: { parts: Part[] },
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### tool.execute.before
```
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "tool.execute.before": async (
      input: { tool: string; sessionID: string; callID: string, inferenceID?: string },
      output: { args: any },
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### tool.execute.after
```
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "tool.execute.after": async (
      input: { tool: string; sessionID: string; callID: string; args: any },
      output: {
        title: string
        output: string
        metadata: any
      },
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### tool.definition
```
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "tool.definition": async (
      input: { toolID: string }, 
      output: { description: string; parameters: any }
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### shell.env
```
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "shell.env": async (
      input: { cwd: string; sessionID?: string; callID?: string },
      output: { env: Record<string, string> },
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### permission.ask
```
import type { Permission } from "@opencode-ai/sdk"
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "permission.ask": async (
      input: Permission, 
      output: { status: "ask" | "deny" | "allow" }
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### experimental.chat.messages.transform
```
import type { Message, Part } from "@opencode-ai/sdk"
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "experimental.chat.messages.transform": async (
      input: {},
      output: {
        messages: {
          info: Message
          parts: Part[]
        }[]
      },
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### experimental.chat.system.transform
```
import type { Model } from "@opencode-ai/sdk"
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "experimental.chat.system.transform": async (
      input: { sessionID?: string; model: Model },
      output: {
        system: string[]
      },
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### experimental.session.compacting
```
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "experimental.session.compacting": async (
      input: { sessionID: string },
      output: { context: string[]; prompt?: string },
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### experimental.text.complete
```
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "experimental.text.complete": async (
      input: { sessionID: string; messageID: string; partID: string },
      output: { text: string },
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### config
```
import type { Config } from "@opencode-ai/sdk"
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    config: async (
      input: Config
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### event
```
import type { Event } from "@opencode-ai/sdk"
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    event: async (
      input: { event: Event }
    ) => {
      // Hook implementations go here
    }
  }
}
```
#### 场景示例
以"删除文件前弹窗进行高危操作提示"为例，向您详细介绍如何使用Hook。本示例以**项目级** ，**plugin**目录为例。
1. 根据[支持的Hook事件]中场景描述，选择对应的事件。
   
   删除文件会调用工具，一般是deleteFile工具，要在删除文件前弹窗，所以此处选择事件"tool.execute.before"。
   
   
2. 编写Hook脚本。 
   1. 在项目根目录"./.codeartsdoer"下，新建一个目录plugin。
      图2新建plugin目录   
      ![](https://support.huaweicloud.com/usermanual-codeartsagent/zh-cn_image_0000002631256436.png) 
   
   2. 在plugin目录下，新建文件deleteFileHintPlugin.ts。
   
   3. 编写脚本，并保存文件。
      ```
      //导入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)
              }
            }
          }
        }
      }
      ```
      
   
   
   
   
3. **重启IDE验证效果。**
   
   1. 在华为云码道代码智能体IDE工具左上角，单击"文件(F)"，选择"重启IDE"。因为当前Hook脚本不支持实时加载，所以需要重新启动IDE，脚本才可以正常使用。
      ![](https://support.huaweicloud.com/usermanual-codeartsagent/public_sys-resources/caution_3.0-zh-cn.png)
      为确保脚本生效，建议在任务管理器中结束掉所有Bun进程，以彻底清除残留实例。
      
   
   2. 在输入框中输入"删除文件*\*\*\** "，客户端会弹出预警窗口。
      其中，*\*\*\**为待删除文件的名称。
      
   
   
   
   
 
