0%

跟着OpenCode学智能体设计和开发1:Agent系统

OpenCode Agent系统是一个多智能体架构,通过[1a]定义Agent结构,使用[2a]Task工具实现Agent间调用,集成[3a]权限系统进行访问控制,通过[4a]会话处理器处理交互,并使用[5a]工具系统提供可扩展能力。

1. Agent配置和初始化流程

OpenCode需要支持多种专业化AI agent来处理不同类型的开发任务。一些agent需要完整的文件编辑和系统命令权限(如Build agent),而另一些只需要只读权限(如Plan agent)。系统必须能够动态配置这些agent的行为、权限和可用工具,同时确保安全性和灵活性。

Agent系统的核心是Info结构定义[1a],它规定每个Agent的基本属性:名称、描述、模式(primary/subagent/all)和权限集。系统通过Instance.state()[1b]创建状态管理实例,加载全局配置并建立默认权限规则。

内置Agent如build[1c]通过PermissionNext.merge()合并默认权限和用户配置,确保安全的权限继承。用户可以通过配置文件自定义Agent[1d],系统会遍历cfg.agent对象,处理禁用逻辑并深度合并配置选项,实现完全的定制化控制。

这种设计使得Agent系统既能提供开箱即用的专业化助手,又能支持用户根据项目需求进行精确的权限和功能定制。

1a. Agent信息结构定义 (agent.ts:18)

定义Agent的核心属性结构,包括名称、描述、模式等

1
2
3
4
5
export const Info = z.object({
name: z.string(),
description: z.string().optional(),
mode: z.enum(["subagent", "primary", "all"]),
native: z.boolean().optional(),

1b. Agent状态管理初始化 (agent.ts:44)

创建Agent状态管理实例,加载配置和默认权限

1
2
3
4
const state = Instance.state(async () => {
const cfg = await Config.get()
const defaults = PermissionNext.fromConfig({
"*": "allow",

1c. 内置Build Agent定义 (agent.ts:66)

定义Build智能体的具体配置和权限设置

1
2
3
4
5
6
7
8
build: {
name: "build",
options: {},
permission: PermissionNext.merge(
defaults,
PermissionNext.fromConfig({
question: "allow",
}),

1d. 用户配置Agent加载 (agent.ts:185)

处理用户自定义Agent配置,支持禁用和覆盖默认配置

1
2
3
4
5
for (const [key, value] of Object.entries(cfg.agent ?? {})) {
if (value.disable) {
delete result[key]
continue
}

2. Task工具实现Agent间调用

在复杂的开发任务中,单个AI智能体往往难以处理所有类型的工作。OpenCode需要一种机制让不同的专门化智能体能够协作,比如让代码分析智能体调用文档生成智能体,或者让主智能体将特定任务委托给专门的子智能体。这种设计既提高了效率,也保持了每个智能体的专业性。

Task工具是Agent间协作的核心机制 [2a]。它首先获取所有可用的subagent列表,然后通过权限系统过滤出当前智能体可以调用的子智能体 [2b]。在执行前,系统会请求用户确认权限 [2c],确保安全控制。

调用时,Task工具会创建一个独立的子会话 [2d],这样可以将子智能体的工作与主对话隔离。子会话继承父会话的上下文,但有独立的权限控制。系统会加载指定的子智能体配置,设置适当的模型和工具权限 [2e],然后在子会话中执行任务。

整个流程体现了权限控制、会话隔离和智能体专业化的设计原则,让复杂的AI协作变得可控且高效。

2a. Task工具定义和Agent过滤 (task.ts:23)

定义Task工具,获取可调用的subagent列表

1
2
export const TaskTool = Tool.define("task", async (ctx) => {
const agents = await Agent.list().then((x) => x.filter((a) => a.mode !== "primary"))

2b. 权限检查过滤 (task.ts:28)

基于权限规则过滤当前Agent可调用的subagents

1
2
3
const accessibleAgents = caller
? agents.filter((a) => PermissionNext.evaluate("task", a.name, caller.permission).action !== "deny")
: agents

2c. 任务权限请求 (task.ts:46)

在调用subagent前请求用户权限确认

1
2
3
4
await ctx.ask({
permission: "task",
patterns: [params.subagent_type],
always: ["*"],

2d. 创建子会话 (task.ts:65)

为subagent调用创建独立的子会话,继承父会话关系

1
2
3
return await Session.create({
parentID: ctx.sessionID,
title: params.description + ` (@${agent.name} subagent)`,

2e. 执行Agent任务 (task.ts:138)

在子会话中使用指定Agent执行任务

1
2
3
4
5
6
7
8
const result = await SessionPrompt.prompt({
messageID,
sessionID: session.id,
model: {
modelID: model.modelID,
providerID: model.providerID,
},
agent: agent.name,

3. 权限系统集成与控制

在多智能体系统中,不同的Agent需要不同级别的访问权限。例如,代码审查Agent只需要读取权限,而构建Agent需要执行bash命令和修改文件的权限。权限系统解决了如何精细控制Agent行为的问题,防止Agent执行危险操作,同时确保用户对关键操作有最终控制权。

权限系统基于三个核心概念构建:动作类型(允许/拒绝/询问)[3a]、规则结构(权限类型+匹配模式+动作)[3b]和配置转换器(将用户配置转换为可执行的权限规则)[3c]。

系统支持模式匹配,可以对特定工具或命令设置细粒度权限。例如,可以允许git status但询问git push。权限检查通过工具上下文中的ask()方法[3d]实现,在Agent尝试执行受限操作时触发用户确认流程。

这种设计使得权限系统既灵活又安全,为多Agent协作提供了必要的安全边界。

3a. 权限动作定义 (next.ts:15)

定义三种基本权限动作:允许、拒绝、询问

1
2
3
export const Action = z.enum(["allow", "deny", "ask"]).meta({
ref: "PermissionAction",
})

3b. 权限规则结构 (next.ts:20)

定义权限规则的数据结构,包含权限类型、模式和动作

1
2
3
4
export const Rule = z.object({
permission: z.string(),
pattern: z.string(),
action: Action,

3c. 配置转权限规则 (next.ts:36)

将用户配置转换为权限规则集

1
2
3
export function fromConfig(permission: Config.Permission) {
const ruleset: Ruleset = []
for (const [key, value] of Object.entries(permission)) {

3d. 工具权限请求接口 (tool.ts:24)

在Tool上下文中定义权限请求方法

1
ask(input: Omit<PermissionNext.Request, "id" | "sessionID" | "tool">): Promise<void>

4. Agent会话处理流程

OpenCode需要处理与AI助手的复杂交互,包括流式响应、工具调用和多轮对话。会话处理器必须能够实时处理来自LLM的响应,管理工具执行状态,并处理异常情况。这个系统的核心挑战是在保持响应性的同时,确保每个会话的状态一致性和可靠性。

会话处理从[4a]创建处理器开始,它初始化toolcalls记录来跟踪工具执行状态。主处理循环[4b]通过LLM.stream()获取流式响应,使用for-await循环处理每一条数据。系统会区分不同类型的响应:文本内容直接显示,推理过程特殊处理,工具调用则创建对应的工具部分并跟踪执行状态。

工具调用处理特别重要,因为Agent可能需要使用Task工具调用其他subagents[4c]。这时系统会创建子会话,继承父会话的权限和上下文,让subagent能够独立执行任务。处理器还包含异常处理和重试机制,确保在网络中断或API错误时能够优雅恢复。

整个设计采用事件驱动架构,通过Bus系统发布状态更新,让UI和其他组件能够实时响应会话变化。

4a. 会话处理器创建 (processor.ts:26)

创建会话处理器,处理Agent的响应生成

1
2
3
4
5
export function create(input: {
assistantMessage: MessageV2.Assistant
sessionID: string
model: Provider.Model
abort: AbortSignal

4b. LLM流式处理 (processor.ts:53)

处理来自LLM的流式响应,包括文本和工具调用

1
2
3
4
5
const stream = await LLM.stream(streamInput)

for await (const value of stream.fullStream) {
input.abort.throwIfAborted()
switch (value.type) {

4c. 获取可用Agent列表 (task.ts:24)

在工具执行时获取可用的subagent列表

1
const agents = await Agent.list().then((x) => x.filter((a) => a.mode !== "primary"))

5. 工具系统与Agent集成

OpenCode需要为不同的AI智能体提供可配置的工具访问控制。如果所有智能体都能访问所有工具,会导致安全风险和权限混乱。工具系统需要支持动态加载、权限验证和插件扩展,让每个Agent只能使用被授权的工具。

工具系统基于接口定义构建 [5a],通过define()工厂函数创建工具实例 [5b]。每个工具都有明确的参数类型和执行逻辑,系统会自动进行参数验证 [5b]。

工具注册中心管理两类工具:内置工具(如BashTool、ReadTool等核心功能)[5c] 和插件工具(通过Plugin.list()动态加载)[5d]。注册过程会遍历所有插件,将插件提供的工具注册到系统中 [5d]。

工具执行时,系统会先初始化工具实例,然后进行严格的参数验证 [5b],最后执行工具逻辑并处理结果。这种设计确保了工具系统的安全性、可扩展性和一致性。

5a. 工具接口定义 (tool.ts:26)

定义工具的标准接口,支持初始化上下文

1
2
3
export interface Info<Parameters extends z.ZodType = z.ZodType, M extends Metadata = Metadata> {
id: string
init: (ctx?: InitContext) => Promise<{

5b. 工具定义函数 (tool.ts:47)

创建工具定义的工厂函数,包含参数验证和执行逻辑

1
2
3
export function define<Parameters extends z.ZodType, Result extends Metadata>(
id: string,
init: Info<Parameters, Result>["init"] | Awaited<ReturnType<Info<Parameters, Result>["init"]>>,

5c. 内置工具注册 (registry.ts:94)

注册系统内置的工具集合

1
2
3
4
5
6
return [
InvalidTool,
...(Flag.OPENCODE_CLIENT === "cli" ? [QuestionTool] : []),
BashTool,
ReadTool,
GlobTool,

5d. 插件工具加载 (registry.ts:51)

动态加载插件提供的扩展工具

1
2
3
const plugins = await Plugin.list()
for (const plugin of plugins) {
for (const [id, def] of Object.entries(plugin.tool ?? {})) {