feat: 全新智能体功能
- 基于先进智能体框架,增加智能体编排功能 - 增加智能体聊天,并对接持久化
This commit is contained in:
@@ -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);
|
||||
};
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user