persona-core-opencode 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
ADDED
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
# persona-core-opencode
|
|
2
|
+
|
|
3
|
+
使用 [opencode](https://github.com/opencode-ai/opencode) 作为执行引擎的 PersonaCore 实现。
|
|
4
|
+
|
|
5
|
+
PersonaCore 是一个 AI 人格/角色框架,允许你定义具有特定能力和行为风格的 AI 角色,并通过程序化的方式与其交互。
|
|
6
|
+
|
|
7
|
+
## 特性
|
|
8
|
+
|
|
9
|
+
- **声明式人格定义**:通过 TypeScript 类型定义人格的系统提示和可用能力
|
|
10
|
+
- **动态工具生成**:根据 ActionNodeType 自动生成 opencode 兼容的工具文件
|
|
11
|
+
- **多种执行类型**:支持 API 调用、文件读写、HTTP 请求、LLM 调用等
|
|
12
|
+
- **会话管理**:自动管理会话状态,支持多轮对话
|
|
13
|
+
- **工具调用追踪**:记录所有工具调用历史
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install persona-core-opencode
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 前置要求
|
|
22
|
+
|
|
23
|
+
- Node.js >= 18
|
|
24
|
+
- [opencode CLI](https://github.com/opencode-ai/opencode) 已安装并配置
|
|
25
|
+
|
|
26
|
+
## 快速开始
|
|
27
|
+
|
|
28
|
+
### 1. 创建客户端
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { PersonaCoreClient, CreatePersonaInput, ActionNodeType } from 'persona-core-opencode';
|
|
32
|
+
|
|
33
|
+
// 创建客户端实例
|
|
34
|
+
const client = await PersonaCoreClient.create({
|
|
35
|
+
workspaceRoot: './workspace', // 可选:工作空间根目录
|
|
36
|
+
opencodePath: 'opencode', // 可选:opencode 命令路径
|
|
37
|
+
defaultTimeoutMs: 60000, // 可选:默认超时时间
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. 定义并创建 Persona
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// 定义动作类型
|
|
45
|
+
const actionTypes: ActionNodeType[] = [
|
|
46
|
+
{
|
|
47
|
+
kind: 'action',
|
|
48
|
+
id: 'save_note',
|
|
49
|
+
name: '保存笔记',
|
|
50
|
+
description: '将笔记内容保存到文件',
|
|
51
|
+
executionType: 'file_write',
|
|
52
|
+
inputFields: [
|
|
53
|
+
{ name: 'filePath', required: true, typeHint: 'string', description: '文件路径' },
|
|
54
|
+
{ name: 'content', required: true, typeHint: 'string', description: '笔记内容' },
|
|
55
|
+
],
|
|
56
|
+
outputFields: [
|
|
57
|
+
{ name: 'success', required: true, typeHint: 'boolean' },
|
|
58
|
+
{ name: 'filePath', required: true, typeHint: 'string' },
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
// 创建 Persona
|
|
64
|
+
const personaId = await client.createPersona({
|
|
65
|
+
name: '笔记助手',
|
|
66
|
+
description: '帮助用户记录和整理笔记',
|
|
67
|
+
systemPrompt: `你是一个专业的笔记助手。
|
|
68
|
+
你可以使用 save_note 工具来保存用户的笔记。
|
|
69
|
+
始终使用友好、专业的语气与用户交流。`,
|
|
70
|
+
actionTypes,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.log(`Persona created: ${personaId}`);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 3. 发送消息
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// 发送消息并获取响应
|
|
80
|
+
const result = await client.sendMessage(
|
|
81
|
+
personaId,
|
|
82
|
+
'请帮我记录今天的会议纪要:项目进度良好,下周发布新版本。'
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
console.log('Response:', result.response);
|
|
86
|
+
console.log('Tool calls:', result.toolCalls);
|
|
87
|
+
|
|
88
|
+
// 继续对话(使用相同的 session)
|
|
89
|
+
const followUp = await client.sendMessage(
|
|
90
|
+
personaId,
|
|
91
|
+
'再补充一点:需要准备发布文档。',
|
|
92
|
+
{ sessionId: result.sessionId }
|
|
93
|
+
);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 4. 清理资源
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// 删除 Persona
|
|
100
|
+
await client.deletePersona(personaId);
|
|
101
|
+
|
|
102
|
+
// 或清理所有资源
|
|
103
|
+
await client.cleanup();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## 核心概念
|
|
107
|
+
|
|
108
|
+
### Persona(人格)
|
|
109
|
+
|
|
110
|
+
Persona 是 PersonaCore 的核心概念,代表一个具有特定能力和行为风格的 AI 角色:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
interface Persona {
|
|
114
|
+
id: PersonaId; // 唯一标识
|
|
115
|
+
name: string; // 名称
|
|
116
|
+
description?: string; // 描述
|
|
117
|
+
systemPrompt: string; // 系统提示词,定义角色的行为风格
|
|
118
|
+
actionTypes: ActionNodeType[]; // 可用的动作类型
|
|
119
|
+
createdAt: Date;
|
|
120
|
+
updatedAt: Date;
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### ActionNodeType(动作节点类型)
|
|
125
|
+
|
|
126
|
+
定义 Persona 可以执行的具体动作:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
interface ActionNodeType {
|
|
130
|
+
kind: 'action';
|
|
131
|
+
id: ActionTypeId; // 动作唯一标识
|
|
132
|
+
name: string; // 语义名称
|
|
133
|
+
description?: string; // 描述
|
|
134
|
+
executionType: ActionExecutionType; // 执行类型
|
|
135
|
+
apiConfig?: ApiCallConfig; // API 调用配置
|
|
136
|
+
asyncMode?: AsyncModeConfig; // 异步模式配置
|
|
137
|
+
inputFields: FieldDefinition[]; // 输入字段
|
|
138
|
+
outputFields: FieldDefinition[]; // 输出字段
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 执行类型(ActionExecutionType)
|
|
143
|
+
|
|
144
|
+
系统内置的执行类型:
|
|
145
|
+
|
|
146
|
+
| 类型 | 说明 | 用途 |
|
|
147
|
+
|-----|------|------|
|
|
148
|
+
| `api_call` | HTTP API 调用 | 调用外部 API 服务 |
|
|
149
|
+
| `file_write` | 文件写入 | 保存内容到文件 |
|
|
150
|
+
| `file_read` | 文件读取 | 读取文件内容 |
|
|
151
|
+
| `http_request` | 通用 HTTP 请求 | 发送 HTTP 请求 |
|
|
152
|
+
| `llm_call` | LLM 调用 | 调用语言模型处理 |
|
|
153
|
+
| `database_query` | 数据库查询 | 执行数据库操作 |
|
|
154
|
+
| `shell_command` | Shell 命令 | 执行系统命令 |
|
|
155
|
+
|
|
156
|
+
## API 参考
|
|
157
|
+
|
|
158
|
+
### PersonaCoreClient
|
|
159
|
+
|
|
160
|
+
主要的客户端 API 类。
|
|
161
|
+
|
|
162
|
+
#### 构造函数
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
const client = new PersonaCoreClient(config?: PersonaCoreClientConfig);
|
|
166
|
+
// 或使用静态工厂方法
|
|
167
|
+
const client = await PersonaCoreClient.create(config?: PersonaCoreClientConfig);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**配置选项:**
|
|
171
|
+
|
|
172
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
173
|
+
|-----|------|-------|------|
|
|
174
|
+
| `workspaceRoot` | `string` | `process.cwd()` | 工作空间根目录 |
|
|
175
|
+
| `opencodePath` | `string` | `'opencode'` | opencode 命令路径 |
|
|
176
|
+
| `defaultTimeoutMs` | `number` | `30000` | 默认超时时间(毫秒) |
|
|
177
|
+
|
|
178
|
+
#### 方法
|
|
179
|
+
|
|
180
|
+
##### `createPersona(input: CreatePersonaInput): Promise<string>`
|
|
181
|
+
|
|
182
|
+
创建新的 Persona。
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
const personaId = await client.createPersona({
|
|
186
|
+
name: 'My Assistant',
|
|
187
|
+
systemPrompt: 'You are a helpful assistant.',
|
|
188
|
+
actionTypes: [...],
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
##### `deletePersona(personaId: string): Promise<void>`
|
|
193
|
+
|
|
194
|
+
删除 Persona 及其关联的所有 session。
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
await client.deletePersona(personaId);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
##### `getPersona(personaId: string): RegisteredPersona | undefined`
|
|
201
|
+
|
|
202
|
+
获取已注册的 Persona。
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const persona = client.getPersona(personaId);
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
##### `hasPersona(personaId: string): boolean`
|
|
209
|
+
|
|
210
|
+
检查 Persona 是否存在。
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
if (client.hasPersona(personaId)) {
|
|
214
|
+
// Persona exists
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
##### `sendMessage(personaId: string, message: string, options?: SendMessageOptions): Promise<SessionExecutionResult>`
|
|
219
|
+
|
|
220
|
+
向 Persona 发送消息并获取响应。
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
const result = await client.sendMessage(personaId, 'Hello!', {
|
|
224
|
+
sessionId: 'existing-session-id', // 可选:继续已有会话
|
|
225
|
+
timeoutMs: 60000, // 可选:超时时间
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**返回值 `SessionExecutionResult`:**
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
interface SessionExecutionResult {
|
|
233
|
+
sessionId: string; // 会话 ID
|
|
234
|
+
success: boolean; // 是否成功
|
|
235
|
+
response?: string; // 响应文本
|
|
236
|
+
toolCalls: ToolCallRecord[]; // 工具调用记录
|
|
237
|
+
error?: string; // 错误信息
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
interface ToolCallRecord {
|
|
241
|
+
tool: string; // 工具名称
|
|
242
|
+
args: Record<string, unknown>; // 调用参数
|
|
243
|
+
result?: string; // 调用结果
|
|
244
|
+
error?: string; // 错误信息
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
##### `cleanup(): Promise<void>`
|
|
249
|
+
|
|
250
|
+
清理所有资源(关闭 session、删除工作空间)。
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
await client.cleanup();
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## API 调用配置
|
|
257
|
+
|
|
258
|
+
对于 `api_call` 类型的动作,可以通过 `apiConfig` 配置详细的 HTTP 请求:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
const apiAction: ActionNodeType = {
|
|
262
|
+
kind: 'action',
|
|
263
|
+
id: 'search_web',
|
|
264
|
+
name: '网页搜索',
|
|
265
|
+
description: '搜索网页内容',
|
|
266
|
+
executionType: 'api_call',
|
|
267
|
+
apiConfig: {
|
|
268
|
+
url: 'https://api.example.com/search?q=${query}',
|
|
269
|
+
method: 'GET',
|
|
270
|
+
headers: {
|
|
271
|
+
type: 'static',
|
|
272
|
+
values: {
|
|
273
|
+
'Authorization': 'Bearer ${API_KEY}',
|
|
274
|
+
'Content-Type': 'application/json',
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
response: {
|
|
278
|
+
type: 'json',
|
|
279
|
+
extract: {
|
|
280
|
+
results: 'data.items',
|
|
281
|
+
total: 'meta.total',
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
timeoutMs: 30000,
|
|
285
|
+
},
|
|
286
|
+
inputFields: [
|
|
287
|
+
{ name: 'query', required: true, typeHint: 'string', description: '搜索关键词' },
|
|
288
|
+
],
|
|
289
|
+
outputFields: [
|
|
290
|
+
{ name: 'results', required: true, typeHint: 'Array<{title: string, url: string}>' },
|
|
291
|
+
{ name: 'total', required: true, typeHint: 'number' },
|
|
292
|
+
],
|
|
293
|
+
};
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### URL 模板
|
|
297
|
+
|
|
298
|
+
URL 支持变量替换:
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
url: 'https://api.example.com/users/${userId}/posts/${postId}'
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### 请求头配置
|
|
305
|
+
|
|
306
|
+
**静态请求头:**
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
headers: {
|
|
310
|
+
type: 'static',
|
|
311
|
+
values: {
|
|
312
|
+
'Authorization': 'Bearer token',
|
|
313
|
+
'X-Custom-Header': 'value',
|
|
314
|
+
},
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**模板请求头(使用 Nunjucks):**
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
headers: {
|
|
322
|
+
type: 'template',
|
|
323
|
+
template: '{"Authorization": "Bearer {{ apiKey }}", "X-User": "{{ userId }}"}',
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### 请求体配置
|
|
328
|
+
|
|
329
|
+
**JSON 请求体:**
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
body: {
|
|
333
|
+
contentType: 'json',
|
|
334
|
+
includeFields: ['title', 'content'], // 只包含指定字段
|
|
335
|
+
// 或
|
|
336
|
+
excludeFields: ['internal'], // 排除指定字段
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**模板请求体:**
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
body: {
|
|
344
|
+
contentType: 'template',
|
|
345
|
+
template: '{"query": "{{ query }}", "limit": {{ limit | default(10) }}}',
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### 响应处理
|
|
350
|
+
|
|
351
|
+
**提取字段(使用 JSONPath):**
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
response: {
|
|
355
|
+
type: 'json',
|
|
356
|
+
extract: {
|
|
357
|
+
imageUrl: 'data.images.0.url',
|
|
358
|
+
status: 'meta.status',
|
|
359
|
+
},
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**保存到文件:**
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
response: {
|
|
367
|
+
type: 'json',
|
|
368
|
+
saveToFile: {
|
|
369
|
+
enabled: true,
|
|
370
|
+
pathField: 'outputPath', // 从输入参数获取路径
|
|
371
|
+
dataPath: 'data.image', // 从响应中提取数据
|
|
372
|
+
dataFormat: 'base64', // 数据格式
|
|
373
|
+
outputField: 'savedFilePath', // 输出字段名
|
|
374
|
+
},
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## 异步任务支持
|
|
379
|
+
|
|
380
|
+
对于需要轮询等待结果的异步 API,可以配置 `asyncMode`:
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
const asyncAction: ActionNodeType = {
|
|
384
|
+
kind: 'action',
|
|
385
|
+
id: 'generate_video',
|
|
386
|
+
name: '生成视频',
|
|
387
|
+
executionType: 'api_call',
|
|
388
|
+
apiConfig: {
|
|
389
|
+
url: 'https://api.example.com/video/generate',
|
|
390
|
+
method: 'POST',
|
|
391
|
+
body: { contentType: 'json' },
|
|
392
|
+
},
|
|
393
|
+
asyncMode: {
|
|
394
|
+
isAsync: true,
|
|
395
|
+
defaultCheckIntervalMs: 5000,
|
|
396
|
+
defaultTimeoutMs: 300000,
|
|
397
|
+
taskIdPath: 'data.taskId',
|
|
398
|
+
statusCheck: {
|
|
399
|
+
url: 'https://api.example.com/video/status/${taskId}',
|
|
400
|
+
method: 'GET',
|
|
401
|
+
extract: {
|
|
402
|
+
statusPath: 'status',
|
|
403
|
+
completedValues: ['completed', 'success'],
|
|
404
|
+
failedValues: ['failed', 'error'],
|
|
405
|
+
progressPath: 'progress',
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
resultFetch: {
|
|
409
|
+
extractFromStatus: {
|
|
410
|
+
videoUrl: 'data.videoUrl',
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
inputFields: [...],
|
|
415
|
+
outputFields: [...],
|
|
416
|
+
};
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## 项目结构
|
|
420
|
+
|
|
421
|
+
```
|
|
422
|
+
persona-core-opencode/
|
|
423
|
+
├── src/
|
|
424
|
+
│ ├── index.ts # 主入口,导出公共 API
|
|
425
|
+
│ ├── client/
|
|
426
|
+
│ │ └── personaCoreClient.ts # 客户端 API
|
|
427
|
+
│ ├── persona/
|
|
428
|
+
│ │ └── personaRegistry.ts # Persona 注册和管理
|
|
429
|
+
│ ├── session/
|
|
430
|
+
│ │ ├── sessionManager.ts # 会话管理和执行
|
|
431
|
+
│ │ └── sessionHandle.ts # 会话句柄
|
|
432
|
+
│ ├── generators/
|
|
433
|
+
│ │ └── toolFileGenerator.ts # 工具文件生成
|
|
434
|
+
│ └── types/
|
|
435
|
+
│ ├── common.ts # 通用类型
|
|
436
|
+
│ ├── nodes.ts # 节点类型定义
|
|
437
|
+
│ └── persona.ts # Persona 类型定义
|
|
438
|
+
├── suites/ # 示例 Persona 套件
|
|
439
|
+
│ ├── comic/ # 漫画生成 Persona
|
|
440
|
+
│ └── humor/ # 幽默安慰师 Persona
|
|
441
|
+
├── tests/ # 测试文件
|
|
442
|
+
│ ├── unit/ # 单元测试
|
|
443
|
+
│ └── integration/ # 集成测试
|
|
444
|
+
├── package.json
|
|
445
|
+
├── tsconfig.json
|
|
446
|
+
└── vitest.config.ts
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
## 工作原理
|
|
450
|
+
|
|
451
|
+
```
|
|
452
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
453
|
+
│ PersonaCoreClient │
|
|
454
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
455
|
+
│ createPersona() → PersonaRegistry.registerPersona() │
|
|
456
|
+
│ │ │ │
|
|
457
|
+
│ │ ├── 创建工作空间目录 │
|
|
458
|
+
│ │ ├── 生成 .opencode/tools/*.ts │
|
|
459
|
+
│ │ └── 生成 .opencode/rules/persona.md │
|
|
460
|
+
│ │ │
|
|
461
|
+
│ sendMessage() → SessionManager.executeThinking() │
|
|
462
|
+
│ │ │ │
|
|
463
|
+
│ │ ├── 启动 opencode run CLI │
|
|
464
|
+
│ │ ├── 通过 stdin 发送消息 │
|
|
465
|
+
│ │ ├── 解析 stdout JSONL 输出 │
|
|
466
|
+
│ │ └── 返回响应和工具调用记录 │
|
|
467
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
1. **Persona 注册**:当创建 Persona 时,系统会:
|
|
471
|
+
- 在工作空间中创建目录结构
|
|
472
|
+
- 根据 `actionTypes` 生成 opencode 工具文件(TypeScript)
|
|
473
|
+
- 将 `systemPrompt` 保存为 opencode 规则文件
|
|
474
|
+
|
|
475
|
+
2. **消息执行**:当发送消息时,系统会:
|
|
476
|
+
- 创建或复用 session
|
|
477
|
+
- 启动 `opencode run` CLI 进程
|
|
478
|
+
- 通过 stdin 发送用户消息
|
|
479
|
+
- 解析 stdout 的 JSONL 输出
|
|
480
|
+
- 提取响应文本和工具调用记录
|
|
481
|
+
|
|
482
|
+
## 环境变量
|
|
483
|
+
|
|
484
|
+
参考 `.env.example` 配置所需的环境变量:
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
# LLM Provider(根据实际使用的 API 配置)
|
|
488
|
+
DEEPSEEK_KEY=your-api-key
|
|
489
|
+
DEEPSEEK_MODEL=deepseek-v3
|
|
490
|
+
DEEPSEEK_URL=https://api.deepseek.com
|
|
491
|
+
|
|
492
|
+
# 外部服务 API
|
|
493
|
+
SERPER_KEY=your-serper-key # 网页搜索
|
|
494
|
+
JINA_KEY=your-jina-key # 网页内容获取
|
|
495
|
+
|
|
496
|
+
# 对象存储(如需要保存文件到 OSS)
|
|
497
|
+
OSS_ENDPOINT=http://localhost:9000
|
|
498
|
+
OSS_ACCESS_KEY_ID=minioadmin
|
|
499
|
+
OSS_SECRET_ACCESS_KEY=minioadmin
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
## 开发
|
|
503
|
+
|
|
504
|
+
### 安装依赖
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
npm install
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### 构建
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
npm run build
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### 运行测试
|
|
517
|
+
|
|
518
|
+
```bash
|
|
519
|
+
# 运行所有测试
|
|
520
|
+
npm run test
|
|
521
|
+
|
|
522
|
+
# 监听模式
|
|
523
|
+
npm run test:watch
|
|
524
|
+
|
|
525
|
+
# 类型检查
|
|
526
|
+
npm run typecheck
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### 运行示例套件
|
|
530
|
+
|
|
531
|
+
```bash
|
|
532
|
+
npm run suite
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### 发布
|
|
536
|
+
|
|
537
|
+
```bash
|
|
538
|
+
# 补丁版本
|
|
539
|
+
npm run release:patch
|
|
540
|
+
|
|
541
|
+
# 次要版本
|
|
542
|
+
npm run release:minor
|
|
543
|
+
|
|
544
|
+
# 主要版本
|
|
545
|
+
npm run release:major
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
## 示例:创建一个幽默安慰师
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
import { PersonaCoreClient, ActionNodeType } from 'persona-core-opencode';
|
|
552
|
+
|
|
553
|
+
const actionTypes: ActionNodeType[] = [
|
|
554
|
+
{
|
|
555
|
+
kind: 'action',
|
|
556
|
+
id: 'reply',
|
|
557
|
+
name: '回复',
|
|
558
|
+
description: '生成幽默的回复内容并保存到文件',
|
|
559
|
+
executionType: 'file_write',
|
|
560
|
+
inputFields: [
|
|
561
|
+
{ name: 'filePath', required: true, typeHint: 'string', description: '保存路径' },
|
|
562
|
+
{ name: 'content', required: true, typeHint: 'string', description: '回复内容' },
|
|
563
|
+
],
|
|
564
|
+
outputFields: [
|
|
565
|
+
{ name: 'success', required: true, typeHint: 'boolean' },
|
|
566
|
+
{ name: 'filePath', required: true, typeHint: 'string' },
|
|
567
|
+
],
|
|
568
|
+
},
|
|
569
|
+
];
|
|
570
|
+
|
|
571
|
+
async function main() {
|
|
572
|
+
const client = await PersonaCoreClient.create();
|
|
573
|
+
|
|
574
|
+
const personaId = await client.createPersona({
|
|
575
|
+
name: '幽默安慰师',
|
|
576
|
+
description: '用幽默的方式安慰别人',
|
|
577
|
+
systemPrompt: `你是一个幽默的人,特别擅长用幽默的话安慰别人。
|
|
578
|
+
|
|
579
|
+
你的特点:
|
|
580
|
+
1. 总能用轻松幽默的方式化解对方的负面情绪
|
|
581
|
+
2. 善于用比喻和类比来让人开心
|
|
582
|
+
3. 会在安慰中加入恰到好处的调侃,但不会伤害对方
|
|
583
|
+
|
|
584
|
+
当用户向你倾诉时,使用 reply 工具保存你的幽默回复。`,
|
|
585
|
+
actionTypes,
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
const result = await client.sendMessage(
|
|
589
|
+
personaId,
|
|
590
|
+
'今天代码写了一天都没跑通,太难受了...'
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
console.log('幽默安慰师的回复:', result.response);
|
|
594
|
+
console.log('保存的文件:', result.toolCalls);
|
|
595
|
+
|
|
596
|
+
await client.cleanup();
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
main().catch(console.error);
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
## 许可证
|
|
603
|
+
|
|
604
|
+
MIT License
|
|
@@ -34,7 +34,7 @@ export declare class PersonaRegistry {
|
|
|
34
34
|
constructor(config?: PersonaRegistryConfig);
|
|
35
35
|
/**
|
|
36
36
|
* 注册 Persona
|
|
37
|
-
* 1. 生成唯一 ID
|
|
37
|
+
* 1. 生成唯一 ID(如果 input.id 已提供,则直接使用)
|
|
38
38
|
* 2. 创建工作目录
|
|
39
39
|
* 3. 生成 tool 文件
|
|
40
40
|
*/
|
|
@@ -67,12 +67,10 @@ export declare class PersonaRegistry {
|
|
|
67
67
|
* 获取工作空间根目录
|
|
68
68
|
*/
|
|
69
69
|
getWorkspaceRoot(): string;
|
|
70
|
-
/**
|
|
71
|
-
* 生成唯一 ID
|
|
72
|
-
*/
|
|
73
|
-
private generateId;
|
|
74
70
|
/**
|
|
75
71
|
* 生成 Persona 唯一 ID
|
|
72
|
+
* @param name Persona 名称
|
|
73
|
+
* @returns 生成的唯一 ID(格式:safeName-shortUuid)
|
|
76
74
|
*/
|
|
77
75
|
private generatePersonaId;
|
|
78
76
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"personaRegistry.d.ts","sourceRoot":"","sources":["../../src/persona/personaRegistry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAGlE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,YAAY;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW;IACX,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,cAAc;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,kBAAkB,CAA6C;gBAE3D,MAAM,GAAE,qBAA0B;IAI9C;;;;;OAKG;IACG,eAAe,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"personaRegistry.d.ts","sourceRoot":"","sources":["../../src/persona/personaRegistry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAGlE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,YAAY;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW;IACX,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,cAAc;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,kBAAkB,CAA6C;gBAE3D,MAAM,GAAE,qBAA0B;IAI9C;;;;;OAKG;IACG,eAAe,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC;IAgEjE;;OAEG;IACG,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAUhE;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAI5D;;OAEG;IACH,cAAc,IAAI,iBAAiB,EAAE;IAIrC;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAItC;;OAEG;IACG,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiB5D;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B;;OAEG;IACH,gBAAgB,IAAI,MAAM;IAI1B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;CAK1B"}
|
|
@@ -17,15 +17,19 @@ export class PersonaRegistry {
|
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* 注册 Persona
|
|
20
|
-
* 1. 生成唯一 ID
|
|
20
|
+
* 1. 生成唯一 ID(如果 input.id 已提供,则直接使用)
|
|
21
21
|
* 2. 创建工作目录
|
|
22
22
|
* 3. 生成 tool 文件
|
|
23
23
|
*/
|
|
24
24
|
async registerPersona(input) {
|
|
25
|
+
// 如果提供了 id,直接使用;否则生成新的 id
|
|
26
|
+
const personaId = input.id
|
|
27
|
+
? input.id
|
|
28
|
+
: this.generatePersonaId(input.name);
|
|
25
29
|
// 创建 Persona 对象
|
|
26
30
|
const now = new Date();
|
|
27
31
|
const persona = {
|
|
28
|
-
id:
|
|
32
|
+
id: personaId,
|
|
29
33
|
name: input.name,
|
|
30
34
|
description: input.description,
|
|
31
35
|
systemPrompt: input.systemPrompt,
|
|
@@ -33,7 +37,6 @@ export class PersonaRegistry {
|
|
|
33
37
|
createdAt: now,
|
|
34
38
|
updatedAt: now,
|
|
35
39
|
};
|
|
36
|
-
const personaId = this.generatePersonaId(persona);
|
|
37
40
|
const workspacePath = path.resolve(this.workspaceRoot, personaId);
|
|
38
41
|
const toolsPath = path.join(workspacePath, '.opencode', 'tools');
|
|
39
42
|
// 创建目录结构
|
|
@@ -132,19 +135,14 @@ ${persona.systemPrompt}
|
|
|
132
135
|
getWorkspaceRoot() {
|
|
133
136
|
return this.workspaceRoot;
|
|
134
137
|
}
|
|
135
|
-
/**
|
|
136
|
-
* 生成唯一 ID
|
|
137
|
-
*/
|
|
138
|
-
generateId(name) {
|
|
139
|
-
const safeName = name.replace(/[^a-zA-Z0-9-]/g, '-').toLowerCase();
|
|
140
|
-
return safeName;
|
|
141
|
-
}
|
|
142
138
|
/**
|
|
143
139
|
* 生成 Persona 唯一 ID
|
|
140
|
+
* @param name Persona 名称
|
|
141
|
+
* @returns 生成的唯一 ID(格式:safeName-shortUuid)
|
|
144
142
|
*/
|
|
145
|
-
generatePersonaId(
|
|
143
|
+
generatePersonaId(name) {
|
|
146
144
|
const shortUuid = uuidv4().slice(0, 8);
|
|
147
|
-
const safeName =
|
|
145
|
+
const safeName = name.replace(/[^a-zA-Z0-9-]/g, '-').toLowerCase();
|
|
148
146
|
return `${safeName}-${shortUuid}`;
|
|
149
147
|
}
|
|
150
148
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"personaRegistry.js","sourceRoot":"","sources":["../../src/persona/personaRegistry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,EAAE,IAAI,MAAM,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AA0BvF;;GAEG;AACH,MAAM,OAAO,eAAe;IAClB,aAAa,CAAS;IACtB,kBAAkB,GAAmC,IAAI,GAAG,EAAE,CAAC;IAEvE,YAAY,SAAgC,EAAE;QAC5C,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,aAAa,IAAI,sBAAsB,CAAC;IACtE,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,eAAe,CAAC,KAAyB;QAC7C,
|
|
1
|
+
{"version":3,"file":"personaRegistry.js","sourceRoot":"","sources":["../../src/persona/personaRegistry.ts"],"names":[],"mappings":"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"}
|