hololake-system-architecture/product-source/guanghu-knowledge-base/server/index.ts
冰朔 dcf8f7d7e9 feat(persona-agent): 光湖人格 Agent v0.1.0 — Grok Build 架构拆解+Git驱动人格体
核心架构(拆解自 xAI Grok Build 开源 Rust crate):
- PersonaAgent: Agent = Definition + ToolBridge + PromptContext(映射自 xai-grok-agent)
- 7个内置工具: read/create/update/delete/search/list/history(直接操作 Git 引擎)
- LLM 调用层: 可插拔 OpenAI-compatible API(支持 tool calling)
- 离线模式: 无 API Key 时降级为本地响应

API 端点:
- POST /api/agent/chat — Agent 对话(支持工具调用链)
- GET  /api/agent/status — Agent 状态
- GET  /api/agent/conversation — 对话历史
- POST /api/agent/clear — 清空对话

前端:
- AgentChat 组件: 对话气泡+工具调用展示+欢迎建议
- 三栏布局: 文档树 | 编辑器 | Agent 面板(可收起)

设计原则:
- Agent 是活的人格体,不是孤立的功能模块
- Agent 直接操作 Git(知识库引擎 = 人格体记忆空间)
- 所有 Git 依赖模块由人格体驱动
- 不搬 Rust 代码,拆设计模式用 TypeScript 重新实现
2026-08-08 07:38:09 +08:00

262 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 光湖知识库 · API 服务器
*
* Express + Git 引擎,提供知识库全部 REST API。
* Agent 直接调用这些接口,不经过任何第三方服务。
*/
import express from 'express';
import cors from 'cors';
import { GitEngine } from './git-engine.js';
import { PersonaAgent, createDefaultPersona } from './persona-agent.js';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// ─── 配置 ───
const PORT = parseInt(process.env.KB_PORT || '3890', 10);
const REPO_PATH = process.env.KB_REPO_PATH || path.resolve(__dirname, '../kb-data');
// ─── 初始化 ───
const app = express();
const engine = new GitEngine(REPO_PATH);
app.use(cors());
app.use(express.json({ limit: '10mb' }));
// Express v5 的 {*param} 返回数组,工具函数统一转字符串
const p = (v: unknown): string => Array.isArray(v) ? v.join('/') : String(v);
// ─── 健康检查 ───
app.get('/api/health', (_req, res) => {
res.json({
module: 'guanghu-knowledge-base',
version: '0.1.0',
engine: 'git',
repo: REPO_PATH,
status: 'online',
time: new Date().toISOString(),
});
});
// ─── 文档树 ───
app.get('/api/tree', async (_req, res) => {
try {
const tree = await engine.getTree();
res.json({ ok: true, tree });
} catch (err: any) {
res.status(500).json({ ok: false, error: err.message });
}
});
// ─── 文档 CRUD ───
// 读取文档
app.get('/api/docs/{*docPath}', async (req, res) => {
try {
const docPath = p(req.params.docPath);
const doc = await engine.getDoc(docPath);
res.json({ ok: true, doc });
} catch (err: any) {
res.status(404).json({ ok: false, error: `文档不存在: ${p(req.params.docPath)}` });
}
});
// 创建文档
app.post('/api/docs/{*docPath}', async (req, res) => {
try {
const docPath = p(req.params.docPath);
const { title, body, author } = req.body;
if (!title || !body) {
return res.status(400).json({ ok: false, error: 'title 和 body 必填' });
}
const doc = await engine.createDoc(docPath, title, body, author);
res.status(201).json({ ok: true, doc });
} catch (err: any) {
res.status(500).json({ ok: false, error: err.message });
}
});
// 更新文档
app.put('/api/docs/{*docPath}', async (req, res) => {
try {
const docPath = p(req.params.docPath);
const { title, body, author } = req.body;
if (!title || !body) {
return res.status(400).json({ ok: false, error: 'title 和 body 必填' });
}
const doc = await engine.updateDoc(docPath, title, body, author);
res.json({ ok: true, doc });
} catch (err: any) {
res.status(500).json({ ok: false, error: err.message });
}
});
// 删除文档
app.delete('/api/docs/{*docPath}', async (req, res) => {
try {
const docPath = p(req.params.docPath);
await engine.deleteDoc(docPath);
res.json({ ok: true, message: `已删除: ${docPath}` });
} catch (err: any) {
res.status(500).json({ ok: false, error: err.message });
}
});
// 重命名/移动文档
app.post('/api/move', async (req, res) => {
try {
const { oldPath, newPath } = req.body;
if (!oldPath || !newPath) {
return res.status(400).json({ ok: false, error: 'oldPath 和 newPath 必填' });
}
await engine.moveDoc(oldPath, newPath);
res.json({ ok: true, message: `${oldPath}${newPath}` });
} catch (err: any) {
res.status(500).json({ ok: false, error: err.message });
}
});
// ─── 版本历史 ───
app.get('/api/history/{*docPath}', async (req, res) => {
try {
const docPath = p(req.params.docPath);
const maxCount = parseInt(req.query.max?.toString() || '50', 10);
const history = await engine.getHistory(docPath, maxCount);
res.json({ ok: true, history });
} catch (err: any) {
res.status(500).json({ ok: false, error: err.message });
}
});
// 获取某版本内容
app.get('/api/version/:hash/{*docPath}', async (req, res) => {
try {
const { hash } = req.params;
const docPath = p(req.params.docPath);
const content = await engine.getDocAtVersion(docPath, hash);
res.json({ ok: true, content });
} catch (err: any) {
res.status(500).json({ ok: false, error: err.message });
}
});
// 版本对比
app.get('/api/diff/{*docPath}', async (req, res) => {
try {
const docPath = p(req.params.docPath);
const { from, to } = req.query;
if (!from || !to) {
return res.status(400).json({ ok: false, error: 'from 和 to (commit hash) 必填' });
}
const diff = await engine.diffVersions(docPath, from.toString(), to.toString());
res.json({ ok: true, diff });
} catch (err: any) {
res.status(500).json({ ok: false, error: err.message });
}
});
// ─── 搜索 ───
app.get('/api/search', async (req, res) => {
try {
const query = req.query.q?.toString();
if (!query) {
return res.status(400).json({ ok: false, error: 'q 参数必填' });
}
const results = await engine.search(query);
res.json({ ok: true, results, count: results.length });
} catch (err: any) {
res.status(500).json({ ok: false, error: err.message });
}
});
// ─── Agent人格体 ───
let personaAgent: PersonaAgent | null = null;
function getAgent(): PersonaAgent {
if (!personaAgent) {
personaAgent = createDefaultPersona(engine);
console.log('人格体已创建:', personaAgent.getDefinition().name);
}
return personaAgent;
}
// Agent 状态
app.get('/api/agent/status', (_req, res) => {
const agent = getAgent();
const def = agent.getDefinition();
res.json({
ok: true,
persona: {
id: def.id,
name: def.name,
role: def.role,
model: def.model,
permissionMode: def.permissionMode,
},
conversationLength: agent.getConversation().length,
engine: 'git',
repo: REPO_PATH,
});
});
// Agent 对话
app.post('/api/agent/chat', async (req, res) => {
try {
const { message } = req.body;
if (!message || typeof message !== 'string') {
return res.status(400).json({ ok: false, error: 'message 必填且为字符串' });
}
const agent = getAgent();
const reply = await agent.chat(message);
res.json({
ok: true,
reply,
conversationLength: agent.getConversation().length,
});
} catch (err: any) {
res.status(500).json({ ok: false, error: err.message });
}
});
// Agent 对话历史
app.get('/api/agent/conversation', (_req, res) => {
const agent = getAgent();
res.json({ ok: true, messages: agent.getConversation() });
});
// 清空对话
app.post('/api/agent/clear', (_req, res) => {
const agent = getAgent();
agent.clearConversation();
res.json({ ok: true, message: '对话已清空' });
});
// ─── 启动 ───
async function start() {
await engine.init();
// 初始化人格体
getAgent();
app.listen(PORT, () => {
console.log(`光湖知识库 API 已启动: http://localhost:${PORT}`);
console.log(`人格体已激活: ${personaAgent?.getDefinition().name}`);
console.log(`仓库路径: ${REPO_PATH}`);
});
}
start().catch(err => {
console.error('启动失败:', err);
process.exit(1);
});
export default app;