Cline SDK @cline/agents 包详解:构建可移植的工具调用型 LLM Agent 循环
@cline/agents 是 Cline SDK 中的运行时无关(runtime-agnostic)agent 循环包:它提供运行和延续"会用工具的 LLM 对话"所需的核心原语,却不捆绑会话存储、Hub 传输或宿主默认工具。读完本文,你将理解该包在 Cline SDK 分层架构中的定位、两种配置形态(Provider 形式与 Model 形式)、工具/事件/钩子/插件四大扩展点的用法与底层实现,并能在 Node、浏览器或自定义宿主环境中独立搭起一个工具调用型 Agent。
一、包定位:只有循环,没有宿主
@cline/agents 的自我定位可以用一句话概括:它是 agent 循环本身,而不是一个完整的应用运行时。根据 README 与 package.json:
- 包描述为 "Browser-safe agent runtime for the next-generation Cline SDK",
type: module,要求node >= 22,许可证 Apache-2.0; - 运行时依赖只有三个:
@cline/llms、@cline/shared和nanoid(用于生成agentId等标识符),这正是"轻依赖、可移植"的直接体现; - 它不包含:文件系统/shell/网页抓取等默认宿主工具、会话持久化与有状态编排、共享 Hub 运行时/会话传输(
@cline/core/hub)、子 agent 与团队协作原语——这些全部位于@cline/core。
这种切分的意义在于:当你想在 Node、浏览器或某个自定义宿主里,自备工具与运行时策略地跑一个 agent 循环时,@cline/agents 是唯一需要引入的循环层。从源码结构看,整个包只有一个实现文件 agent-runtime.ts(约 2100 行),其余为入口与测试,边界非常收敛。
对外导出的 API 面
包入口 index.ts 的导出面与文档完全一致:
| 导出 | 说明 |
|---|---|
Agent / AgentRuntime |
同一个类的两个名字(agent-runtime.ts#L2183:export const Agent = AgentRuntime;)。供 provider/model ID 时用 Agent,供预构建 AgentModel 时用 AgentRuntime |
createAgent / createAgentRuntime |
工厂函数等价物 |
AgentRuntimeAbortError |
中止时携带的错误类型 |
AgentRuntimeConfig 及其两个变体 |
判别式配置联合类型 |
createTool |
从 @cline/shared 再导出,便于编写工具 |
| 一批类型 | AgentTool、AgentMessage、AgentRuntimeEvent、AgentRuntimeHooks、AgentRunResult、AgentRuntimeStateSnapshot 等,均从 @cline/shared 再导出 |
README 建议:共享类型直接从 @cline/shared 导入,本包只是便利性地再导出。
二、安装与快速上手
安装
npm install @cline/agents @cline/shared @cline/llms
三个包都要装的原因:@cline/agents 内部依赖 @cline/llms(构建模型网关)与 @cline/shared(全部共享类型),而用户代码通常也会直接引用 AgentTool 等共享类型。
Quick Start
下面的示例完整继承自 README 的 Quick Start 一节,展示一个最小可运行的天气查询 agent:
import { Agent } from "@cline/agents";
import type { AgentTool } from "@cline/shared";
const getWeather: AgentTool<{ city: string }, { forecast: string }> = {
name: "get_weather",
description: "Return the current weather for a city.",
inputSchema: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
},
async execute({ city }) {
return { forecast: `sunny in ${city}` };
},
};
const agent = new Agent({
providerId: "anthropic",
modelId: "claude-sonnet-4-6",
apiKey: process.env.ANTHROPIC_API_KEY,
systemPrompt: "You are a concise assistant.",
tools: [getWeather],
});
const result = await agent.run("What's the weather in San Francisco?");
console.log(result.outputText);
这里有几个值得注意的行为,均可在源码中找到对应:
agentId自动生成:构造函数中若未提供agentId,会用createUID("agent")(agent_前缀 + 8 位 nanoid)生成,见 agent-runtime.ts#L518-L534;- 工具以
name为键注册进 Map,重名工具会被后注册者覆盖(initialize()中this.tools.set(tool.name, tool),见 agent-runtime.ts#L619-L635); toolExecution默认"sequential":构造函数会把未显式给出的toolExecution填充为顺序执行,即同一轮内的多个工具调用默认串行执行。
三、两种配置形态:Provider 形式 vs Model 形式
Agent / AgentRuntime 接受一个判别式配置联合 AgentRuntimeConfig(agent-runtime.ts#L101-L139),对应两种用法。
Provider 形式(友好入口)
提供 providerId / modelId 与凭据,运行时通过 @cline/llms 替你构建 AgentModel:
new Agent({
providerId: "openai",
modelId: "gpt-5",
apiKey: process.env.OPENAI_API_KEY,
// baseUrl, headers also supported
tools: [/* ... */],
});
AgentRuntimeConfigWithProvider 在源码中的完整字段为(agent-runtime.ts#L115-L129):providerId: string(必填)、modelId: string(必填)、apiKey?、baseUrl?(自定义 API 地址)、headers?(额外请求头)、options?(provider 专属网关选项,类型为 GatewayProviderSettings["options"])。
源码印证:resolveRuntimeConfig()(agent-runtime.ts#L147-L168)在检测到没有预构建 model 时,执行 createGateway({ providerConfigs: [{ providerId, apiKey, baseUrl, headers, options }] }) 再调用 gateway.createAgentModel({ providerId, modelId }),并自动为 assistant 消息打上 messageModelInfo: { id: modelId, provider: providerId } 标记(除非调用者已显式提供)。也就是说,Provider 形式本质上是 Model 形式的一层语法糖。
Model 形式(高级)
宿主已经自己拥有网关时(@cline/core 内部就是这么用的),直接传入预构建的 AgentModel:
import { createGateway } from "@cline/llms";
const gateway = createGateway({ providerConfigs: [/* ... */] });
const model = gateway.createAgentModel({ providerId, modelId });
new Agent({
model,
tools: [/* ... */],
});
AgentModel 的契约本身非常薄(shared/src/agent.ts#L329-L333):只有一个 stream(request: AgentModelRequest) 方法,返回 AgentModelEvent 的异步可迭代流。这意味着你甚至可以完全绕过 @cline/llms,用自定义实现接入任意后端——只要事件流遵循 text-delta / reasoning-delta / tool-call-delta / usage / finish 等事件协议。
四、核心概念 1:工具(AgentTool)
工具遵循 @cline/shared 的 AgentTool<TInput, TOutput> 接口。README 给出的摘要式示例:
import type { AgentTool } from "@cline/shared";
const summarize: AgentTool<{ text: string }, { summary: string }> = {
name: "summarize_text",
description: "Summarize text into a short preview.",
inputSchema: {
type: "object",
properties: { text: { type: "string" } },
required: ["text"],
},
async execute({ text }, context) {
// context.signal — aborts when the run is cancelled
// context.emitUpdate(...) — stream progress as `tool-updated` events
return { summary: text.slice(0, 120) };
},
};
结合源码(shared/src/agent.ts#L195-L211),可以把接口拆得更细:
AgentTool继承AgentToolDefinition(即name+description+inputSchema三元组,inputSchema为 JSON Schema);- 另有三个可选执行策略字段:
timeoutMs?(超时)、retryable?(是否可重试)、maxRetries?(最大重试次数); execute(input, context)直接返回工具输出。context: AgentToolContext提供toolCallId?、signal?(run 被取消时触发 abort)、metadata?、snapshot?(当前运行时状态快照)、emitUpdate?(流式上报进度,运行时将其转成tool-updated事件)。
两条失败语义(来自 README,并被运行时的工具执行实现印证):
- 抛错即失败:
execute(...)中throw会把该次工具调用标记为失败(isError: true); - 包装信封可改写:成功输出会被运行时包进内部的
AgentToolResult信封,可用afterTool钩子对该信封做转换。
另外,配置里的 toolPolicies 支持按工具名配置策略,且 "*" 通配项与具体工具项会合并(agent-runtime.ts#L170-L178);若宿主需要交互式审批,还可以传入 requestToolApproval 回调(见下文配置总表)。
五、核心概念 2:事件流(AgentRuntimeEvent)
订阅运行时事件有两种方式:
// 1. 构造后挂载监听器,返回一个取消订阅函数。
const unsubscribe = agent.subscribe((event) => {
if (event.type === "assistant-text-delta") {
process.stdout.write(event.text);
}
});
// 2. 构造时注册 onEvent 钩子。
new Agent({
providerId,
modelId,
apiKey,
hooks: {
onEvent(event) {
// fires for every runtime event
},
},
});
subscribe() 的实现极简(agent-runtime.ts#L562-L567):把监听器放入 Set,返回移除闭包。
AgentRuntimeEvent 是一个联合类型(shared/src/agent.ts#L563 起),覆盖 run/turn 边界、assistant 文本与推理增量、工具生命周期、用量更新和 run 完成/失败。从源码可见的事件成员包括:
| 事件 | 关键字段 |
|---|---|
run-started |
snapshot |
message-added |
message、snapshot |
turn-started |
iteration、snapshot |
assistant-text-delta |
text、accumulatedText、iteration |
assistant-reasoning-delta |
text、accumulatedText、redacted? |
assistant-media |
media(生成式媒体) |
assistant-message |
message、finishReason |
tool-started / 后续工具生命周期事件 |
工具名、入参/输出、错误标志等 |
每个事件都携带 snapshot(当前运行时状态快照),因此监听器无需单独维护状态即可获得一致的运行时视图。
六、核心概念 3:会话控制 API
README 列出的六个会话控制点,全部可以在 AgentRuntime 类中找到对应实现:
agent.run(input)— 启动一次 run。input可以是字符串、单个AgentMessage或消息数组(AgentRunInput = string | AgentMessage | readonly AgentMessage[]);传undefined表示不新增用户轮次、直接延续。实现上run与continue都委托给同一个私有execute()(agent-runtime.ts#L536-L542);agent.continue(input?)—run(input?)的便捷别名,语义是"延续对话";agent.abort(reason?)— 取消当前 run。实现为(agent-runtime.ts#L544-L560):把reason归一为AgentRuntimeAbortError,记录lastError、发出TASK_CANCELLED生命周期遥测,再触发内部AbortController。.run()会以status: "aborted"正常 resolve,而不是 reject;agent.snapshot()— 返回当前AgentRuntimeStateSnapshot的不可变视图(agent-runtime.ts#L597-L612),字段包括agentId、agentRole、parentAgentId、conversationId、runId、status、iteration、messages(深拷贝)、pendingToolCalls、usage、lastError、lastErrorClass;agent.restore(messages)— 用持久化的消息数组替换整个会话(agent-runtime.ts#L577-L595)。源码注释明确了它"丢弃进行中的 run 与用量状态,但保留 model、tools、hooks、plugins、active event subscribers 与 agent 身份"——这对在外部自行持久化会话、需要重新灌入运行时而不重建订阅者的宿主非常关键;initialMessages(构造参数)— 在启动时给会话播种初始消息,构造函数中通过cloneMessages()拷贝进内部状态。
七、核心概念 4:钩子(AgentRuntimeHooks)
在构造函数里传入 hooks 对象即可观察或影响 agent 循环。AgentRuntimeHooks 接口定义了 7 个挂载点(shared/src/agent.ts#L420-L449):
| 钩子 | 时机 | 可返回的控制信号 |
|---|---|---|
beforeRun |
run 开始前 | AgentStopControl(可停止) |
afterRun |
run 结束(含 result) |
无(仅观察) |
beforeModel |
每次模型调用前,可改写 request 中的消息/工具/选项 |
AgentBeforeModelResult,如 { options: { temperature: 0.2 } } |
afterModel |
每次模型调用后 | AgentStopControl |
beforeTool |
每次工具执行前 | AgentBeforeToolResult,如 { skip: true, reason } 可拦截调用 |
afterTool |
每次工具执行后 | AgentAfterToolResult,可改写结果信封 |
onEvent |
每个运行时事件 | 无 |
所有钩子都可以是异步的;任一可停止钩子返回 { stop: true, reason } 都会以 aborted 状态终止本次 run。README 给出的组合示例(策略拦截 + 采样参数注入 + 用量日志):
new Agent({
providerId,
modelId,
apiKey,
tools: [/* ... */],
hooks: {
beforeModel({ request }) {
// mutate messages/tools/options before the model call
return { options: { temperature: 0.2 } };
},
beforeTool({ tool, input }) {
// block a tool call based on policy
if (tool.name === "get_weather" && !(input as { city?: string }).city) {
return { skip: true, reason: "city required" };
}
return undefined;
},
afterRun({ result }) {
console.log("done", result.usage);
},
},
});
注册机制值得注意:钩子不是"最后一个覆盖前一个",而是按数组追加(registerHooks 对每个挂载点 push,agent-runtime.ts#L637-L648),因此构造参数里的 hooks 与插件贡献的 hooks 会叠加生效。若需要更强的宿主侧钩子编排——15 阶段 HookEngine、子进程钩子、MCP 扩展——README 明确指回 @cline/core。
八、prepareTurn:只改写请求、不改写转录
prepareTurn 在消息发往 provider 之前运行,可以改写下一次请求的消息或系统提示词。README 给出的数据流非常清晰:
saved transcript
|
| turn preparation
v
prepareTurn
|
v
prepared provider request
prepareTurn returns prepared messages
|
+--> provider request: yes
+--> saved transcript: no
+--> AgentRunResult.messages: no
这是有意的语义边界:prepareTurn 的返回值只影响当前这次模型调用的 provider 请求,不会替换保存的历史转录,也不会出现在 AgentRunResult.messages 里。需要做持久化脱敏、归一化或策略过滤的宿主,必须在消息进入转录之前自行完成。
从源码看(shared/src/agent.ts#L227-L256),钩子收到的上下文 AgentRuntimePrepareTurnContext 包含 agentId、conversationId、parentAgentId、iteration、当前 messages / systemPrompt / tools、model 信息、signal,以及一个特别字段 overflowRecovery?——当上一次请求因超出上下文窗口被拒时会置位,提示 prepare-turn 管线强制压缩而不是信任 token 估算。返回值是 { messages?, systemPrompt? }。运行时内部也配合了"每个 run 仅一次自动溢出恢复"的机制(agent-runtime.ts#L511-L512),并对"无可压缩历史"与"压缩后仍溢出"两种终端失败分别定义了可读的终止文案。
九、插件(AgentRuntimePlugin)
插件可以在启动(setup)阶段贡献工具与钩子,适合把横切能力(审计、限流、日志)打包复用:
import type { AgentRuntimePlugin } from "@cline/shared";
const loggingPlugin: AgentRuntimePlugin = {
name: "logging",
setup({ agentId }) {
return {
hooks: {
afterTool({ tool, result }) {
console.log(agentId, tool.name, result.isError);
return undefined; // hook may return an AgentAfterToolResult
},
},
};
},
};
new Agent({
providerId,
modelId,
apiKey,
plugins: [loggingPlugin],
});
类型定义(shared/src/agent.ts#L455-L475):插件由 name: string 和可选的 setup(context) 组成;context 提供 agentId、agentRole?、systemPrompt?;setup 可以返回 undefined 或 { tools?, hooks? },且允许是异步的。
运行时在首次 run 前的惰性初始化(ensureInitialized() → initialize(),agent-runtime.ts#L619-L635)中依次完成:注册构造参数里的钩子 → 注册构造参数里的工具 → 依次 await 每个插件的 setup,把返回的 tools 并入工具 Map、hooks 并入钩子注册表。从源码结构看,插件初始化只发生一次(由 this.initialization Promise 缓存),因此插件内可以安全地做一次性资源准备。
十、完整配置参数速查
把 README 各节与 shared/src/agent.ts#L481-L557 的 AgentRuntimeConfig 接口对照,完整构造参数如下(Provider/Model 二选一必填):
| 参数 | 类型/取值 | 说明 |
|---|---|---|
model 或 providerId+modelId |
二选一 | 见"两种配置形态";Provider 形式另有 apiKey?/baseUrl?/headers?/options? |
systemPrompt |
string |
系统提示词 |
tools |
AgentTool[] |
本地执行的工具列表 |
hooks |
AgentRuntimeHooks |
7 个生命周期挂载点 |
plugins |
AgentRuntimePlugin[] |
启动时贡献工具/钩子 |
initialMessages |
AgentMessage[] |
启动时播种会话 |
toolExecution |
"sequential" | "parallel" |
同轮工具执行策略,默认 "sequential" |
toolPolicies |
Record<string, ToolPolicy> |
按工具名配置策略,支持 "*" 通配 |
toolContextMetadata |
Record<string, unknown> |
注入工具 context.metadata |
requestToolApproval |
(request) => ToolApprovalResult |
宿主侧交互式审批回调 |
prepareTurn |
见第八节 | 请求前投影钩子 |
consumePendingUserMessage |
() => string | undefined |
交互式会话在循环轮次间注入排队中的用户消息 |
maxIterations |
number |
循环迭代上限 |
completionPolicy |
{ requireCompletionTool?, completionGuard? } |
完成判定策略 |
agentId / conversationId / sessionId |
string |
标识符三件套:agent 身份、转录 ID、宿主生命周期会话 ID |
parentAgentId / agentRole |
— | 团队/子 agent 场景的身份标记 |
messageModelInfo |
{ id, provider } |
assistant 消息的模型标记 |
modelOptions / modelTools |
— | 模型选项与 provider 侧执行的工具 |
telemetry / logger / distinctId / clientName / clientVersion / clineCoreVersion |
— | 遥测与客户端元数据 |
十一、Teams 与 Spawn:多 agent 去哪找
@cline/agents 刻意不提供多 agent 协调原语。README 指向 @cline/core 的四个入口:
import {
createSpawnAgentTool,
AgentTeamsRuntime,
createAgentTeamsTools,
bootstrapAgentTeams,
} from "@cline/core";
它们提供委派 run、邮箱(mailbox)、任务管理与结果收敛等协调原语。也就是说,架构上的分工是:@cline/agents 负责"一个 agent 如何转起来",@cline/core 负责"多个 agent 与宿主如何组织起来"。
十二、入口点、浏览器安全与相关包
- 单一入口 + 浏览器条件:
@cline/agents只有一个包入口。package.json 的exports映射为"."下依次声明browser、types、import条件,均指向dist/index.js—— bundler 解析到browser条件时会自动获得浏览器安全 bundle。这与 README 的 Entry Point 一节一致; - 相关包分工:
@cline/shared:共享类型(AgentTool、AgentMessage、AgentRuntimeEvent、AgentRuntimeHooks等),位于 sdk/packages/shared/src/agent.ts;@cline/llms:provider 配置、模型目录、网关与 handler 创建(createGateway即出自此包);@cline/core:有状态运行时组装、存储、默认工具、子进程钩子、Hub 传输与 MCP 集成。
十三、测试与更多示例
- 单元测试:agent-runtime.test.ts(近 3000 行,覆盖循环、工具执行、钩子、快照/恢复等主路径)与 agent-runtime.provider-form.test.ts(专门验证 Provider 形式的网关构建路径);
- 仓库内更多可运行示例:sdk/examples/hooks(PreToolUse/PostToolUse 等钩子示例)、sdk/examples/plugins(含 agents-squad 团队插件)、sdk/examples/cron(定时 agent 场景);
- SDK 工作区总览与架构参考:sdk/README.md、sdk/ARCHITECTURE.md。
小结
@cline/agents 的价值在于把"agent 循环"从宿主环境中彻底解耦:一个类、两种配置形态、工具/事件/钩子/插件四个扩展点,外加一套语义明确的会话控制 API(run / continue / abort / snapshot / restore)。当你只需要循环本身而自备工具与策略时,它是最小的引入面;当你需要会话存储、默认宿主工具或团队协作时,沿 @cline/core 向上一层组装即可。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00