Skip to content

Hooks 生命周期钩子 ​

SDK 通过 AgentOptions.advanced.hooks 提供进程内 TypeScript callback; 底层 SessionOptions.hooks 使用同一套契约。

快速开始 ​

ts
import { createAgent, HookEvent } from '@blade-ai/agent-sdk';

const agent = await createAgent({
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  advanced: {
    hooks: {
      [HookEvent.PreToolUse]: [
        async (input) => {
          console.log('[工具调用]', input.toolName, input.toolInput);
          return { action: 'continue' };
        },
      ],
      [HookEvent.PostToolUseFailure]: [
        async (input) => {
          console.error('[工具失败]', input.toolName, input.error);
          return { action: 'continue' };
        },
      ],
    },
  },
});

Session 支持的事件 ​

HookEvent 和 SessionHookEvent 包含以下 8 个事件:

事件时机常用输入
SessionStartSession 初始化完成sessionId
UserPromptSubmit用户输入进入 Agent 前userPrompt、hasImages、imageCount
PreToolUse工具权限检查与执行前toolName、toolInput
PermissionRequest工具需要权限决策时toolName、toolInput
PostToolUse工具成功后toolName、toolInput、toolOutput
PostToolUseFailure工具失败后toolName、toolInput、toolOutput、error
TaskCompleted任务完成任务相关字段
SessionEndSession 关闭sessionId

核心类型 ​

ts
interface HookInput {
  event: HookEvent;
  abortSignal?: AbortSignal;
  toolName?: string;
  toolInput?: JsonObject;
  toolOutput?: ToolModelContent;
  error?: Error;
  sessionId: SessionId;
  [key: string]: unknown;
}

interface HookOutput {
  action: 'continue' | 'skip' | 'abort';
  modifiedInput?: JsonObject;
  modifiedOutput?: JsonValue;
  reason?: string;
}

type HookCallback = (input: HookInput) => Promise<HookOutput>;
action含义
continue继续处理,可同时返回修改后的输入或输出
skip跳过当前工具调用
abort中止当前 prompt 或工具调用;不会永久关闭 Session

时限与取消 ​

每次 inline hook 事件共享一份总 wall-clock 预算,callback 按注册顺序执行。 AgentOptions.advanced.hookTimeoutMs 默认是 600000(10 分钟)。SessionEnd 使用更短的 advanced.sessionEndHookTimeoutMs,默认是 3000。底层 SessionOptions 使用同名字段。

SDK 会组合调用方 signal 与 deadline,并通过 HookInput.abortSignal 传给 callback。到期后事件以 HookTimeoutError(code 为 HOOK_TIMEOUT)失败。 callback 必须监听 signal 并释放资源;如果取消后仍未结束,后续 inline hook dispatch 以及 Session close/handoff 都会 fail-closed,直至该 callback settle。

SessionEnd callback 在一次 runtime 关闭流程中只执行一次;callback 失败或 超时后,重试 close() 不会再次调用它。

修改用户输入 ​

UserPromptSubmit 的文本字段名是 userPrompt。返回 modifiedInput.userPrompt 可替换文本,同时保留原消息中的图片:

ts
hooks: {
  [HookEvent.UserPromptSubmit]: [
    async (input) => {
      const prompt = String(input.userPrompt ?? '');
      return {
        action: 'continue',
        modifiedInput: {
          userPrompt: `[tenant:acme]\n${prompt}`,
        },
      };
    },
  ],
}

修改工具输入 ​

ts
hooks: {
  [HookEvent.PreToolUse]: [
    async (input) => {
      if (input.toolName !== 'Write') {
        return { action: 'continue' };
      }
      return {
        action: 'continue',
        modifiedInput: {
          ...input.toolInput,
          content: `// Generated\n${String(input.toolInput?.content ?? '')}`,
        },
      };
    },
  ],
}

阻止工具调用 ​

ts
const dangerous = [/rm\s+-rf/, /mkfs/, /dd\s+if=/];

hooks: {
  [HookEvent.PreToolUse]: [
    async (input) => {
      if (input.toolName !== 'Bash') {
        return { action: 'continue' };
      }
      const command = String(input.toolInput?.command ?? '');
      if (dangerous.some((pattern) => pattern.test(command))) {
        return {
          action: 'abort',
          reason: `危险命令被阻止: ${command}`,
        };
      }
      return { action: 'continue' };
    },
  ],
}

skip 会跳过执行并生成带原因的成功结果;abort 会生成失败结果。两者都 不等价于 session.close(),后续仍可继续使用 Session。

修改工具输出 ​

ts
hooks: {
  [HookEvent.PostToolUse]: [
    async (input) => {
      if (input.toolName !== 'Read') {
        return { action: 'continue' };
      }
      return {
        action: 'continue',
        modifiedOutput: String(input.toolOutput)
          .replace(/SECRET_KEY=\w+/g, 'SECRET_KEY=***'),
      };
    },
  ],
}

modifiedOutput 会替换回写给模型的 ToolResult.model,不修改 UI 专用的 display 字段。

与权限回调的关系 ​

机制用途返回值
PreToolUse / PostToolUse拦截、审计、修改工具调用HookOutput
PermissionRequest观察权限请求HookOutput
advanced.permission作出 allow / deny / ask 决策字符串或 PermissionResult

权限决策放在 advanced.permission:

ts
const agent = await createAgent({
  model,
  apiKey,
  advanced: {
    permission: async (request) => {
      if (request.kind === 'readonly') {
        return 'allow';
      }
      return 'ask';
    }
  },
});

SessionOptions.permissionHandler 是底层运行时扩展点。

回调顺序与错误 ​

同一事件的内联回调按数组顺序调用,但 dispatch 会先收集全部结果,再统一处理:

ts
hooks: {
  [HookEvent.PreToolUse]: [hookA, hookB, hookC],
}
  • hookA 返回 skip 或 abort 时,hookB 和 hookC 仍会执行。
  • 后一个回调收到的是同一份原始 HookInput,不会看到前一个回调的 modifiedInput。
  • 结果处理阶段按顺序合并修改,并采用遇到的第一个 skip 或 abort。
  • prompt 等非工具 Hook 的异常会向上层传播。
  • 工具 Hook 的异常会被执行管道规范化为工具错误;处理失败结果的 PostToolUseFailure 再次抛错时,SDK 会记录 warning 并保留原始工具错误。

用于日志、监控等非关键 Hook 时,应在回调内部处理可恢复错误:

ts
async (input) => {
  try {
    await sendToMonitoring(input);
  } catch (error) {
    console.error('Hook 上报失败', error);
  }
  return { action: 'continue' };
};

Released under the MIT License.