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,230 @@
import {describe, expect, it} from 'vitest';
import type {ChatTimelineMessageItem} from '@easyflow/common-ui';
import {
applyAgentSseEnvelope,
parseAgentSseMessage,
recordsToTimelineItems,
} from './agentTimelineAdapter';
describe('agentTimelineAdapter', () => {
it('projects history records to chat timeline items', () => {
const items = recordsToTimelineItems([
{
id: '1',
senderRole: 'user',
contentText: '帮我查一下',
roundId: 'r1',
},
{
id: '2',
senderRole: 'assistant',
contentText: '查到了',
roundId: 'r1',
contentPayload: {
agentResult: {
reasoning: '先检索',
text: '查到了',
knowledgeReferences: [
{
documentName: '手册',
chunkContent: '内容片段',
},
],
},
chains: [
{
id: 'tool-1',
name: 'search',
status: 'TOOL_RESULT',
arguments: { q: 'EasyFlow' },
result: 'ok',
},
],
},
},
]);
expect(
items.some((item) => item.type === 'message' && item.role === 'user'),
).toBe(true);
const assistant = items.find(
(item): item is ChatTimelineMessageItem =>
item.type === 'message' && item.role === 'assistant',
);
expect(assistant?.parts.some((part) => part.type === 'thinking')).toBe(
true,
);
expect(assistant?.parts.some((part) => part.type === 'text')).toBe(true);
expect(items.some((item) => item.type === 'tool')).toBe(true);
expect(assistant?.knowledgeItems?.[0]?.documentName).toBe('手册');
});
it('keeps stable ids when history has reasoning, tools and final text', () => {
const items = recordsToTimelineItems([
{
id: '42',
senderRole: 'assistant',
contentText: '最终回答',
roundId: 'r2',
contentPayload: {
agentResult: {
text: '最终回答',
},
chains: [
{
reasoning_content: '先思考',
},
{
id: 'tool-2',
name: 'search',
status: 'TOOL_RESULT',
result: 'ok',
},
],
messageChain: [
{
role: 'assistant',
reasoningContent: '中间思考',
toolCalls: [{ id: 'tool-2', name: 'search', arguments: '{}' }],
},
{
role: 'tool',
toolCallId: 'tool-2',
content: 'ok',
},
],
},
},
]);
const ids = items.map((item) => item.id);
expect(new Set(ids).size).toBe(ids.length);
expect(
items.filter(
(item) => item.type === 'message' && item.role === 'assistant',
),
).toHaveLength(2);
expect(
items.some((item) => item.type === 'tool' && item.status === 'success'),
).toBe(true);
});
it('parses raw SSE text as message delta', () => {
const envelope = parseAgentSseMessage({
data: 'hello',
event: '',
id: '',
retry: undefined,
});
expect(envelope).toMatchObject({
domain: 'LLM',
type: 'MESSAGE',
payload: { delta: 'hello' },
});
});
it('applies streaming text, HITL approval and error envelopes', () => {
const items: any[] = [];
applyAgentSseEnvelope(items, {
domain: 'LLM',
type: 'MESSAGE',
payload: { delta: '你好' },
});
applyAgentSseEnvelope(items, {
domain: 'TOOL',
type: 'FORM_REQUEST',
payload: {
requestId: 'req-1',
resumeToken: 'token-1',
toolCallId: 'tool-1',
toolName: 'workflow',
input: { name: 'demo' },
},
});
applyAgentSseEnvelope(items, {
domain: 'ERROR',
type: 'ERROR',
payload: { message: '失败' },
});
const assistant = items.find(
(item): item is ChatTimelineMessageItem =>
item.type === 'message' && item.role === 'assistant',
);
const tool = items.find((item) => item.type === 'tool');
const error = items.find((item) => item.type === 'error');
expect(assistant?.parts[0]?.content).toBe('你好');
expect(tool?.status).toBe('pending_approval');
expect(tool?.approval?.resumeToken).toBe('token-1');
expect(error?.message).toBe('失败');
});
it('keeps assistant text and approval card when a tool request is rejected', () => {
const items: any[] = [];
applyAgentSseEnvelope(items, {
domain: 'LLM',
type: 'MESSAGE',
payload: { delta: '正在处理' },
});
applyAgentSseEnvelope(items, {
domain: 'TOOL',
type: 'FORM_REQUEST',
payload: {
requestId: 'req-2',
resumeToken: 'token-2',
toolCallId: 'tool-2',
toolName: '审批工具',
input: { name: 'demo' },
},
});
applyAgentSseEnvelope(items, {
domain: 'TOOL',
type: 'FORM_REJECTED',
payload: {
requestId: 'req-2',
resumeToken: 'token-2',
toolCallId: 'tool-2',
reason: '用户拒绝执行',
},
});
const assistant = items.find(
(item): item is ChatTimelineMessageItem =>
item.type === 'message' && item.role === 'assistant',
);
const tool = items.find((item) => item.type === 'tool');
expect(assistant?.parts[0]?.content).toBe('正在处理');
expect(items).toHaveLength(2);
expect(tool?.status).toBe('rejected');
expect(tool?.rejectReason).toBe('用户拒绝执行');
});
it('applies streaming round metadata to assistant messages for action toolbar anchoring', () => {
const items: any[] = [];
applyAgentSseEnvelope(
items,
{
domain: 'LLM',
type: 'MESSAGE',
payload: { delta: '准备调用工具' },
},
{ roundId: 'runtime-round-1' },
);
const assistant = items.find(
(item): item is ChatTimelineMessageItem =>
item.type === 'message' && item.role === 'assistant',
);
expect(assistant?.roundId).toBe('runtime-round-1');
expect(assistant?.parts[0]?.content).toBe('准备调用工具');
});
});

View File

@@ -0,0 +1,420 @@
import type {ServerSentEventMessage} from 'fetch-event-stream';
import type {
ChatTimelineItem,
ChatTimelineKnowledgeHit,
ChatTimelineMessageItem,
ChatTimelineToolApprovalPayload,
} from '@easyflow/common-ui';
import {ChatTimelineBuilder} from '@easyflow/common-ui';
import type {AgentChatMessageRecord} from '../api';
export interface AgentSseEnvelope {
domain: string;
payload: Record<string, any>;
type: string;
}
function asText(value: unknown) {
return value === null || value === undefined ? '' : String(value);
}
function asRecord(value: unknown): Record<string, any> {
return value && typeof value === 'object' && !Array.isArray(value)
? (value as Record<string, any>)
: {};
}
function asArray(value: unknown): any[] {
return Array.isArray(value) ? value : [];
}
function asTimestamp(value: unknown) {
if (!value) {
return Date.now();
}
const timestamp = new Date(String(value)).getTime();
return Number.isFinite(timestamp) ? timestamp : Date.now();
}
function normalizeRole(value: unknown): 'assistant' | 'system' | 'user' {
const role = asText(value).toLowerCase();
if (role === 'assistant' || role === 'system' || role === 'user') {
return role;
}
return 'assistant';
}
function normalizeToolName(value: unknown) {
return asText(value).trim();
}
function normalizeToolCallId(payload: Record<string, any>) {
return asText(payload.toolCallId ?? payload.tool_call_id ?? payload.id);
}
function normalizeMetadata(record: AgentChatMessageRecord) {
return {
createdAt: asTimestamp(record.created),
id: `history-${record.id || record.roundId || Date.now()}`,
roundId: asText(record.roundId),
roundNo: record.roundNo,
selectedVariantIndex: record.selectedVariantIndex,
switchable: false,
variantCount: record.variantCount,
variantIndex: record.variantIndex,
} satisfies Partial<ChatTimelineMessageItem>;
}
function assistantMetadata(
record: AgentChatMessageRecord,
suffix?: string,
): Partial<ChatTimelineMessageItem> {
const metadata = normalizeMetadata(record);
return suffix ? { ...metadata, id: `${metadata.id}-${suffix}` } : metadata;
}
function normalizeKnowledgeItems(payload: Record<string, any>) {
const rawItems =
asArray(payload.items).length > 0
? asArray(payload.items)
: asArray(payload.knowledgeReferences).length > 0
? asArray(payload.knowledgeReferences)
: asArray(payload.knowledgeCitations);
return rawItems
.map((item, index): ChatTimelineKnowledgeHit => {
const source = asRecord(item);
const metadata = asRecord(source.metadata);
const documentName = asText(
source.documentName ?? source.title ?? metadata.documentName,
);
const sourceFileName = asText(
source.sourceFileName ?? metadata.sourceFileName,
);
const chunkContent = asText(
source.chunkContent ?? source.content ?? source.text ?? source.summary,
);
return {
...source,
id: asText(source.id ?? source.chunkId ?? index),
chunkContent,
content: asText(source.content ?? source.text ?? source.summary),
documentId: asText(source.documentId ?? metadata.documentId),
documentName,
knowledgeId: asText(source.knowledgeId ?? payload.knowledgeId),
knowledgeName: asText(source.knowledgeName ?? payload.knowledgeName),
metadata,
score: source.score ?? source.similarity,
sourceFileName,
sourceUri: asText(source.sourceUri ?? metadata.sourceUri),
title: documentName || sourceFileName || asText(source.source),
};
})
.filter((item) => item.chunkContent || item.title || item.documentName);
}
function buildApprovalPayload(payload: Record<string, any>) {
return {
expiresAt: asText(payload.expiresAt),
input: payload.input,
metadata: payload.metadata,
requestId: asText(payload.requestId),
resumeToken: asText(payload.resumeToken),
toolCallId: normalizeToolCallId(payload),
toolDisplayName: asText(payload.toolDisplayName),
toolName: normalizeToolName(payload.toolName ?? payload.name) || '工具调用',
toolType: asText(payload.toolType),
} satisfies ChatTimelineToolApprovalPayload;
}
function appendAssistantText(
items: ChatTimelineItem[],
record: AgentChatMessageRecord,
content: unknown,
suffix?: string,
metadata?: Partial<ChatTimelineMessageItem>,
) {
const text = asText(content);
if (!text) {
return;
}
ChatTimelineBuilder.appendMessageDelta(
items,
text,
{
...assistantMetadata(record, suffix),
...metadata,
},
);
}
function appendAssistantThinking(
items: ChatTimelineItem[],
record: AgentChatMessageRecord,
content: unknown,
suffix?: string,
metadata?: Partial<ChatTimelineMessageItem>,
) {
const text = asText(content);
if (!text) {
return;
}
ChatTimelineBuilder.appendThinkingDelta(
items,
text,
{
...assistantMetadata(record, suffix),
...metadata,
},
);
}
function projectHistoryChain(
items: ChatTimelineItem[],
record: AgentChatMessageRecord,
) {
const payload = asRecord(record.contentPayload);
let hasAssistantText = false;
let hasAssistantThinking = false;
const displayChains = asArray(payload.displayChains ?? payload.chains);
for (const chain of displayChains) {
const item = asRecord(chain);
const reasoning = item.reasoningContent ?? item.reasoning_content;
if (reasoning) {
appendAssistantThinking(items, record, reasoning, 'thinking');
hasAssistantThinking = true;
continue;
}
const toolName = normalizeToolName(item.name ?? item.toolName);
if (toolName) {
ChatTimelineBuilder.upsertToolCall(items, {
input: item.arguments ?? item.input,
output: item.result ?? item.output,
status: asText(item.status) === 'TOOL_RESULT' ? 'success' : 'running',
toolCallId: asText(item.id ?? item.toolCallId),
toolName,
});
}
}
const messageChain = asArray(payload.messageChain);
for (const chain of messageChain) {
const item = asRecord(chain);
const role = asText(item.role).toLowerCase();
if (role === 'assistant') {
appendAssistantThinking(items, record, item.reasoningContent, 'thinking');
if (item.reasoningContent) {
hasAssistantThinking = true;
}
if (!payload.agentResult && item.content) {
appendAssistantText(items, record, item.content, 'text');
hasAssistantText = true;
}
for (const toolCall of asArray(item.toolCalls)) {
const tool = asRecord(toolCall);
ChatTimelineBuilder.upsertToolCall(items, {
input: tool.arguments ?? tool.input,
status: 'running',
toolCallId: asText(tool.id ?? tool.toolCallId),
toolName: normalizeToolName(tool.name ?? tool.toolName),
});
}
continue;
}
if (role === 'tool') {
ChatTimelineBuilder.upsertToolCall(items, {
output: item.content ?? item.result,
status: 'success',
toolCallId: asText(item.toolCallId ?? item.id),
toolName: normalizeToolName(item.name ?? item.toolName) || '工具调用',
});
}
}
return {
hasAssistantText,
hasAssistantThinking,
};
}
function appendHistoryRecord(
items: ChatTimelineItem[],
record: AgentChatMessageRecord,
) {
const role = normalizeRole(record.senderRole);
const metadata = normalizeMetadata(record);
if (role === 'user') {
ChatTimelineBuilder.appendUserMessage(items, record.contentText, metadata);
return;
}
if (role === 'system') {
ChatTimelineBuilder.appendError(items, record.contentText || '系统消息');
return;
}
const payload = asRecord(record.contentPayload);
const agentResult = asRecord(payload.agentResult);
const chainProjection = projectHistoryChain(items, record);
if (!chainProjection.hasAssistantThinking) {
appendAssistantThinking(
items,
record,
payload.reasoningContent ?? agentResult.reasoning,
'thinking',
);
}
if (!chainProjection.hasAssistantText) {
appendAssistantText(
items,
record,
agentResult.text ?? payload.content ?? record.contentText,
chainProjection.hasAssistantThinking ? 'text' : undefined,
);
}
const knowledgeItems = normalizeKnowledgeItems({
...payload,
items:
payload.knowledgeCitations ??
agentResult.knowledgeReferences ??
payload.knowledgeReferences,
});
if (knowledgeItems.length > 0) {
ChatTimelineBuilder.appendKnowledge(items, knowledgeItems);
}
ChatTimelineBuilder.finalize(items);
}
export function recordsToTimelineItems(records: AgentChatMessageRecord[] = []) {
const items: ChatTimelineItem[] = [];
for (const record of records) {
appendHistoryRecord(items, record);
}
ChatTimelineBuilder.finalize(items);
return items;
}
export function parseAgentSseMessage(message: ServerSentEventMessage) {
const raw = message.data || '';
if (!raw) {
return undefined;
}
try {
const data = JSON.parse(raw);
return {
domain: asText(
data.domain ?? data.eventDomain ?? data.typeDomain,
).toUpperCase(),
payload: asRecord(data.payload ?? data.data ?? data),
type: asText(
data.type ?? data.eventType ?? data.chatType ?? data.event,
).toUpperCase(),
} satisfies AgentSseEnvelope;
} catch {
return {
domain: 'LLM',
payload: { delta: raw },
type: 'MESSAGE',
} satisfies AgentSseEnvelope;
}
}
export function applyAgentSseEnvelope(
items: ChatTimelineItem[],
envelope: AgentSseEnvelope,
metadata?: Partial<ChatTimelineMessageItem>,
) {
const { domain, payload, type } = envelope;
if (domain === 'LLM' && type === 'MESSAGE') {
ChatTimelineBuilder.appendMessageDelta(
items,
payload.delta ?? payload.text,
metadata,
);
return;
}
if (domain === 'LLM' && type === 'THINKING') {
ChatTimelineBuilder.appendThinkingDelta(
items,
payload.reasoning ?? payload.delta ?? payload.text,
metadata,
);
return;
}
if (domain === 'TOOL' && type === 'FORM_REQUEST') {
ChatTimelineBuilder.appendToolApproval(
items,
buildApprovalPayload(payload),
);
return;
}
if (domain === 'TOOL' && type === 'FORM_APPROVING') {
ChatTimelineBuilder.markToolApproving(items, {
requestId: asText(payload.requestId),
resumeToken: asText(payload.resumeToken),
toolCallId: normalizeToolCallId(payload),
});
return;
}
if (domain === 'TOOL' && type === 'FORM_REJECTED') {
ChatTimelineBuilder.markToolRejected(items, {
reason: asText(payload.reason),
requestId: asText(payload.requestId),
resumeToken: asText(payload.resumeToken),
toolCallId: normalizeToolCallId(payload),
});
return;
}
if (domain === 'TOOL' && (type === 'TOOL_CALL' || type === 'TOOL_RESULT')) {
ChatTimelineBuilder.upsertToolCall(items, {
input: payload.input ?? payload.toolInput,
output: payload.output ?? payload.result ?? payload.text,
status: type === 'TOOL_RESULT' ? 'success' : 'running',
statusKey: asText(payload.statusKey) || undefined,
toolCallId: normalizeToolCallId(payload),
toolName: normalizeToolName(
payload.toolDisplayName ?? payload.toolName ?? payload.name,
),
});
return;
}
if (domain === 'BUSINESS' && type === 'CITATIONS') {
ChatTimelineBuilder.appendKnowledge(
items,
normalizeKnowledgeItems(payload),
);
return;
}
if (domain === 'BUSINESS' && type === 'STATUS') {
if (asText(payload.statusKey) === 'memory-compression') {
ChatTimelineBuilder.upsertMemoryCompressionStatus(items, {
compressed:
typeof payload.compressed === 'boolean'
? payload.compressed
: undefined,
label: asText(payload.label),
phase: asText(payload.phase),
status: asText(payload.status),
statusKey: asText(payload.statusKey),
});
return;
}
if (asText(payload.statusKey) === 'knowledge-retrieval') {
ChatTimelineBuilder.upsertKnowledgeRetrievalStatus(
items,
asText(payload.status) === 'running' ? 'running' : 'done',
asText(payload.statusKey),
);
}
return;
}
if (domain === 'SYSTEM' && type === 'DONE') {
ChatTimelineBuilder.finalize(items);
return;
}
if (domain === 'ERROR' || type === 'ERROR') {
ChatTimelineBuilder.appendError(
items,
payload.message ?? payload.error ?? '请求失败',
);
}
}

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);
};
},
};

View File

@@ -0,0 +1,146 @@
import type {ServerSentEventMessage} from 'fetch-event-stream';
import type {AgentInfo} from '../agents/types';
import {api, SseClient} from '#/api/request';
const agentChatSseClient = new SseClient();
export interface RequestResult<T = any> {
data: T;
errorCode: number;
message?: string;
}
export interface AgentChatSessionView {
accessAt?: string;
assistantCode?: string;
assistantId?: number | string;
assistantName?: string;
continuable?: boolean;
lastMessageAt?: string;
lastMessagePreview?: string;
messageCount?: number;
readOnlyReason?: unknown;
sessionId?: number | string;
title?: string;
}
export interface AgentChatSessionPage {
pageNumber?: number;
pageSize?: number;
records?: AgentChatSessionView[];
total?: number;
}
export interface AgentChatMessageRecord {
assistantId?: number | string;
contentPayload?: Record<string, any>;
contentText?: string;
contentType?: string;
created?: string;
id?: number | string;
messageKind?: string;
roundId?: number | string;
roundNo?: number;
selectedVariantIndex?: number;
senderName?: string;
senderRole?: string;
sessionId?: number | string;
switchable?: boolean;
variantCount?: number;
variantIndex?: number;
}
export interface AgentChatConversationView {
records?: AgentChatMessageRecord[];
total?: number;
variantsByRound?: Record<string, AgentChatMessageRecord[]>;
}
export function getPublishedAgents() {
return api.get<RequestResult<AgentInfo[]>>('/api/v1/agent/list', {
params: { publishedOnly: true },
});
}
export function generateAgentSessionId() {
return api.get<RequestResult<string>>('/api/v1/agent/session/generateId');
}
export function getAgentSession(sessionId: number | string) {
return api.get<RequestResult<AgentChatSessionView>>(
`/api/v1/agent/session/${sessionId}`,
);
}
export function getAgentSessions(params?: {
agentId?: number | string;
pageNumber?: number;
pageSize?: number;
}) {
return api.get<RequestResult<AgentChatSessionPage>>(
'/api/v1/agent/session/list',
{
params: {
pageNumber: params?.pageNumber ?? 1,
pageSize: params?.pageSize ?? 50,
...(params?.agentId ? { agentId: params.agentId } : {}),
},
},
);
}
export function getAgentConversation(sessionId: number | string) {
return api.get<RequestResult<AgentChatConversationView>>(
`/api/v1/agent/session/${sessionId}/conversation`,
);
}
export function renameAgentSession(sessionId: number | string, title: string) {
return api.post<RequestResult>(`/api/v1/agent/session/${sessionId}/rename`, {
title,
});
}
export function deleteAgentSession(sessionId: number | string) {
return api.post<RequestResult>(`/api/v1/agent/session/${sessionId}/delete`);
}
export function approveAgentRun(requestId: string, resumeToken: string) {
return api.post<RequestResult>('/api/v1/agent/run/approve', {
requestId,
resumeToken,
});
}
export function rejectAgentRun(
requestId: string,
resumeToken: string,
reason?: string,
) {
return api.post<RequestResult>('/api/v1/agent/run/reject', {
requestId,
resumeToken,
reason,
});
}
export function sendAgentChat(
data: {
agentId: number | string;
prompt: string;
sessionId?: number | string;
},
options: {
onError?: (error: unknown) => void;
onFinished?: () => void;
onMessage?: (message: ServerSentEventMessage) => void;
},
) {
return agentChatSseClient.post('/api/v1/agent/chat', data, options);
}
export function stopAgentChatStream() {
agentChatSseClient.abort();
}

File diff suppressed because it is too large Load Diff