refactor(agent): 迁移AI会话引擎至pi-agent-core库
将原有基于Anthropic/OpenAI SDK的直播聊天代理重构为使用`@earendil-works/pi-agent-core`和`@earendil-works/pi-ai`库的统一API。 新增pi-bridge、pi-model、pi-persist、pi-tools四个模块,封装Agent路由、模型配置、消息持久化和工具适配逻辑。移除`chat.ts`中大量死代码,简化WebSocket处理流程。 BREAKING CHANGE: 移除`VideoAgent`类的`getAnthropicClient`、`getOpenAIClient`、`executeTool`等方法,外部调用需迁移至新pi-bridge API。`PROJECT_ROOT`路径计算方式变更,从`../../..`变为`../../`。
This commit is contained in:
164
web/server/agent/pi-persist.ts
Normal file
164
web/server/agent/pi-persist.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
import { getDb } from '../db';
|
||||
import type { Message, AssistantMessage, TextContent, ToolCall } from '@earendil-works/pi-ai';
|
||||
|
||||
export interface DbMessage {
|
||||
id: string;
|
||||
conversation_id: string;
|
||||
role: string;
|
||||
content: string;
|
||||
tool_calls: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export function dbToPiMessages(dbMessages: DbMessage[]): Message[] {
|
||||
const sanitized = sanitizeHistory(dbMessages);
|
||||
return sanitized.map(dbToPiMessage);
|
||||
}
|
||||
|
||||
function dbToPiMessage(msg: DbMessage): Message {
|
||||
if (msg.role === 'user') {
|
||||
return { role: 'user', content: msg.content, timestamp: Date.parse(msg.created_at) || Date.now() };
|
||||
}
|
||||
|
||||
if (msg.role === 'assistant') {
|
||||
const content: (TextContent | ToolCall)[] = [];
|
||||
const textContent = msg.content && msg.content !== '(调用工具)' ? msg.content : '';
|
||||
if (textContent) {
|
||||
content.push({ type: 'text', text: textContent });
|
||||
}
|
||||
|
||||
if (msg.tool_calls) {
|
||||
try {
|
||||
const parsed = JSON.parse(msg.tool_calls);
|
||||
const blocks = Array.isArray(parsed) ? parsed : parsed.content_blocks;
|
||||
if (Array.isArray(blocks)) {
|
||||
for (const b of blocks) {
|
||||
if (b.type === 'tool_use') {
|
||||
content.push({
|
||||
type: 'toolCall',
|
||||
id: b.id,
|
||||
name: b.name,
|
||||
arguments: b.input || b.arguments || {},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return {
|
||||
role: 'assistant',
|
||||
content,
|
||||
api: 'unknown' as any,
|
||||
provider: 'unknown',
|
||||
model: 'unknown',
|
||||
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
|
||||
stopReason: 'stop',
|
||||
timestamp: Date.parse(msg.created_at) || Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
if (msg.role === 'tool') {
|
||||
try {
|
||||
const { tool_use_id, content: resultContent } = JSON.parse(msg.content);
|
||||
return {
|
||||
role: 'toolResult',
|
||||
toolCallId: tool_use_id,
|
||||
toolName: '',
|
||||
content: [{ type: 'text', text: resultContent }],
|
||||
isError: false,
|
||||
timestamp: Date.parse(msg.created_at) || Date.now(),
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
role: 'toolResult',
|
||||
toolCallId: 'unknown',
|
||||
toolName: '',
|
||||
content: [{ type: 'text', text: msg.content }],
|
||||
isError: true,
|
||||
timestamp: Date.parse(msg.created_at) || Date.now(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { role: 'user', content: msg.content, timestamp: Date.now() };
|
||||
}
|
||||
|
||||
function sanitizeHistory(messages: DbMessage[]): DbMessage[] {
|
||||
const cleaned = messages.filter((m) => {
|
||||
if (m.role === 'assistant' && m.content.startsWith('抱歉,出错了:')) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
const result: DbMessage[] = [];
|
||||
for (const msg of cleaned) {
|
||||
if (msg.role === 'tool') {
|
||||
let hasPrecedingToolCall = false;
|
||||
for (let j = result.length - 1; j >= 0; j--) {
|
||||
const prev = result[j];
|
||||
if (prev.role === 'assistant') {
|
||||
if (prev.tool_calls) {
|
||||
try {
|
||||
const parsed = JSON.parse(prev.tool_calls);
|
||||
const blocks = Array.isArray(parsed) ? parsed : parsed.content_blocks;
|
||||
if (blocks?.some((b: any) => b.type === 'tool_use')) {
|
||||
hasPrecedingToolCall = true;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (prev.role === 'tool') continue;
|
||||
break;
|
||||
}
|
||||
if (!hasPrecedingToolCall) continue;
|
||||
}
|
||||
result.push(msg);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function saveUserMessage(convId: string, content: string): string {
|
||||
const id = randomUUID();
|
||||
getDb().prepare(
|
||||
'INSERT INTO messages (id, conversation_id, role, content) VALUES (?, ?, ?, ?)'
|
||||
).run(id, convId, 'user', content);
|
||||
return id;
|
||||
}
|
||||
|
||||
export function saveAssistantMessage(convId: string, msg: AssistantMessage): string {
|
||||
const id = randomUUID();
|
||||
const textParts = msg.content.filter((c): c is TextContent => c.type === 'text');
|
||||
const text = textParts.map((c) => c.text).join('');
|
||||
|
||||
const toolCalls = msg.content.filter((c): c is ToolCall => c.type === 'toolCall');
|
||||
|
||||
if (toolCalls.length > 0) {
|
||||
const dbToolCalls = toolCalls.map((tc) => ({
|
||||
type: 'tool_use',
|
||||
id: tc.id,
|
||||
name: tc.name,
|
||||
input: tc.arguments,
|
||||
}));
|
||||
const contentBlocks = text ? [{ type: 'text', text }, ...dbToolCalls] : dbToolCalls;
|
||||
getDb().prepare(
|
||||
'INSERT INTO messages (id, conversation_id, role, content, tool_calls) VALUES (?, ?, ?, ?, ?)'
|
||||
).run(id, convId, 'assistant', text || '(调用工具)', JSON.stringify(contentBlocks));
|
||||
} else {
|
||||
getDb().prepare(
|
||||
'INSERT INTO messages (id, conversation_id, role, content) VALUES (?, ?, ?, ?)'
|
||||
).run(id, convId, 'assistant', text);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
export function saveToolResult(convId: string, toolCallId: string, toolName: string, result: string, isError: boolean): string {
|
||||
const id = randomUUID();
|
||||
getDb().prepare(
|
||||
'INSERT INTO messages (id, conversation_id, role, content) VALUES (?, ?, ?, ?)'
|
||||
).run(id, convId, 'tool', JSON.stringify({ tool_use_id: toolCallId, tool_name: toolName, content: result }));
|
||||
return id;
|
||||
}
|
||||
Reference in New Issue
Block a user