Cline SDK Agent 模式实战:交互式 CLI、流式 UI、结构化输出与状态恢复
本文围绕 Cline SDK 仓库中 .agents/skills/cline-sdk/references/agent/patterns.md 收录的八类 Agent 使用模式展开:交互式 CLI Agent、按会话线程维护记忆的对话型 Agent、基于事件订阅的流式 UI、通过 Completion Tool 提取结构化数据、带超时/中断的 Agent、插件化 Agent、跨会话状态恢复,以及通过 Gateway 注入预构建模型的进阶配置。读完本文后,你可以直接复制这些可运行骨架到自己的项目中,并理解每个模式背后 @cline/agents 运行时(AgentRuntime)的实际执行逻辑。
背景:Agent 类与两种配置形态
上述所有模式都围绕同一个类:@cline/sdk 导出的 Agent。从源码看,Agent 只是 AgentRuntime 的别名,二者是同一个类,别名存在的目的是让独立使用者写更友好的构造方式(见 agent-runtime.ts 的注释与 export const Agent = AgentRuntime):
// sdk/packages/agents/src/agent-runtime.ts
/**
* `Agent` is the user-friendly name for `AgentRuntime`. They are the same
* class; this alias exists so standalone callers can write:
*
* const agent = new Agent({ providerId, modelId, apiKey });
* await agent.run("hello");
*
* while `@cline/core` (which owns model construction) continues to use
* the `AgentRuntime` name with `{ model, ... }` configs.
*/
export const Agent = AgentRuntime;
运行时配置是一个可判别联合类型,存在两种形态(见 agent-runtime.ts):
- Provider 形态(推荐,独立使用者):提供
providerId+modelId+ 可选apiKey/baseUrl/headers/options,运行时内部通过@cline/llms的createGateway自行构建AgentModel; - 预构建模型形态(进阶):直接传入
model: AgentModel,用于@cline/core这类需要共享 gateway/telemetry 装配的场景。
两种形态在构造函数中通过 resolveRuntimeConfig 统一解析:Provider 形态会调用 createGateway({ providerConfigs: [...] }) 并 gateway.createAgentModel({ providerId, modelId }) 生成模型,同时把 messageModelInfo 默认设为 { id: modelId, provider: providerId },使 assistant 消息自动带上模型标签(见 resolveRuntimeConfig)。完整类型定义可参考 Agent API 参考。
另外两个工厂函数也可用:createAgent(config) / createAgentRuntime(config) 等价于 new(见 agents 包入口)。
模式一:交互式 CLI Agent(终端多轮对话 + 流式输出)
仓库给出的模式骨架是一个多轮终端对话 Agent:Agent 负责执行,readline 负责输入,subscribe() 负责把 assistant-text-delta 事件增量打印到终端:
import { Agent } from "@cline/sdk"
import * as readline from "node:readline"
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: [],
})
agent.subscribe((event) => {
if (event.type === "assistant-text-delta") {
process.stdout.write(event.text)
}
})
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
function prompt(): void {
rl.question("\nYou: ", async (input) => {
const trimmed = input.trim()
if (!trimmed || trimmed === "exit") {
rl.close()
return
}
process.stdout.write("\nAssistant: ")
if (agent.hasRun) {
await agent.continue(trimmed)
} else {
await agent.run(trimmed)
}
process.stdout.write("\n")
prompt()
})
}
prompt()
几个实现层面的要点:
hasRun决定调run()还是continue()。API 参考将其定义为“run()是否至少被调用过一次”的布尔属性(见 api.md 的 Methods 一节)。从源码结构看,run()与continue()内部走同一个执行入口execute(input)(agent-runtime.ts),因此文档约定“首轮用run()、后续轮用continue()”,并用hasRun来分流(gotchas.md 的 “run() vs continue()” 一节)。- 先
subscribe()再run()。事件监听必须注册在run()启动之前,否则可能丢失早期事件;subscribe()返回一个取消函数,可在不再需要时调用以移除监听(源码中subscribe实现见 agent-runtime.ts)。 - 递归
prompt()保持循环,输入exit或空行时rl.close()退出。
模式二:对话型 Agent(Slack Bot、聊天应用)
多租户场景(如 Slack Bot)中,每个会话线程维护一个独立 Agent 实例来保存对话记忆,用 Map<string, Agent> 按线程 ID 管理:
import { Agent } from "@cline/sdk"
const agents = new Map<string, Agent>()
async function handleMessage(threadId: string, message: string) {
let agent = agents.get(threadId)
if (!agent) {
agent = new Agent({
providerId: "anthropic",
modelId: "claude-sonnet-4-6",
systemPrompt: "You are a concise assistant.",
tools: [],
})
agents.set(threadId, agent)
}
const result = agent.hasRun
? await agent.continue(message)
: await agent.run(message)
return result.outputText
}
注意事项(来自 gotchas.md 的 “Memory and Long Conversations”):
Agent将所有消息保存在内存中,长会话下内存随轮次增长;- 可通过监控
result.usage.totalInputTokens跟踪上下文增长; - 超长会话建议改用带压缩(compaction)能力的
ClineCore,或定期用会话摘要新建 Agent 实例。
模式三:流式 UI(事件驱动渲染)
构建实时 UI 的核心是 subscribe() 回调里按事件类型分发。仓库示例覆盖了五类事件:assistant-text-delta(文本增量)、assistant-message(单条 assistant 消息完成)、turn-started / turn-finished(一轮迭代开始/结束,含工具调用数)、usage-updated(token 用量更新):
const agent = new Agent({
providerId: "anthropic",
modelId: "claude-sonnet-4-6",
systemPrompt: "You are a helpful assistant.",
tools: [myTool],
})
agent.subscribe((event) => {
switch (event.type) {
case "assistant-text-delta":
ui.appendText(event.text)
break
case "assistant-message":
ui.endText()
break
case "turn-started":
ui.startTurn(event.iteration)
break
case "turn-finished":
if (event.toolCallCount > 0) ui.showToolCount(event.toolCallCount)
break
case "usage-updated":
ui.updateUsage(event.usage.inputTokens, event.usage.outputTokens)
break
}
})
const result = await agent.run("Hello!")
从源码可以印证这些事件确实由运行时逐轮发出:主循环中每轮先 emit({ type: "turn-started", iteration }),模型返回后 emit({ type: "assistant-message", ... }),执行完工具调用后 emit({ type: "turn-finished", toolCallCount })(见 execute 主循环);此外还有 run-started、run-finished、run-failed、message-added、status-notice 等事件。turn-finished 中的 toolCallCount 即本轮模型发起的工具调用数量(无工具调用时为 0,此时通常意味着本轮自然结束)。完整事件类型清单见 events 参考。
另一个易错点:AgentRuntimeConfig 没有顶层 onEvent 字段。要接收事件只有两条路:同步的 subscribe()(适合 UI 流式渲染),或 hooks.onEvent(回调会被 await,适合写日志等异步副作用)。两者接收的事件类型相同(gotchas.md 的 “No Top-Level onEvent on Agent Config”)。
模式四:通过 Completion Tool 获取结构化输出
这是让 Agent 输出结构化数据的推荐做法:定义一个 lifecycle: { completesRun: true } 的工具,Agent 调用该工具即结束本次运行,工具入参就是你想要的结构化 schema:
import { Agent, createTool } from "@cline/sdk"
import { z } from "zod"
const submitReview = createTool({
name: "submit_review",
description: "Submit the final code review with structured feedback.",
inputSchema: z.object({
summary: z.string(),
issues: z.array(z.object({
file: z.string(),
line: z.number(),
severity: z.enum(["error", "warning", "info"]),
message: z.string(),
})),
approved: z.boolean(),
}),
lifecycle: { completesRun: true },
execute: async (input) => input,
})
const agent = new Agent({
providerId: "anthropic",
modelId: "claude-sonnet-4-6",
systemPrompt: "Review the code diff and submit structured feedback.",
tools: [submitReview],
})
const result = await agent.run(diffContent)
const review = result.toolCalls.find(tc => tc.name === "submit_review")
console.log(review?.output)
底层机制可以从源码得到完整印证:
- 运行时通过
getRequiredCompletionToolNames()收集所有tool.lifecycle?.completesRun === true的工具名(agent-runtime.ts); - 若配置了完成策略,运行开始时会自动注入一条系统提醒:“This run is not complete until you call one of these terminal completion tools: …”(getCompletionToolReminderMessage);
- 每轮工具执行后,
findCompletingToolMessage()检查是否存在终态工具消息,命中即finishRun("completed", ...)并返回,结束 Agent 循环(主循环尾部); - 若模型某轮只返回文本、没有调用完成工具,运行时会根据
completionPolicy注入提醒消息并继续下一轮迭代(execute 中 completion reminder 分支)。
实践要点(见 gotchas.md):
execute抛出异常会被计为一次“错误”,多次后 Agent 会停止;错误应作为结构化数据返回,例如return { error: "File not found", path: input.path };- 工具入参 schema 直接影响模型行为:固定取值用
z.enum(),每个属性都应有描述,约束条件(限流、上限)写进工具description; - 工具创建详见 tools 参考。
模式五:带中断/超时的 Agent
长时间任务需要可控的中断能力。模式示例用 setTimeout 触发 agent.abort("Timeout"),然后检查 result.status:
const agent = new Agent({
providerId: "anthropic",
modelId: "claude-sonnet-4-6",
systemPrompt: "Analyze this data.",
tools: [],
})
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)
}
源码中的对应实现:
abort(reason?)创建AgentRuntimeAbortError(reason 为字符串时直接作为消息,Error实例取其message),记录到state.lastError后触发内部AbortController.abort()(agent-runtime.ts、abort 方法);- 执行循环捕获到中止后,
status置为"aborted"(而非"failed"),最终AgentRunResult.status取值有三种:"completed"|"aborted"|"failed"(execute 的 catch 分支); - 注意
run()被 abort 时不会抛出异常,而是返回带status: "aborted"的 result,因此模式示例中的分支判断是必要的。
另外,长耗时工具的 execute 应尊重 context.abortSignal,在中止时返回部分结果而不是挂起(gotchas.md 的 “Abort Signal Handling in Tools”)。
模式六:带插件的 Agent
plugins 配置接受 AgentPlugin[],插件可以声明能力清单(manifest.capabilities)、在 setup() 中返回附加的工具与钩子:
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],
})
运行时的插件装配发生在惰性初始化 initialize() 中:每个插件的 setup() 收到 { agentId, agentRole, systemPrompt } 上下文,其返回值里的 tools 会被注册进运行时工具表,hooks 会被合并注册——即插件既贡献工具也贡献生命周期钩子(agent-runtime.ts)。钩子的完整集合为 beforeRun / afterRun / beforeModel / afterModel / beforeTool / afterTool / onEvent,其中 beforeRun、afterModel、beforeTool 可返回停止控制(stop control)来中止 Agent 循环(api.md 的 AgentRuntimeHooks 一节)。更多插件示例(通知、遥测、环境拦截等)见仓库的 sdk/examples/plugins 目录与 plugins 参考。
模式七:跨会话状态恢复(snapshot / restore)
Agent 提供手动快照与恢复能力:snapshot() 导出当前状态(含 messages),持久化 messages,之后用 restore() 注入新实例继续对话:
// 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")
两个方法的源码语义值得注意:
snapshot()返回一份深拷贝的AgentRuntimeStateSnapshot:agentId、runId、status、iteration、messages(克隆)、pendingToolCalls、usage、lastError等(snapshot 实现);restore(messages)会先中止任何进行中的运行(内部调用abort("Agent state restored")),重置runId/status/iteration/usage/错误状态,然后把消息历史替换为传入内容;同时保留底层模型、工具、钩子、插件与已注册的subscribe()监听者,因此外部持久化会话后重新注入历史不会丢失订阅(restore 实现 及其注释)。
文档同时给出选择建议:若需要自动持久化(含压缩、恢复策略),改用 ClineCore,其参考见 clinecore/REFERENCE.md。
模式八:通过 Gateway 注入预构建模型
Provider 形态之外,还可以显式构建模型再注入 Agent——这是 AgentRuntimeConfigWithModel 形态的典型用法,适合需要精细控制 provider 配置(多 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: [],
})
对照源码可以确认:Provider 形态内部走的路径正是 createGateway({ providerConfigs: [...] }) + gateway.createAgentModel(...)(resolveRuntimeConfig)。换言之,模式八是把运行时内部替你做的事显式提出来——你可以构建多 provider 的 gateway、复用同一个 AgentModel 给多个 Agent、或接入自有遥测装配,而 Provider 形态则是最省事的等价路径。
实践检查清单
综合 patterns 文档与 gotchas.md,落地这些模式时的检查项:
- 事件订阅时机:
subscribe()必须在run()之前注册,且subscribe()回调是同步调用(hooks.onEvent才是被 await 的); - 多轮对话分流:首轮
run()、后续continue(),用agent.hasRun判断(API 语义见 api.md); - 终止控制:希望 Agent 显式结束时提供
completesRun: true工具并在 system prompt 中引导模型调用它;没有工具时,模型返回不含工具调用的文本即自然完成; - 错误处理:工具
execute返回错误数据而非抛异常;result.status === "failed"时读取result.error; - 上下文增长:监控
result.usage.totalInputTokens,长会话考虑ClineCore的自动压缩; - 认证排障:
apiKey与providerId匹配;OpenAI 兼容 provider 需同时设置apiKey与baseUrl。
相关文档
- patterns.md —— 本文对应的原始模式参考
- agent/api.md —— Agent 完整 API 参考(配置形态、方法、
AgentRunResult、钩子) - agent/gotchas.md —— 常见陷阱
- tools/REFERENCE.md —— 工具创建
- plugins/REFERENCE.md —— 插件系统
- events/REFERENCE.md —— 事件类型
- 运行时核心实现:sdk/packages/agents/src/agent-runtime.ts、sdk/packages/agents/src/index.ts,测试见 agent-runtime.test.ts
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