feat: 全新智能体功能

- 基于先进智能体框架,增加智能体编排功能
- 增加智能体聊天,并对接持久化
This commit is contained in:
2026-05-25 11:42:48 +08:00
parent 6c3d98eaac
commit 72df00f25b
168 changed files with 22045 additions and 400 deletions

View File

@@ -0,0 +1,290 @@
import type {ChatTimelineItem} from '@easyflow/common-ui';
import {ChatTimelineBuilder} from '@easyflow/common-ui';
import {generateAgentSessionId, sendAgentChat, stopAgentChatStream,} from './api';
import {applyAgentSseEnvelope, parseAgentSseMessage,} from './adapters/agentTimelineAdapter';
interface RuntimeSessionState {
agentId: string;
agentName?: string;
completed: boolean;
error?: string;
items: ChatTimelineItem[];
prompt: string;
roundId: string;
sending: boolean;
sessionId: string;
updatedAt: number;
}
interface StoredRuntimeSession {
agentId: string;
agentName?: string;
completed: boolean;
error?: string;
items: ChatTimelineItem[];
prompt: string;
roundId: string;
sessionId: string;
updatedAt: number;
version: number;
}
interface StartOptions {
agentId: string;
agentName?: string;
baseItems?: ChatTimelineItem[];
prompt: string;
sessionId?: string;
}
const STORAGE_PREFIX = 'easyflow:agent-chat-runtime';
const LATEST_STORAGE_KEY = `${STORAGE_PREFIX}:latest`;
const STORAGE_VERSION = 1;
const sessions = new Map<string, RuntimeSessionState>();
const listeners = new Set<() => void>();
let latestSessionId = '';
function clone<T>(value: T): T {
return JSON.parse(JSON.stringify(value)) as T;
}
function createRoundId() {
return `agent-chat-round-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
}
function storageKey(sessionId: string) {
return `${STORAGE_PREFIX}:${sessionId}`;
}
function safeSessionStorage() {
try {
return globalThis.sessionStorage;
} catch {
return undefined;
}
}
function notify() {
for (const listener of listeners) {
listener();
}
}
function persistSession(state: RuntimeSessionState) {
const storage = safeSessionStorage();
if (!storage) {
return;
}
const snapshot: StoredRuntimeSession = {
agentId: state.agentId,
agentName: state.agentName,
completed: state.completed,
error: state.error,
items: clone(state.items),
prompt: state.prompt,
roundId: state.roundId,
sessionId: state.sessionId,
updatedAt: state.updatedAt,
version: STORAGE_VERSION,
};
try {
storage.setItem(storageKey(state.sessionId), JSON.stringify(snapshot));
storage.setItem(LATEST_STORAGE_KEY, state.sessionId);
} catch {
// 缓存失败不影响正式聊天主流程。
}
}
function restoreSession(sessionId: string) {
const existing = sessions.get(sessionId);
if (existing) {
return existing;
}
const storage = safeSessionStorage();
if (!storage) {
return undefined;
}
try {
const raw = storage.getItem(storageKey(sessionId));
if (!raw) {
return undefined;
}
const parsed = JSON.parse(raw) as StoredRuntimeSession;
if (parsed.version !== STORAGE_VERSION || parsed.sessionId !== sessionId) {
return undefined;
}
const restored: RuntimeSessionState = {
agentId: parsed.agentId,
agentName: parsed.agentName,
completed: parsed.completed,
error: parsed.error,
items: Array.isArray(parsed.items) ? parsed.items : [],
prompt: parsed.prompt,
roundId: parsed.roundId,
sending: false,
sessionId,
updatedAt: parsed.updatedAt,
};
sessions.set(sessionId, restored);
return restored;
} catch {
return undefined;
}
}
function upsertState(state: RuntimeSessionState) {
state.updatedAt = Date.now();
latestSessionId = state.sessionId;
sessions.set(state.sessionId, state);
persistSession(state);
notify();
}
function runningSession() {
return [...sessions.values()].find((session) => session.sending);
}
function restoreLatestSession() {
const running = runningSession();
if (running) {
return running;
}
const storage = safeSessionStorage();
const storedSessionId = storage?.getItem(LATEST_STORAGE_KEY) || '';
const sessionId = latestSessionId || storedSessionId;
return sessionId ? restoreSession(sessionId) : undefined;
}
async function resolveSessionId(sessionId?: string) {
if (sessionId) {
return sessionId;
}
const res = await generateAgentSessionId();
if (res.errorCode !== 0 || !res.data) {
throw new Error(res.message || '会话创建失败');
}
return String(res.data);
}
function errorMessage(error: unknown) {
return error instanceof Error ? error.message : '发送失败,请稍后再试';
}
export const agentChatRuntimeManager = {
getSnapshot(sessionId?: string) {
if (!sessionId) {
return undefined;
}
const state = restoreSession(sessionId);
return state ? clone(state) : undefined;
},
getLatestSnapshot() {
const state = restoreLatestSession();
return state ? clone(state) : undefined;
},
hasRunning() {
return Boolean(runningSession());
},
replaceItems(sessionId: string, items: ChatTimelineItem[]) {
const state = restoreSession(sessionId);
if (!state) {
return;
}
state.items = clone(items);
upsertState(state);
},
async start(options: StartOptions) {
const active = runningSession();
if (active) {
throw new Error('当前回复完成后再发送新消息');
}
const sessionId = await resolveSessionId(options.sessionId);
const roundId = createRoundId();
const state: RuntimeSessionState = {
agentId: options.agentId,
agentName: options.agentName,
completed: false,
items: clone(options.baseItems || []),
prompt: options.prompt,
roundId,
sending: true,
sessionId,
updatedAt: Date.now(),
};
ChatTimelineBuilder.appendUserMessage(state.items, options.prompt, {
roundId,
});
upsertState(state);
void sendAgentChat(
{
agentId: options.agentId,
prompt: options.prompt,
sessionId,
},
{
onError(error) {
const current = sessions.get(sessionId);
if (!current || !current.sending) {
return;
}
current.error = errorMessage(error);
current.sending = false;
current.completed = true;
ChatTimelineBuilder.appendError(current.items, current.error);
ChatTimelineBuilder.finalize(current.items);
upsertState(current);
},
onFinished() {
const current = sessions.get(sessionId);
if (!current) {
return;
}
current.sending = false;
current.completed = true;
ChatTimelineBuilder.finalize(current.items);
upsertState(current);
},
onMessage(message) {
const current = sessions.get(sessionId);
if (!current || !current.sending) {
return;
}
const envelope = parseAgentSseMessage(message);
if (!envelope) {
return;
}
applyAgentSseEnvelope(current.items, envelope, { roundId });
upsertState(current);
},
},
);
return sessionId;
},
stop(sessionId?: string) {
const state = sessionId ? restoreSession(sessionId) : runningSession();
if (!state || !state.sending) {
return;
}
stopAgentChatStream();
state.sending = false;
state.completed = true;
ChatTimelineBuilder.finalize(state.items);
upsertState(state);
},
subscribe(listener: () => void) {
listeners.add(listener);
return () => {
listeners.delete(listener);
};
},
};