OpenCode 是一个开源的 AI 编码代理系统,由 anomalyco 团队开发。它与 Claude Code 在功能上非常相似,但具有以下核心差异:
- 模型无关:支持多种 LLM 提供商(Claude、OpenAI、Google、本地模型等)
- 原生 LSP 支持:内置语言服务器协议集成
- TUI 优先:专注于终端用户界面体验
- 客户端/服务器架构:支持远程控制和多客户端访问
架构设计
1. 整体架构
OpenCode 采用现代化的 monorepo 架构,基于 TypeScript 构建:
opencode/
├── packages/
│ ├── opencode/ # 核心服务器和业务逻辑
│ ├── tui/ # 终端用户界面(Go实现)
│ ├── app/ # Web/Desktop应用(SolidJS)
│ ├── sdk/ # JavaScript/TypeScript SDK
│ ├── plugin/ # 插件系统框架
│ ├── ui/ # 共享UI组件库
│ ├── console/ # 管理控制台
│ ├── web/ # 文档和营销网站
│ └── functions/ # 无服务器函数
├── infra/ # 基础设施定义(SST v3)
├── .opencode/ # 配置和规则
└── sst.config.ts # SST配置
2. 核心技术栈
前端技术
- SolidJS:高性能响应式框架(Desktop/Web应用)
- Bubble Tea:优雅的TUI框架(Go)
- TypeScript:类型安全的开发体验
后端技术
- Bun:高性能JavaScript运行时
- TypeScript:核心业务逻辑
- SQLite:会话和消息持久化
- SST v3:基础设施即代码
构建和部署
- Turbo:Monorepo构建系统
- Bun build:快速打包和编译
- GitHub Actions:CI/CD流程
Agent 系统核心原理
1. Agent 架构设计
OpenCode 实现了一个分层的 Agent 系统:
// Agent 类型定义
interface Agent {
mode: 'primary' | 'subagent' | 'all'
model?: string
provider?: string
temperature?: number
description?: string
prompt?: string
permission?: {
edit?: 'allow' | 'ask' | 'deny'
bash?: 'allow' | 'ask' | 'deny' | Array<{pattern: string, action: string}>
webfetch?: 'allow' | 'ask' | 'deny'
task?: Array<{pattern: string, action: 'allow' | 'deny'}>
}
tools?: Record<string, boolean>
hidden?: boolean
steps?: number
}
2. 主要 Agent 类型
Primary Agents(主代理)
- build:默认的全功能开发代理,拥有完整的文件编辑和命令执行权限
- plan:只读分析代理,专注于代码审查和规划,默认需要用户批准才能执行操作
Subagents(子代理)
- general:通用研究和多步骤任务代理
- explore:快速代码库探索代理
- title:会话标题生成
- summary:消息摘要和差异生成
- compaction:上下文压缩代理
3. Agent 调用机制
// Task工具允许主代理调用子代理
const taskTool = {
type: 'function',
name: 'task',
description: 'Run a subtask with a specialized subagent',
parameters: {
type: 'object',
properties: {
agent: {
type: 'string',
description: 'The subagent to invoke'
},
prompt: {
type: 'string',
description: 'The task description for the subagent'
}
},
required: ['agent', 'prompt']
}
}
4. Agent 生命周期
用户输入 → 主Agent处理
↓
需要专业任务?
↓ Yes
通过Task工具调用子Agent
↓
子Agent执行并返回结果
↓
主Agent整合结果
↓
返回给用户
5. 权限系统
OpenCode 实现了细粒度的权限控制:
// 权限配置示例
{
"agent": {
"plan": {
"permission": {
"edit": "ask", // 编辑文件需要确认
"bash": {
"git *": "allow", // Git命令直接允许
"rm -rf *": "deny", // 危险命令禁止
"*": "ask" // 其他命令需要确认
}
}
}
}
}
权限评估逻辑:
- 按顺序匹配规则
- 最后一个匹配的规则生效
- 支持glob模式匹配
Skills 系统技术实现
1. Skills 的概念模型
Skills 是可重用的指令包,允许 Agent 按需加载专业知识。这是 OpenCode 借鉴自 Anthropic 的 Agent Skills 规范的实现。
2. Skill 目录结构
skills/
├── pdf/
│ ├── SKILL.md # 主要指令文档
│ ├── scripts/ # 可执行脚本
│ ├── assets/ # 资源文件
│ └── refs/ # 参考文档
├── docx/
│ └── SKILL.md
└── custom-skill/
└── SKILL.md
3. SKILL.md 格式规范
---
name: pdf
description: PDF处理和生成工具集
---
# PDF Skill 使用指南
当用户需要处理PDF时:
1. 使用pypdf2提取文本
2. 使用reportlab创建新PDF
3. 使用PyPDF2合并PDF文档
## 最佳实践
- 始终验证PDF文件存在性
- 处理编码问题时使用UTF-8
...
4. Skill 发现和加载机制
// 技术实现原理(基于社区插件分析)
class SkillRegistry {
private skills: Map<string, Skill> = new Map()
// 1. 扫描技能目录
async discover() {
const paths = [
'./.opencode/skill', // 项目本地
'~/.config/opencode/skill', // 全局用户
'~/.claude/skills' // Claude Code兼容
]
for (const basePath of paths) {
const skillDirs = await glob(`${basePath}/*/SKILL.md`)
for (const skillPath of skillDirs) {
await this.loadSkill(skillPath)
}
}
}
// 2. 解析技能文件
async loadSkill(path: string) {
const content = await readFile(path)
const { data: frontmatter, content: body } = matter(content)
const skill: Skill = {
name: frontmatter.name,
description: frontmatter.description,
path: path,
content: body,
resources: await this.indexResources(path)
}
this.skills.set(skill.name, skill)
}
// 3. 索引资源文件
async indexResources(skillPath: string) {
const dir = dirname(skillPath)
return {
scripts: await glob(`${dir}/scripts/**/*`),
assets: await glob(`${dir}/assets/**/*`),
refs: await glob(`${dir}/refs/**/*`)
}
}
}
5. Skill 注入到 Agent 上下文
OpenCode 使用"按需加载"策略,避免污染上下文:
// Skill工具定义
const skillTool = {
type: 'function',
name: 'skill',
description: `Load a skill for specialized instructions.
Available skills:
${skills.map(s => `- ${s.name}: ${s.description}`).join('\n')}`,
parameters: {
type: 'object',
properties: {
name: {
type: 'string',
enum: skills.map(s => s.name),
description: 'The skill to load'
}
}
}
}
// 当Agent调用skill工具时
async function handleSkillTool(skillName: string) {
const skill = registry.get(skillName)
// 通过消息注入的方式添加到上下文
return {
role: 'user',
content: `<skill_loaded name="${skill.name}">
${skill.content}
Base directory: ${skill.basePath}
Available resources:
- Scripts: ${skill.resources.scripts.join(', ')}
- Assets: ${skill.resources.assets.join(', ')}
</skill_loaded>`
}
}
6. Skill 权限控制
// 配置示例
{
"tools": {
"skill*": false, // 禁用所有技能
"skill_pdf": true, // 允许PDF技能
"skill_docx": true // 允许DOCX技能
},
"agent": {
"build": {
"tools": {
"skill_document_skills_*": true // build代理可访问所有文档技能
}
}
}
}
7. 与 MCP 的区别
| 特性 | Skills | MCP (Model Context Protocol) |
|---|---|---|
| 用途 | 静态指令和资源 | 动态工具和服务 |
| 加载时机 | 按需加载到上下文 | 运行时调用 |
| 网络依赖 | 无 | 可能需要 |
| 适用场景 | 知识库、最佳实践 | 外部API、实时数据 |
| 安全性 | 高(纯文本) | 需要验证 |
Tool 系统实现
1. 核心 Tool 定义
OpenCode 提供了一套丰富的内置工具:
// 文件操作工具
const tools = {
read: '读取文件内容',
write: '创建新文件',
edit: '编辑现有文件(str_replace方式)',
glob: '使用模式匹配查找文件',
grep: '在文件中搜索文本',
// 系统操作
bash: '执行shell命令',
// 代码智能
lsp_diagnostics: '获取LSP诊断信息',
// Agent协作
task: '调用子代理执行任务',
// Web能力
webfetch: '获取网页内容',
// 技能系统
skill: '加载技能指令'
}
2. Tool 执行流程
// 简化的工具执行逻辑
async function executeTool(toolCall: ToolCall, context: Context) {
const { name, arguments: args } = toolCall
// 1. 权限检查
const permission = checkPermission(context.agent, name, args)
if (permission === 'deny') {
throw new Error('Permission denied')
}
if (permission === 'ask') {
const approved = await askUser(`Allow ${name}?`, args)
if (!approved) return { error: 'User denied' }
}
// 2. 执行工具
const tool = tools[name]
const result = await tool.execute(args, context)
// 3. 记录和返回
await logToolExecution(name, args, result)
return result
}
3. Custom Tools(自定义工具)
OpenCode 支持项目级自定义工具:
// .opencode/tool/my-tool.ts
import { tool } from '@opencode-ai/plugin'
import { z } from 'zod'
export const myTool = tool({
name: 'my_custom_tool',
description: '执行特定项目操作',
parameters: z.object({
action: z.string(),
target: z.string()
}),
execute: async ({ action, target }, context) => {
// 访问OpenCode上下文
const { session, agent } = context
// 执行自定义逻辑
return {
success: true,
message: `Executed ${action} on ${target}`
}
}
})
LSP 集成实现
1. LSP 架构
// packages/opencode/src/lsp/server.ts
interface LSPServerInfo {
id: string
root: RootDetector
extensions: string[]
spawn: (root: string) => Promise<LSPProcess>
}
// TypeScript服务器配置
const TypeScriptLSP: LSPServerInfo = {
id: 'typescript',
root: NearestRoot(
['package-lock.json', 'bun.lockb', 'pnpm-lock.yaml'],
['deno.json']
),
extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs'],
async spawn(root) {
const tsserver = await Bun.resolve('typescript/lib/tsserver.js')
const proc = spawn('bun', ['x', 'typescript-language-server', '--stdio'], {
cwd: root,
env: process.env
})
return {
process: proc,
initialization: { tsserver: { path: tsserver } }
}
}
}
2. LSP 工具暴露
// 诊断工具定义
const lspDiagnosticsTool = {
type: 'function',
name: 'lsp_diagnostics',
description: 'Get LSP diagnostics (errors, warnings) for files in the project',
parameters: {
type: 'object',
properties: {
paths: {
type: 'array',
items: { type: 'string' },
description: 'File paths to get diagnostics for'
}
}
}
}
// 工具实现
async function getLSPDiagnostics(paths: string[]) {
const diagnostics = []
for (const path of paths) {
const uri = pathToUri(path)
const serverDiags = await lspClient.getDiagnostics(uri)
diagnostics.push({
file: path,
issues: serverDiags.map(d => ({
severity: d.severity,
message: d.message,
line: d.range.start.line,
character: d.range.start.character
}))
})
}
return diagnostics
}
3. 语言服务器自动下载
// ESLint服务器自动设置
async spawn(root) {
const serverPath = path.join(
Global.Path.bin,
'vscode-eslint/server/out/eslintServer.js'
)
if (!await exists(serverPath)) {
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
// 自动下载和构建
const response = await fetch(
'https://github.com/microsoft/vscode-eslint/archive/refs/heads/main.zip'
)
await extractAndBuild(response.body, serverPath)
}
return spawnServer(serverPath)
}
Plugin 系统架构
1. Plugin API 设计
// @opencode-ai/plugin 核心接口
interface Plugin {
name: string
version: string
// 钩子函数
onLoad?(): Promise<void>
onChatStart?(session: Session): Promise<void>
onChatEnd?(session: Session): Promise<void>
// 工具注册
tools?: ToolDefinition[]
// 配置扩展
config?: PluginConfig
}
// 示例插件
export default {
name: 'my-plugin',
version: '1.0.0',
onLoad: async () => {
console.log('Plugin loaded')
},
tools: [
tool({
name: 'custom_action',
description: 'Perform custom action',
execute: async (args) => {
return { result: 'done' }
}
})
]
}
2. Plugin 加载机制
// packages/opencode/src/plugin/index.ts
class PluginManager {
private plugins: Map<string, Plugin> = new Map()
async loadPlugins() {
const pluginDirs = [
'./.opencode/plugin',
'~/.config/opencode/plugin'
]
for (const dir of pluginDirs) {
const files = await glob(`${dir}/**/*.ts`)
for (const file of files) {
const module = await import(file)
const plugin = module.default
await this.registerPlugin(plugin)
}
}
}
async registerPlugin(plugin: Plugin) {
// 验证插件
validatePlugin(plugin)
// 执行加载钩子
await plugin.onLoad?.()
// 注册工具
if (plugin.tools) {
for (const tool of plugin.tools) {
toolRegistry.register(tool)
}
}
this.plugins.set(plugin.name, plugin)
}
}
3. 事件系统
// 插件可以监听系统事件
const eventBus = new EventEmitter()
eventBus.on('chat:start', async (session) => {
for (const plugin of plugins.values()) {
await plugin.onChatStart?.(session)
}
})
eventBus.on('tool:execute', async (tool, args) => {
for (const plugin of plugins.values()) {
await plugin.onToolExecute?.(tool, args)
}
})
会话管理系统
1. 会话持久化
// 使用SQLite存储会话
interface Session {
id: string
projectId: string
parentId?: string // 支持会话分支
createdAt: Date
updatedAt: Date
title?: string
agentId: string
}
interface Message {
id: string
sessionId: string
role: 'user' | 'assistant' | 'system' | 'tool'
content: string
toolCalls?: ToolCall[]
createdAt: Date
}
2. 上下文管理
// 智能上下文压缩
async function manageContext(session: Session) {
const messages = await getMessages(session.id)
const tokenCount = estimateTokens(messages)
if (tokenCount > contextLimit) {
// 调用compaction代理进行压缩
const summary = await invokeAgent('compaction', {
messages: messages.slice(0, -10) // 保留最近10条
})
// 替换旧消息为摘要
await replaceMessages(session.id, [{
role: 'system',
content: `Previous conversation summary:\n${summary}`
}])
}
return await getMessages(session.id)
}
3. 会话分支
// 支持从任意点创建新分支
async function branchSession(parentId: string, fromMessageId: string) {
const messages = await getMessagesUntil(parentId, fromMessageId)
const newSession = await Session.create({
projectId: session.projectId,
parentId: parentId,
agentId: session.agentId
})
// 复制消息到新会话
for (const msg of messages) {
await Message.create({
sessionId: newSession.id,
role: msg.role,
content: msg.content
})
}
return newSession
}
配置系统设计
1. 配置文件层级
优先级(高到低):
1. 项目本地:./.opencode/opencode.json
2. 工作树根:<git-root>/.opencode/opencode.json
3. 全局用户:~/.config/opencode/opencode.json
4. 系统默认:内置默认配置
2. 配置合并策略
// packages/opencode/src/config/config.ts
function mergeConfigs(...configs: Config[]): Config {
return configs.reduce((merged, config) => {
return mergeDeep(merged, config, {
// 数组合并策略
arrayMerge: (target, source) => {
return [...source] // 完全覆盖
},
// 对象深度合并
customMerge: (key) => {
if (key === 'agent') {
return (target, source) => ({
...target,
...source
})
}
}
})
}, {})
}
3. 配置验证
// 使用Zod进行运行时验证
import { z } from 'zod'
const ConfigSchema = z.object({
provider: z.string().optional(),
model: z.string().optional(),
temperature: z.number().min(0).max(2).optional(),
agent: z.record(z.object({
model: z.string().optional(),
provider: z.string().optional(),
temperature: z.number().optional(),
permission: z.object({
edit: z.enum(['allow', 'ask', 'deny']).optional(),
bash: z.union([
z.enum(['allow', 'ask', 'deny']),
z.array(z.object({
pattern: z.string(),
action: z.enum(['allow', 'ask', 'deny'])
}))
]).optional()
}).optional()
})).optional(),
tools: z.record(z.boolean()).optional(),
instructions: z.array(z.string()).optional()
})
// 加载和验证配置
async function loadConfig(path: string) {
const raw = await readJSON(path)
return ConfigSchema.parse(raw)
}
与 Claude Code 的对比
核心技术差异
| 特性 | OpenCode | Claude Code |
|---|---|---|
| 开源性 | 完全开源 | 闭源 |
| 模型支持 | 多提供商(Claude/GPT/Gemini/本地) | 仅Claude |
| 架构 | 客户端/服务器分离 | 一体化 |
| LSP集成 | 原生内置 | 需要扩展 |
| 插件系统 | TypeScript原生 | 受限 |
| Skills系统 | 兼容+增强 | 原生 |
| TUI | 主要界面 | 次要 |
| 配置 | JSON+YAML | 主要JSON |
| 部署 | 自托管/云端 | 云端 |
推测 Claude Code 的技术实现
基于 OpenCode 的开源实现和公开文档,可以推测 Claude Code 的核心技术:
1. Skills 系统实现
Claude Code 的 Skills 系统可能采用以下技术方案:
// 推测的实现方式
class ClaudeCodeSkillsSystem {
// 1. Skill 发现
async discoverSkills() {
// 扫描 .claude/skills/ 目录
// 支持marketplace动态下载
// 缓存已下载的skills
}
// 2. Skill 索引
async indexSkill(skillPath: string) {
// 解析SKILL.md的YAML frontmatter
// 建立名称->描述的映射
// 索引resources(scripts/assets/refs)
}
// 3. 上下文注入
async injectSkill(skillName: string, context: Context) {
// 方案A: 系统消息注入
context.messages.unshift({
role: 'system',
content: `<skill>${skillContent}</skill>`
})
// 方案B: 工具调用后注入
// 更节省token,按需加载
}
// 4. Resource访问
async accessResource(skillName: string, resourcePath: string) {
// 安全性检查:只允许访问预索引的路径
// 防止路径遍历攻击
const skill = this.skills.get(skillName)
const safePath = skill.resources.get(resourcePath)
return readFile(safePath)
}
}
2. 推测的Agent协作机制
// Claude Code 可能的实现
class ClaudeCodeAgentSystem {
async executeWithSubagent(
mainAgent: Agent,
task: string,
subagentName: string
) {
// 1. 创建子对话上下文
const subContext = {
parentSessionId: mainAgent.sessionId,
messages: [{
role: 'system',
content: subagents[subagentName].systemPrompt
}, {
role: 'user',
content: task
}]
}
// 2. 执行子对话
const subResult = await this.runAgent(subContext)
// 3. 将结果返回主Agent
return {
role: 'tool',
content: subResult.finalResponse
}
}
}
3. 推测的工具执行沙箱
Claude Code 可能使用以下安全机制:
class ToolExecutionSandbox {
async execute(tool: Tool, args: any) {
// 1. 权限检查
if (!this.checkPermission(tool.name, args)) {
throw new PermissionError()
}
// 2. 资源限制
const timeout = 30000 // 30秒超时
const maxMemory = 512 * 1024 * 1024 // 512MB
// 3. 隔离执行(可能使用V8 Isolate或Worker)
const result = await runInSandbox(
tool.execute,
args,
{ timeout, maxMemory }
)
// 4. 结果验证
return validateToolResult(result)
}
}
性能优化技术
1. 智能上下文管理
// 渐进式上下文加载
class ContextManager {
async buildContext(session: Session) {
const messages = []
// 1. 系统提示词(总是包含)
messages.push(systemPrompt)
// 2. 重要历史(摘要形式)
if (session.messageCount > 20) {
const summary = await this.getSummary(session.id)
messages.push(summary)
}
// 3. 最近对话(完整)
const recentMessages = await this.getRecentMessages(
session.id,
limit: 10
)
messages.push(...recentMessages)
// 4. 当前用户输入
messages.push(currentInput)
return messages
}
}
2. 工具调用批处理
// 批量执行工具调用以减少往返
async function executeToolsBatch(toolCalls: ToolCall[]) {
// 识别可并行执行的工具
const parallelizable = toolCalls.filter(
t => !hasSideEffects(t)
)
// 并行执行
const results = await Promise.all(
parallelizable.map(t => executeTool(t))
)
return results
}
3. LSP 客户端缓存
class LSPClientCache {
private diagnosticsCache = new Map<string, CachedDiagnostics>()
async getDiagnostics(uri: string) {
const cached = this.diagnosticsCache.get(uri)
if (cached && !cached.isStale()) {
return cached.data
}
const fresh = await lspClient.getDiagnostics(uri)
this.diagnosticsCache.set(uri, {
data: fresh,
timestamp: Date.now(),
ttl: 5000 // 5秒缓存
})
return fresh
}
}
安全性设计
1. 命令执行沙箱
// Bash工具的安全执行
async function executeBashCommand(
command: string,
permission: Permission
) {
// 1. 黑名单检查
const dangerousPatterns = [
/rm\s+-rf\s+\//,
/:\(\)\{\s*:\|:&\s*\};:/, // Fork bomb
/> \/dev\/sd[a-z]/ // 直接写硬盘
]
if (dangerousPatterns.some(p => p.test(command))) {
throw new Error('Dangerous command blocked')
}
// 2. 用户确认(如果需要)
if (permission === 'ask') {
const approved = await askUser(command)
if (!approved) return
}
// 3. 限制执行环境
const result = await exec(command, {
cwd: projectRoot,
timeout: 30000,
maxBuffer: 1024 * 1024, // 1MB输出限制
env: sanitizeEnv(process.env)
})
return result
}
2. 文件系统访问控制
class FileSystemGuard {
private allowedPaths: Set<string>
constructor(projectRoot: string) {
this.allowedPaths = new Set([
projectRoot,
path.join(os.homedir(), '.opencode'),
path.join(os.homedir(), '.claude')
])
}
validatePath(filePath: string) {
const normalized = path.normalize(filePath)
const absolute = path.resolve(normalized)
// 检查是否在允许的路径内
const allowed = Array.from(this.allowedPaths).some(
allowedPath => absolute.startsWith(allowedPath)
)
if (!allowed) {
throw new SecurityError(
`Access to ${absolute} is not allowed`
)
}
return absolute
}
}
扩展性设计
1. MCP (Model Context Protocol) 集成
// MCP服务器配置
interface MCPServerConfig {
command: string
args: string[]
env?: Record<string, string>
}
// 启动MCP服务器
async function startMCPServer(config: MCPServerConfig) {
const proc = spawn(config.command, config.args, {
env: { ...process.env, ...config.env },
stdio: ['pipe', 'pipe', 'pipe']
})
const client = new MCPClient(proc.stdin, proc.stdout)
await client.initialize()
return client
}
// 将MCP工具暴露给Agent
async function exposeMCPTools(client: MCPClient) {
const tools = await client.listTools()
for (const tool of tools) {
toolRegistry.register({
name: `mcp_${tool.name}`,
description: tool.description,
parameters: tool.inputSchema,
execute: async (args) => {
return await client.callTool(tool.name, args)
}
})
}
}
2. 多模型并行策略
// 同时使用多个模型并比较结果
async function parallelModelStrategy(
prompt: string,
models: string[]
) {
const results = await Promise.all(
models.map(model =>
invokeModel(model, prompt)
)
)
// 使用投票或置信度选择最佳结果
return selectBestResult(results)
}
// 快速模型预筛选 + 强模型精确处理
async function tieredModelStrategy(task: Task) {
// 1. 使用快速模型判断任务复杂度
const complexity = await invokeModel(
'claude-haiku-4',
`Rate task complexity (1-10): ${task.description}`
)
// 2. 根据复杂度选择模型
const model = complexity > 7
? 'claude-opus-4'
: 'claude-sonnet-4'
return await invokeModel(model, task.description)
}
总结
OpenCode 的核心技术原理体现了以下设计哲学:
- 模块化架构:清晰的关注点分离,易于扩展和维护
- 性能优先:使用 Bun、智能缓存、并行执行等优化
- 安全第一:细粒度权限、沙箱执行、路径验证
- 开放生态:插件系统、MCP集成、多模型支持
- 开发者友好:TypeScript类型安全、完整文档、活跃社区
通过这些技术实现,OpenCode 成功地创建了一个既强大又灵活的 AI 编码代理平台,为开发者提供了 Claude Code 的开源替代方案,同时在某些方面(如 LSP 集成、插件系统)提供了更强大的功能。
其 Skills 系统的实现特别值得关注:
- 按需加载避免上下文污染
- 资源索引确保安全访问
- 标准格式(SKILL.md)易于创建和分享
- 多层配置支持全局、项目、代理级别的精细控制
欢迎关注公众号,一起学习、一起进步。