persona-core-opencode
v1.3.1
Published
PersonaCore implementation using opencode as the execution engine
Downloads
18
Maintainers
Readme
persona-core-opencode
使用 opencode 作为执行引擎的 PersonaCore 实现。
PersonaCore 是一个 AI 人格/角色框架,允许你定义具有特定能力和行为风格的 AI 角色,并通过程序化的方式与其交互。
特性
- 声明式人格定义:通过 TypeScript 类型定义人格的系统提示和可用能力
- 动态工具生成:根据 ActionNodeType 自动生成 opencode 兼容的工具文件
- 多种执行类型:支持 API 调用、文件读写、HTTP 请求、LLM 调用等
- 会话管理:自动管理会话状态,支持多轮对话
- 工具调用追踪:记录所有工具调用历史
- 流式输出:支持实时回调获取 AI 响应和工具调用过程
- OSS 对象存储:支持将数据上传到 S3 兼容的对象存储服务
安装
npm install persona-core-opencode前置要求
- Node.js >= 18
- opencode CLI 已安装并配置
快速开始
1. 创建客户端
import { PersonaCoreClient, CreatePersonaInput, ActionNodeType } from 'persona-core-opencode';
// 创建客户端实例
const client = await PersonaCoreClient.create({
workspaceRoot: './workspace', // 可选:工作空间根目录
opencodePath: 'opencode', // 可选:opencode 命令路径
defaultTimeoutMs: 60000, // 可选:默认超时时间
});2. 定义并创建 Persona
// 定义动作类型
const actionTypes: ActionNodeType[] = [
{
kind: 'action',
id: 'save_note',
name: '保存笔记',
description: '将笔记内容保存到文件',
executionType: 'file_write',
inputFields: [
{ name: 'filePath', required: true, typeHint: 'string', description: '文件路径' },
{ name: 'content', required: true, typeHint: 'string', description: '笔记内容' },
],
outputFields: [
{ name: 'success', required: true, typeHint: 'boolean' },
{ name: 'filePath', required: true, typeHint: 'string' },
],
},
];
// 创建 Persona
const personaId = await client.createPersona({
name: '笔记助手',
description: '帮助用户记录和整理笔记',
systemPrompt: `你是一个专业的笔记助手。
你可以使用 save_note 工具来保存用户的笔记。
始终使用友好、专业的语气与用户交流。`,
actionTypes,
});
console.log(`Persona created: ${personaId}`);3. 发送消息
// 发送消息并获取响应
const result = await client.sendMessage(
personaId,
'请帮我记录今天的会议纪要:项目进度良好,下周发布新版本。'
);
console.log('Response:', result.response);
console.log('Tool calls:', result.toolCalls);
// 继续对话(使用相同的 session)
const followUp = await client.sendMessage(
personaId,
'再补充一点:需要准备发布文档。',
{ sessionId: result.sessionId }
);4. 清理资源
// 删除 Persona
await client.deletePersona(personaId);
// 或清理所有资源
await client.cleanup();核心概念
Persona(人格)
Persona 是 PersonaCore 的核心概念,代表一个具有特定能力和行为风格的 AI 角色:
interface Persona {
id: PersonaId; // 唯一标识
name: string; // 名称
description?: string; // 描述
systemPrompt: string; // 系统提示词,定义角色的行为风格
actionTypes: ActionNodeType[]; // 可用的动作类型
createdAt: Date;
updatedAt: Date;
}ActionNodeType(动作节点类型)
定义 Persona 可以执行的具体动作:
interface ActionNodeType {
kind: 'action';
id: ActionTypeId; // 动作唯一标识
name: string; // 语义名称
description?: string; // 描述
executionType: ActionExecutionType; // 执行类型
apiConfig?: ApiCallConfig; // API 调用配置
asyncMode?: AsyncModeConfig; // 异步模式配置
inputFields: FieldDefinition[]; // 输入字段
outputFields: FieldDefinition[]; // 输出字段
}执行类型(ActionExecutionType)
系统内置的执行类型:
| 类型 | 说明 | 用途 |
|-----|------|------|
| api_call | HTTP API 调用 | 调用外部 API 服务 |
| file_write | 文件写入 | 保存内容到文件 |
| file_read | 文件读取 | 读取文件内容 |
| http_request | 通用 HTTP 请求 | 发送 HTTP 请求 |
| llm_call | LLM 调用 | 调用语言模型处理 |
| database_query | 数据库查询 | 执行数据库操作 |
| shell_command | Shell 命令 | 执行系统命令 |
API 参考
PersonaCoreClient
主要的客户端 API 类。
构造函数
const client = new PersonaCoreClient(config?: PersonaCoreClientConfig);
// 或使用静态工厂方法
const client = await PersonaCoreClient.create(config?: PersonaCoreClientConfig);配置选项:
| 属性 | 类型 | 默认值 | 说明 |
|-----|------|-------|------|
| workspaceRoot | string | process.cwd() | 工作空间根目录 |
| opencodePath | string | 'opencode' | opencode 命令路径 |
| defaultTimeoutMs | number | 30000 | 默认超时时间(毫秒) |
方法
createPersona(input: CreatePersonaInput): Promise<string>
创建新的 Persona。
const personaId = await client.createPersona({
name: 'My Assistant',
systemPrompt: 'You are a helpful assistant.',
actionTypes: [...],
});deletePersona(personaId: string): Promise<void>
删除 Persona 及其关联的所有 session。
await client.deletePersona(personaId);getPersona(personaId: string): RegisteredPersona | undefined
获取已注册的 Persona。
const persona = client.getPersona(personaId);hasPersona(personaId: string): boolean
检查 Persona 是否存在。
if (client.hasPersona(personaId)) {
// Persona exists
}sendMessage(personaId: string, message: string, options?: SendMessageOptions): Promise<SessionExecutionResult>
向 Persona 发送消息并获取响应。
const result = await client.sendMessage(personaId, 'Hello!', {
sessionId: 'existing-session-id', // 可选:继续已有会话
timeoutMs: 60000, // 可选:超时时间
});返回值 SessionExecutionResult:
interface SessionExecutionResult {
sessionId: string; // 会话 ID
success: boolean; // 是否成功
response?: string; // 响应文本
toolCalls: ToolCallRecord[]; // 工具调用记录
error?: string; // 错误信息
}
interface ToolCallRecord {
tool: string; // 工具名称
args: Record<string, unknown>; // 调用参数
result?: string; // 调用结果
error?: string; // 错误信息
}sendMessageStreaming(personaId: string, message: string, options: SendMessageStreamingOptions): Promise<SessionExecutionResult>
向 Persona 发送消息并通过回调实时获取输出(流式版本)。
const result = await client.sendMessageStreaming(personaId, 'Hello!', {
sessionId: 'existing-session-id', // 可选:继续已有会话
timeoutMs: 60000, // 可选:超时时间
callbacks: {
// 每行 JSON 输出的回调(line: 原始行,parsed: 解析后的对象或 null)
onLine: (line, parsed) => {
if (parsed?.type === 'text') {
process.stdout.write(parsed.part?.text || '');
} else if (parsed?.type === 'tool_use') {
console.log('Tool called:', parsed.part?.tool);
}
},
// 可选:标准错误输出回调
onStderr: (data) => console.error('stderr:', data),
// 可选:进程退出回调
onExit: (code) => console.log('Exit code:', code),
},
});回调类型定义:
// 流式输出回调函数类型
type StreamLineCallback = (
line: string, // 原始 JSON 行内容
parsed: Record<string, unknown> | null // 解析后的对象,失败时为 null
) => void;
interface StreamCallbacks {
onLine?: StreamLineCallback; // 每行输出回调
onStderr?: (data: string) => void; // stderr 回调
onExit?: (code: number | null) => void; // 进程退出回调
}opencode 输出的事件类型:
| 事件类型 | 说明 | 包含内容 |
|---------|------|---------|
| step_start | 步骤开始 | sessionID, timestamp |
| tool_use | 工具调用完成 | tool, input, output, status |
| text | 文本输出 | text content |
| step_finish | 步骤结束 | cost, tokens (input/output/reasoning) |
| error | 错误 | error name, message |
cleanup(): Promise<void>
清理所有资源(关闭 session、删除工作空间)。
await client.cleanup();API 调用配置
对于 api_call 类型的动作,可以通过 apiConfig 配置详细的 HTTP 请求:
const apiAction: ActionNodeType = {
kind: 'action',
id: 'search_web',
name: '网页搜索',
description: '搜索网页内容',
executionType: 'api_call',
apiConfig: {
url: 'https://api.example.com/search?q=${query}',
method: 'GET',
headers: {
type: 'static',
values: {
'Authorization': 'Bearer ${API_KEY}',
'Content-Type': 'application/json',
},
},
response: {
type: 'json',
extract: {
results: 'data.items',
total: 'meta.total',
},
},
timeoutMs: 30000,
},
inputFields: [
{ name: 'query', required: true, typeHint: 'string', description: '搜索关键词' },
],
outputFields: [
{ name: 'results', required: true, typeHint: 'Array<{title: string, url: string}>' },
{ name: 'total', required: true, typeHint: 'number' },
],
};URL 模板
URL 支持变量替换:
url: 'https://api.example.com/users/${userId}/posts/${postId}'请求头配置
静态请求头:
headers: {
type: 'static',
values: {
'Authorization': 'Bearer token',
'X-Custom-Header': 'value',
},
}模板请求头(使用 Nunjucks):
headers: {
type: 'template',
template: '{"Authorization": "Bearer {{ apiKey }}", "X-User": "{{ userId }}"}',
}请求体配置
JSON 请求体:
body: {
contentType: 'json',
includeFields: ['title', 'content'], // 只包含指定字段
// 或
excludeFields: ['internal'], // 排除指定字段
}模板请求体:
body: {
contentType: 'template',
template: '{"query": "{{ query }}", "limit": {{ limit | default(10) }}}',
}响应处理
提取字段(使用 JSONPath):
response: {
type: 'json',
extract: {
imageUrl: 'data.images.0.url',
status: 'meta.status',
},
}保存到文件:
response: {
type: 'json',
saveToFile: {
enabled: true,
pathField: 'outputPath', // 从输入参数获取路径
dataPath: 'data.image', // 从响应中提取数据
dataFormat: 'base64', // 数据格式
outputField: 'savedFilePath', // 输出字段名
},
}保存到对象存储(OSS):
response: {
type: 'json',
saveToOss: {
enabled: true,
bucket: 'my-bucket', // OSS bucket 名称
keyField: 'ossKey', // 从输入参数获取存储路径
dataPath: 'data.image', // 从响应中提取数据
sourceType: 'auto', // 'base64' | 'url' | 'auto'
contentType: 'image/png', // 可选:MIME 类型
outputField: 'ossUrl', // 输出字段名
},
}异步任务支持
对于需要轮询等待结果的异步 API,可以配置 asyncMode:
const asyncAction: ActionNodeType = {
kind: 'action',
id: 'generate_video',
name: '生成视频',
executionType: 'api_call',
apiConfig: {
url: 'https://api.example.com/video/generate',
method: 'POST',
body: { contentType: 'json' },
},
asyncMode: {
isAsync: true,
defaultCheckIntervalMs: 5000,
defaultTimeoutMs: 300000,
taskIdPath: 'data.taskId',
statusCheck: {
url: 'https://api.example.com/video/status/${taskId}',
method: 'GET',
extract: {
statusPath: 'status',
completedValues: ['completed', 'success'],
failedValues: ['failed', 'error'],
progressPath: 'progress',
},
},
resultFetch: {
extractFromStatus: {
videoUrl: 'data.videoUrl',
},
},
},
inputFields: [...],
outputFields: [...],
};OSS 服务(对象存储)
PersonaCore 提供了 OssService 用于将数据上传到 S3 兼容的对象存储服务(如 MinIO、阿里云 OSS、AWS S3 等)。
配置
在 .env 中配置 OSS 相关环境变量:
OSS_ENDPOINT=http://localhost:9000 # S3 兼容的 endpoint
OSS_ACCESS_KEY_ID=minioadmin # Access Key
OSS_SECRET_ACCESS_KEY=minioadmin # Secret Key
OSS_REGION=cn-beijing # 可选基本使用
import { OssService, createOssServiceFromEnv } from 'persona-core-opencode';
// 从环境变量创建
const ossService = createOssServiceFromEnv();
// 或手动配置
const ossService = new OssService({
endpoint: 'http://localhost:9000',
accessKeyId: 'minioadmin',
secretAccessKey: 'minioadmin',
region: 'cn-beijing',
});上传方式
1. 上传 Base64 数据:
const result = await ossService.uploadFromBase64(
'my-bucket',
'images/photo.png',
'data:image/png;base64,iVBORw0KGgoAAAA...',
{ contentType: 'image/png' }
);
console.log(result.url); // http://localhost:9000/my-bucket/images/photo.png2. 从 URL 下载后上传:
const result = await ossService.uploadFromUrl(
'my-bucket',
'videos/movie.mp4',
'https://example.com/video.mp4'
);3. 上传 Buffer:
const buffer = Buffer.from('Hello, World!', 'utf8');
const result = await ossService.uploadFromBuffer(
'my-bucket',
'files/hello.txt',
buffer,
{ contentType: 'text/plain' }
);使用 SaveToOssConfig
最灵活的方式是使用 uploadWithConfig(),可以自动处理各种数据格式:
import { SaveToOssConfig } from 'persona-core-opencode';
// API 响应
const apiResponse = {
status: 'success',
data: { images: [{ base64: 'iVBORw0KGgoAAAA...' }] }
};
// 配置
const config: SaveToOssConfig = {
enabled: true,
bucket: 'my-bucket',
keyField: 'ossKey', // 从 inputs 中获取存储路径
dataPath: 'data.images[0].base64', // JSONPath 提取数据
sourceType: 'auto', // 自动检测 base64 或 URL
outputField: 'ossUrl',
};
// 上传
const result = await ossService.uploadWithConfig(
apiResponse,
config,
{ ossKey: 'generated/image.png' }
);
console.log(result);
// { success: true, url: '...', key: '...', bucket: '...', bytesUploaded: 70, contentType: 'image/png' }SaveToOssConfig 字段
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| enabled | boolean | 是 | 是否启用 |
| bucket | string | 是 | OSS bucket 名称 |
| keyField | string | 是 | 从 inputs 中获取 key 的字段名 |
| dataPath | string | 否 | JSONPath 从响应中提取数据 |
| sourceType | 'base64' | 'url' | 'auto' | 否 | 数据类型,默认 'auto' |
| contentType | string | 否 | MIME 类型,可自动推断 |
| outputField | string | 否 | 输出字段名,默认 'ossUrl' |
异步任务结果上传
在 asyncMode.resultFetch 中配置 saveToOss,可以在异步任务完成后自动上传结果:
const videoAction: ActionNodeType = {
kind: 'action',
id: 'generate_video_to_oss',
name: '生成视频并上传到 OSS',
executionType: 'api_call',
apiConfig: { ... },
asyncMode: {
isAsync: true,
taskIdPath: 'data.taskId',
statusCheck: { ... },
resultFetch: {
extractFromStatus: { videoUrl: 'data.videoUrl' },
// 任务完成后自动上传到 OSS
saveToOss: {
enabled: true,
bucket: 'videos',
keyField: 'ossKey',
dataPath: 'data.videoUrl',
sourceType: 'url',
outputField: 'ossUrl',
},
},
},
inputFields: [
{ name: 'prompt', required: true, typeHint: 'string' },
{ name: 'ossKey', required: true, typeHint: 'string', description: 'OSS 存储路径' },
],
outputFields: [
{ name: 'ossUrl', required: true, typeHint: 'string', description: '上传后的 OSS URL' },
],
};PersonaCoreClient 自动 saveToOss
将 OssService 传入 PersonaCoreClient,工具调用完成后会自动根据 ActionType 配置执行 saveToOss:
import {
PersonaCoreClient,
createOssServiceFromEnv,
ActionNodeType,
OssUploadRecord,
} from 'persona-core-opencode';
// 1. 创建 OssService(从环境变量)
const ossService = createOssServiceFromEnv();
// 2. 创建带 ossService 的客户端
const client = new PersonaCoreClient({
ossService, // 传入 OssService
});
// 3. 定义带 saveToOss 配置的 ActionType
const imageAction: ActionNodeType = {
kind: 'action',
id: 'generate_image',
name: '生成图片',
executionType: 'api_call',
apiConfig: {
url: 'https://api.example.com/generate',
method: 'POST',
response: {
saveToOss: {
enabled: true,
bucket: 'images',
keyField: 'ossKey',
dataPath: 'data.image',
sourceType: 'auto',
outputField: 'ossUrl',
},
},
},
inputFields: [
{ name: 'prompt', required: true, typeHint: 'string' },
{ name: 'ossKey', required: true, typeHint: 'string' },
],
outputFields: [
{ name: 'ossUrl', required: true, typeHint: 'string' },
],
};
// 4. 创建 Persona 并发送消息
const personaId = await client.createPersona({
name: 'Image Generator',
systemPrompt: 'You are an image generation assistant.',
actionTypes: [imageAction],
});
const result = await client.sendMessage(personaId, 'Generate an image of a cat');
// 5. 检查自动上传结果
if (result.ossUploads && result.ossUploads.length > 0) {
for (const upload of result.ossUploads) {
if (upload.success) {
console.log(`Tool: ${upload.tool}`);
console.log(`Uploaded to: ${upload.url}`);
console.log(`Bytes: ${upload.bytesUploaded}`);
} else {
console.error(`Upload failed: ${upload.error}`);
}
}
}OssUploadRecord 字段
| 字段 | 类型 | 说明 |
|------|------|------|
| tool | string | 触发上传的工具名称 |
| success | boolean | 上传是否成功 |
| url | string | 上传后的对象 URL |
| key | string | 对象的 key |
| bucket | string | bucket 名称 |
| bytesUploaded | number | 上传的字节数 |
| contentType | string | MIME 类型 |
| error | string | 错误信息(失败时) |
项目结构
persona-core-opencode/
├── src/
│ ├── index.ts # 主入口,导出公共 API
│ ├── client/
│ │ └── personaCoreClient.ts # 客户端 API
│ ├── persona/
│ │ └── personaRegistry.ts # Persona 注册和管理
│ ├── session/
│ │ ├── sessionManager.ts # 会话管理和执行
│ │ └── sessionHandle.ts # 会话句柄
│ ├── generators/
│ │ └── toolFileGenerator.ts # 工具文件生成
│ ├── services/
│ │ └── ossService.ts # OSS 对象存储服务
│ └── types/
│ ├── common.ts # 通用类型
│ ├── nodes.ts # 节点类型定义
│ └── persona.ts # Persona 类型定义
├── suites/ # 示例 Persona 套件
│ ├── comic/ # 漫画生成 Persona
│ └── humor/ # 幽默安慰师 Persona
├── tests/ # 测试文件
│ ├── unit/ # 单元测试
│ └── integration/ # 集成测试
├── package.json
├── tsconfig.json
└── vitest.config.ts工作原理
┌─────────────────────────────────────────────────────────────────┐
│ PersonaCoreClient │
├─────────────────────────────────────────────────────────────────┤
│ createPersona() → PersonaRegistry.registerPersona() │
│ │ │ │
│ │ ├── 创建工作空间目录 │
│ │ ├── 生成 .opencode/tools/*.ts │
│ │ └── 生成 .opencode/rules/persona.md │
│ │ │
│ sendMessage() → SessionManager.executeThinking() │
│ │ │ │
│ │ ├── 启动 opencode run CLI │
│ │ ├── 通过 stdin 发送消息 │
│ │ ├── 解析 stdout JSONL 输出 │
│ │ └── 返回响应和工具调用记录 │
└─────────────────────────────────────────────────────────────────┘Persona 注册:当创建 Persona 时,系统会:
- 在工作空间中创建目录结构
- 根据
actionTypes生成 opencode 工具文件(TypeScript) - 将
systemPrompt保存为 opencode 规则文件
消息执行:当发送消息时,系统会:
- 创建或复用 session
- 启动
opencode runCLI 进程 - 通过 stdin 发送用户消息
- 解析 stdout 的 JSONL 输出
- 提取响应文本和工具调用记录
环境变量
参考 .env.example 配置所需的环境变量:
# LLM Provider(根据实际使用的 API 配置)
DEEPSEEK_KEY=your-api-key
DEEPSEEK_MODEL=deepseek-v3
DEEPSEEK_URL=https://api.deepseek.com
# 外部服务 API
SERPER_KEY=your-serper-key # 网页搜索
JINA_KEY=your-jina-key # 网页内容获取
# 对象存储(如需要保存文件到 OSS)
OSS_ENDPOINT=http://localhost:9000
OSS_ACCESS_KEY_ID=minioadmin
OSS_SECRET_ACCESS_KEY=minioadmin开发
安装依赖
npm install构建
npm run build运行测试
# 运行所有测试
npm run test
# 监听模式
npm run test:watch
# 类型检查
npm run typecheck运行示例套件
npm run suite发布
# 补丁版本
npm run release:patch
# 次要版本
npm run release:minor
# 主要版本
npm run release:major示例:创建一个幽默安慰师
import { PersonaCoreClient, ActionNodeType } from 'persona-core-opencode';
const actionTypes: ActionNodeType[] = [
{
kind: 'action',
id: 'reply',
name: '回复',
description: '生成幽默的回复内容并保存到文件',
executionType: 'file_write',
inputFields: [
{ name: 'filePath', required: true, typeHint: 'string', description: '保存路径' },
{ name: 'content', required: true, typeHint: 'string', description: '回复内容' },
],
outputFields: [
{ name: 'success', required: true, typeHint: 'boolean' },
{ name: 'filePath', required: true, typeHint: 'string' },
],
},
];
async function main() {
const client = await PersonaCoreClient.create();
const personaId = await client.createPersona({
name: '幽默安慰师',
description: '用幽默的方式安慰别人',
systemPrompt: `你是一个幽默的人,特别擅长用幽默的话安慰别人。
你的特点:
1. 总能用轻松幽默的方式化解对方的负面情绪
2. 善于用比喻和类比来让人开心
3. 会在安慰中加入恰到好处的调侃,但不会伤害对方
当用户向你倾诉时,使用 reply 工具保存你的幽默回复。`,
actionTypes,
});
const result = await client.sendMessage(
personaId,
'今天代码写了一天都没跑通,太难受了...'
);
console.log('幽默安慰师的回复:', result.response);
console.log('保存的文件:', result.toolCalls);
await client.cleanup();
}
main().catch(console.error);许可证
MIT License
