首页
/ Cline SDK Agent 类完全解析:配置、run/continue 生命周期与 Hook 拦截机制

Cline SDK Agent 类完全解析:配置、run/continue 生命周期与 Hook 拦截机制

2026-09-03 15:33:34作者:余洋婵Anita

本篇以 Cline SDK 中独立运行的 Agent 类(@cline/agents 包,经 @cline/sdk 统一导出)为对象,完整覆盖其构造函数与 AgentRuntimeConfig 两种配置形态、run/continue/abort/subscribe/snapshot/restore 全量方法、AgentRunResult/AgentMessage/AgentUsage 等结果类型,以及 AgentRuntimeHooks 七种生命周期钩子的拦截语义;并深入 agent-runtime.ts 源码,印证事件发射顺序、工具策略(toolPolicies)的合并规则、上下文窗口溢出的自动恢复流程等底层行为,读完即可独立搭建并控制一个可流式、可中止、可持久化恢复的无状态 Agent 循环。

一、Agent 定位与创建方式

Agent 类(同时以 AgentRuntime 名导出,二者是同一个类)是 @cline/agents 包提供的轻量、无状态 Agent 循环:发送消息给 LLM → 执行工具调用 → 收集结果 → 重复,直到任务完成。它不向磁盘持久化任何内容,会话历史保存在内存中,可通过 snapshot() 获取。适用于需要自定义工具、最小依赖、浏览器兼容或无状态 worker 的场景;需要内置工具(bash、editor)、会话持久化或 .cline/ 配置发现时,应改用 ClineCore(参见 Agent Runtime 概览)。

构造函数与工厂函数

import { Agent } from "@cline/sdk"

const agent = new Agent(config: AgentRuntimeConfig)

等价工厂函数两种:

import { createAgent } from "@cline/sdk"

const agent = createAgent(config)

更底层的 createAgentRuntime 返回的也是同一个 Agent 类(源码中 createAgentRuntimecreateAgent 都只是 new AgentRuntime(config),见 agent-runtime.ts#L2169-L2188):

import { createAgentRuntime } from "@cline/sdk"

const runtime = createAgentRuntime(config)

index.ts 的导出注释可以看到命名约定:Agent 面向传入 provider/model ID 的独立调用者,AgentRuntime 面向传入预构建 AgentModel@cline/core;共享类型(AgentMessageAgentRunResult 等)应从 @cline/shared 直接导入,createTool 也从 @cline/shared 再导出。

二、AgentRuntimeConfig:两种判别联合配置

配置是判别联合(discriminated union),按“是否携带预构建 model”分两种形态(源码定义见 agent-runtime.ts#L106-L139)。

形态一:Provider ID 形式(推荐)

interface AgentRuntimeConfigWithProvider {
  providerId: string          // e.g. "anthropic", "openai", "gemini"
  modelId: string             // e.g. "claude-sonnet-4-6", "gpt-5.5"
  apiKey?: string             // provider API key
  baseUrl?: string            // custom endpoint
  headers?: Record<string, string>

  systemPrompt?: string
  tools?: AgentTool[]
  initialMessages?: AgentMessage[]
  toolPolicies?: Record<string, ToolPolicy>
  hooks?: Partial<AgentRuntimeHooks>
  plugins?: AgentPlugin[]
}

运行时收到这种配置后,resolveRuntimeConfig() 会用 @cline/llmscreateGateway 内部构建出 AgentModel(见 agent-runtime.ts#L147-L168),同时把 messageModelInfo 默认设为 { id: modelId, provider: providerId },用于给 assistant 消息打 modelInfo 标签(调用方显式提供的值优先)。因此大多数独立用户只需关心 providerIdmodelIdapiKey 三个字段。

形态二:预构建 Model 形式

interface AgentRuntimeConfigWithModel {
  model: AgentModel            // pre-built model from gateway

  systemPrompt?: string
  tools?: AgentTool[]
  initialMessages?: AgentMessage[]
  toolPolicies?: Record<string, ToolPolicy>
  hooks?: Partial<AgentRuntimeHooks>
  plugins?: AgentPlugin[]
}

这是高级形态:调用方自建 AgentModel(通常通过 @cline/llms 的 gateway),以便复用同一个 gateway 的遥测与配置接线。典型场景是多 provider 网关:

import { Agent } from "@cline/sdk"
import { createGateway } from "@cline/llms"

const gateway = createGateway({
  providerConfigs: [
    { providerId: "anthropic", apiKey: process.env.ANTHROPIC_API_KEY },
    { providerId: "openai", apiKey: process.env.OPENAI_API_KEY },
  ],
})

const model = gateway.createAgentModel({
  providerId: "anthropic",
  modelId: "claude-opus-4-7",
})

const agent = new Agent({
  model,
  systemPrompt: "You are a helpful assistant.",
  tools: [],
})

运行时通过 hasPrebuiltModel()(即 config.model !== undefined)判断走哪条分支,因此两种形态不可混用:一旦传了 modelproviderId 等字段就不再起模型构建作用。

一个容易踩的坑:配置上没有顶层 onEvent

AgentRuntimeConfig不存在顶层 onEvent 字段。事件订阅只有两条路:agent.subscribe()(同步回调,适合 UI 流式渲染)或 hooks.onEvent(可异步,被 await 等待,适合写外部日志服务等副作用)。hooks.onEvent 收到的 AgentRuntimeEvent 类型与 subscribe() 完全相同。完整事件目录见 事件参考

三、核心方法逐个解析

run(input)

启动一次运行。输入可以是 string、单个 AgentMessageAgentMessage[](源码中类型别名 AgentRunInput = string | AgentMessage | readonly AgentMessage[],见 agent-runtime.ts#L98)。字符串会被包装为 user 角色的 text 消息;消息数组则深拷贝后入栈。

const result: AgentRunResult = await agent.run("Build a REST API")

从源码的 execute() 主循环看(agent-runtime.ts#L690-L938),run() 的完整流程是:

  1. 确保完成惰性初始化(注册 hooks、工具表、插件 setup());若当前状态是 running 则直接抛错,防止重入;
  2. 新建 AbortController、生成新 runId,重置 iteration、usage、错误状态;
  3. 依次调用 beforeRun 钩子 → 发射 run-started 事件;
  4. 输入消息逐条入栈并各发一个 message-added 事件;
  5. 进入迭代循环:发射 turn-started → 请求模型(含上下文溢出自动恢复)→ 若模型返回 tool-call 部件则执行工具(发射 tool-started/tool-finished,工具结果作为 tool 角色消息入栈)并继续下一轮;若返回纯文本(无工具调用)则结束,发射 turn-finishedrun-finished
  6. 捕获任何异常后统一组装 AgentRunResult 返回(注意:异常不向外抛,而是以 status: "aborted" | "failed" 的 result 形式返回,error 字段仅 failed 时携带)。

continue(input?)

继续既有会话,输入可选(不传输入也可以,例如仅消费排队消息)。它与 run() 在源码里都委托给同一个 execute()agent-runtime.ts#L536-L542),差异仅在于语义:continue 不重置会话历史,直接在当前 messages 之上再跑一个 run(新 runId、usage 从零累计)。

const result = await agent.continue("Now add authentication")

配合 hasRun 属性(布尔值,表示 run() 是否至少被调用过一次)做入口分派是标准写法:

if (agent.hasRun) {
  await agent.continue(input)
} else {
  await agent.run(input)
}

abort(reason?)

取消当前正在进行的运行。reason 可以是任意值:若传入的就是 AgentRuntimeAbortError 则原样使用,否则运行时包装成一个(agent-runtime.ts#L544-L560)。中止后主循环在下一个检查点抛出中止错误,run()/continue() 的 Promise 正常 resolve,返回 status: "aborted" 的 result(而非 reject),因此超时保护可以这样写:

const timeout = setTimeout(() => agent.abort("Timeout"), 30_000)

try {
  const result = await agent.run(data)
  if (result.status === "aborted") {
    console.log("Agent was aborted")
  } else {
    console.log(result.outputText)
  }
} finally {
  clearTimeout(timeout)
}

subscribe(listener)

注册流式事件监听器,返回取消函数:

const unsubscribe = agent.subscribe((event: AgentRuntimeEvent) => {
  // handle event
})

// Later: stop listening
unsubscribe()

实现上就是一个 Set<AgentEventListener>agent-runtime.ts#L562-L567),emit() 先同步调用所有 listener,再 await 所有 hooks.onEvent。因此务必在 run() 之前注册以免错过 run-started 等早期事件;assistant-text-delta 等逐 chunk 事件频率极高,listener 应保持轻量。

snapshot() 与 restore(messages)

snapshot() 返回当前运行时状态的深拷贝(messages 经 cloneMessages 逐项浅克隆,防止外部修改污染内部状态)。AgentRuntimeStateSnapshot 的完整字段(agent.ts#L151-L165)比 API 文档简表更丰富:

interface AgentRuntimeStateSnapshot {
  agentId: string
  agentRole?: string
  parentAgentId?: string | null
  conversationId?: string
  runId?: string
  status: "idle" | "running" | "completed" | "aborted" | "failed"
  iteration: number
  messages: readonly AgentMessage[]
  pendingToolCalls: readonly string[]
  usage: AgentUsage
  lastError?: string
  lastErrorClass?: "context_window_exceeded" | "auth" | "unknown"
}

restore(messages) 用于整体替换会话历史:它会先 abort("Agent state restored") 丢弃进行中的运行与 usage 状态,然后保留 model、tools、hooks、plugins 与已注册的事件订阅者——这正好服务于“外部持久化会话、跨进程恢复”的诉求(agent-runtime.ts#L577-L595):

// Save state
const snapshot = agent.snapshot()
const serialized = JSON.stringify(snapshot.messages)

// Later: restore
const agent2 = new Agent({ ...config })
const messages = JSON.parse(serialized)
agent2.restore(messages)
const result = await agent2.continue("Continue where we left off")

如果需要自动持久化(checkpoint、.cline/ 会话管理),应使用 ClineCore

四、AgentRunResult:统一的结果契约

run()continue() 都返回:

interface AgentRunResult {
  agentId: string
  agentRole?: string
  runId: string
  status: "completed" | "aborted" | "failed"
  iterations: number
  outputText: string
  messages: readonly AgentMessage[]
  usage: AgentUsage
  error?: Error
}

状态值语义:

  • "completed" — 正常结束。两种触发路径:模型返回无工具调用的文本;或成功调用了带 lifecycle.completesRun: true 的“终结工具”(此时 outputText 取该工具的输出文本而非 assistant 消息文本,见 finishRun 调用处 agent-runtime.ts#L850-L867);
  • "aborted" — 经 abort() 或 hook 返回 stop: true 的受控停止(ControlledStopError)导致;
  • "failed" — 不可恢复错误,error 字段携带该 Error 实例,同时发射 run-failed 事件。

outputText 取最后一条 assistant 消息中的 text 部件拼接;iterations 即本次 run 消耗的模型轮次数。注意 messagesusage 都是拷贝,可安全持有。

五、AgentMessage 与 AgentUsage 数据模型

AgentMessage

interface AgentMessage {
  id: string
  role: "user" | "assistant" | "tool"
  content: AgentMessagePart[]
  createdAt: number
  metadata?: Record<string, unknown>
  modelInfo?: { id: string; provider: string; family?: string }
  metrics?: {
    inputTokens: number
    outputTokens: number
    cacheReadTokens?: number
    cacheWriteTokens?: number
    cost?: number
  }
}

补充两个源码层面的事实:消息 id 由 createUID("msg") 生成(msg_ 前缀 + nanoid);metrics 是运行时用 usageDelta() 计算的单条消息增量(本轮模型调用前后 usage 之差,见 agent-runtime.ts#L375-L420),当所有项均为 0 时该字段省略——所以按消息统计 token 成本时应以 metrics 为准,而 usage 是 run 级累计。

content 是部件数组(AgentMessagePart),包含 textreasoningmediafiletool-calltool-result 等类型;assistant 消息可能同时携带文本与多个 tool-call 部件,工具结果则单独作为 role: "tool" 的消息入栈。

AgentUsage

interface AgentUsage {
  inputTokens: number
  outputTokens: number
  cacheReadTokens: number
  cacheWriteTokens: number
  totalInputTokens: number
  totalOutputTokens: number
  totalCost?: number
}

totalInputTokens/totalOutputTokens 为跨 run 累计总量。usage 由模型流的 usage 事件增量累加(updateUsage()agent-runtime.ts#L1610-L1628),每次更新都会发射 usage-updated 事件,因此 UI 可以实时显示成本:

agent.subscribe((event) => {
  if (event.type === "usage-updated" && event.usage.totalCost) {
    console.log(`Running cost: $${event.usage.totalCost.toFixed(4)}`)
  }
})

六、AgentRuntimeHooks:七种生命周期钩子

interface AgentRuntimeHooks {
  beforeRun?(context): AgentStopControl | undefined
  afterRun?(context): void
  beforeModel?(context): AgentBeforeModelResult | undefined
  afterModel?(context): AgentStopControl | undefined
  beforeTool?(context): AgentBeforeToolResult | undefined
  afterTool?(context): AgentAfterToolResult | undefined
  onEvent?(event: AgentRuntimeEvent): void | Promise<void>
}

钩子可以在每个阶段拦截并修改行为。结合 shared/agent.ts 中各 Result 类型的完整字段,各钩子的能力边界是:

钩子 可返回的关键字段 能力
beforeRun stopreason 运行开始前否决整次 run
afterRun 无(void) 拿到最终 result 做收尾、上报
beforeModel stopreasonmessagestoolsoptions 每个模型请求前改写消息、工具集与模型选项
afterModel stopreason 模型返回后审查 assistant 消息并终止
beforeTool skipstopreasoninputpolicyappendContext 拦截/改写工具输入、覆盖工具策略、注入上下文
afterTool stopreasonresultappendContext 改写工具结果、注入上下文
onEvent subscribe() 的事件流,但会被 await,可异步

几个值得注意的源码行为:

  • 停止语义beforeRun/afterModel/beforeTool 返回 { stop: true, reason } 时,运行时抛出内部 ControlledStopError,run 以 status: "aborted" 结束,reason 写入 snapshot().lastErroragent-runtime.ts#L2044-L2054);
  • 钩子可叠加registerHooks() 把 config hooks 与各插件 hooks 追加进同一个 HookBag 数组,按注册顺序逐个执行(agent-runtime.ts#L637-L648);
  • appendContext 注入beforeTool/afterTool 返回的 appendContext 文本会被包装为带 source/tool_name/tool_call_id 属性的 <hook_context> 块,在该轮所有工具结果之后合并为一条用户消息追加进会话(displayRole: "system",不出现在面向用户的 transcript 中),模型在下一轮请求即可看到(agent-runtime.ts#L826-L843);
  • subscribe 与 onEvent 的分工subscribe() listener 同步调用、适合 UI 流式渲染;hooks.onEventemit() 中被逐个 await、适合异步副作用(如上报外部服务),但注意它会阻塞主循环推进,应保持快速。

插件形态的 hooks 写法(插件可额外通过 setup() 返回工具与 hooks):

import { Agent } from "@cline/sdk"
import type { AgentPlugin } from "@cline/sdk"

const loggingPlugin: AgentPlugin = {
  name: "logging",
  manifest: { capabilities: ["hooks"] },
  setup() {},
  hooks: {
    beforeTool({ toolCall }) {
      console.log(`Calling tool: ${toolCall.toolName}`)
    },
    afterRun({ result }) {
      console.log(`Completed in ${result.iterations} iterations`)
    },
  },
}

const agent = new Agent({
  providerId: "anthropic",
  modelId: "claude-sonnet-4-6",
  systemPrompt: "You are a helpful assistant.",
  tools: [myTool],
  plugins: [loggingPlugin],
})

七、工具策略 toolPolicies 与审批

toolPoliciesRecord<string, ToolPolicy>ToolPolicy 定义在 shared/llms/tools.ts

interface ToolPolicy {
  /** 工具是否允许执行,默认 true */
  enabled?: boolean
  /** 是否无需客户端审批即可运行,默认 true */
  autoApprove?: boolean
}

源码中 resolveToolPolicy() 的合并规则(agent-runtime.ts#L170-L178):通配符 "*" 的策略先展开,再被同名工具的策略覆盖,因此可以写“全局默认 + 个别例外”:

const agent = new Agent({
  providerId: "anthropic",
  modelId: "claude-sonnet-4-6",
  tools: [readFile, writeFile, runShell],
  toolPolicies: {
    "*": { autoApprove: false },        // 默认全部需要审批
    read_file: { autoApprove: true },   // 只读工具放行
    run_shell: { enabled: false },      // 直接禁用
  },
})

执行链上的判定顺序(prepareToolExecutionagent-runtime.ts#L1674-L1771):先跑 beforeTool 钩子(可改写 input、覆盖 policy、返回 skip)→ 合并 config 策略与钩子策略 → enabled === false 则记为跳过 → autoApprove === false 则调用配置的 requestToolApproval 回调请求审批,未配置回调时视为拒绝。被跳过/拒绝的工具不会抛出,而是产出 isError: true 的工具结果消息(附带 -- 拒绝后缀),让模型看到失败原因后自行调整。

八、迭代上限与上下文溢出的自动恢复

两个超出 API 文档范围、但直接影响生产可用性的运行时机制:

  • maxIterationsconfig.maxIterations 未设置时无上限;设置后循环条件为 iteration < maxIterations,超出即抛出 “Agent runtime exceeded maxIterations” 错误并以 status: "failed" 返回(agent-runtime.ts#L727-L730)。此外 completionPolicy.requireCompletionTool: true 时,若模型只返回文本,运行时会注入“必须调用终结完成工具”的提醒消息并继续循环,直到成功调用 completesRun: true 的工具。
  • 上下文窗口溢出恢复:当 provider 拒绝请求且错误被分类为 context_window_exceeded 时,运行时每个 run 自动尝试一次恢复:通过 prepareTurn 强制压缩(compaction)后重试,并先发射 status-notice 事件(“context window exceeded — compacting and retrying”)。若重试后消息未实际变小、或重试仍溢出,则以带可操作建议的终态消息失败(如 “there is no conversation history to compact…”),这些常量在 agent-runtime.ts#L52-L75 中定义,可直接用于错误文案匹配。

九、典型完整用法

把前述 API 组合起来的流式多轮 Agent:

import { Agent } from "@cline/sdk"

const agent = new Agent({
  providerId: "anthropic",
  modelId: "claude-sonnet-4-6",
  apiKey: process.env.ANTHROPIC_API_KEY,
  systemPrompt: "You are a helpful assistant. Keep responses concise.",
  tools: [],
  hooks: {
    onEvent: async (event) => {
      if (event.type === "run-failed") {
        await logToExternalService(event.error.message)
      }
    },
  },
})

agent.subscribe((event) => {
  if (event.type === "assistant-text-delta") {
    process.stdout.write(event.text)
  }
})

const first = await agent.run("What is 2 + 2?")
const second = await agent.continue("Now multiply that by 3")
console.log(second.outputText)
console.log(first.usage, second.iterations)

更多模式(交互式 CLI、按线程维护 agent 实例的聊天机器人、completesRun 结构化工具、插件)见 patterns.md;常见陷阱与调试见 gotchas.md

十、延伸阅读(仓库内路径)

登录后查看全文
热门项目推荐
相关项目推荐