mirror of
https://github.com/ILoveBingLu/CipherTalk.git
synced 2026-05-15 08:28:52 +08:00
feat: 新增 MCP 聊天导出自动化并升级到 3.0.0
This commit is contained in:
@@ -32,6 +32,19 @@ export async function executeMcpTool(
|
||||
payload
|
||||
}
|
||||
}
|
||||
case 'export_chat': {
|
||||
const payload = await readService.exportChat(args as any, reporter)
|
||||
return {
|
||||
summary: payload.success
|
||||
? `Exported chat for ${payload.resolvedSession?.displayName || payload.resolvedSession?.sessionId || 'target session'}.`
|
||||
: payload.success === false
|
||||
? `Failed to export chat for ${payload.resolvedSession?.displayName || payload.resolvedSession?.sessionId || 'target session'}.`
|
||||
: payload.canExport
|
||||
? `Prepared export for ${payload.resolvedSession?.displayName || payload.resolvedSession?.sessionId || 'target session'}.`
|
||||
: 'Export request needs more information.',
|
||||
payload
|
||||
}
|
||||
}
|
||||
case 'get_global_statistics': {
|
||||
const payload = await readService.getGlobalStatistics(args as any)
|
||||
return { summary: 'Loaded global statistics.', payload }
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { existsSync, mkdirSync } from 'fs'
|
||||
import { accessSync, constants, existsSync, mkdirSync } from 'fs'
|
||||
import { writeFile } from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import { z } from 'zod'
|
||||
import { analyticsService } from '../analyticsService'
|
||||
import { chatService, type ChatSession, type ContactInfo, type Message } from '../chatService'
|
||||
import { ConfigService } from '../config'
|
||||
import { exportService, type ExportOptions as ExportServiceOptions } from '../exportService'
|
||||
import { imageDecryptService } from '../imageDecryptService'
|
||||
import { videoService } from '../videoService'
|
||||
import { McpToolError } from './result'
|
||||
@@ -15,6 +16,11 @@ import {
|
||||
type McpContactKind,
|
||||
type McpContactsPayload,
|
||||
type McpCursor,
|
||||
type McpExportChatPayload,
|
||||
type McpExportDateRange,
|
||||
type McpExportFormat,
|
||||
type McpExportMediaOptions,
|
||||
type McpExportMissingField,
|
||||
type McpGlobalStatisticsPayload,
|
||||
type McpContactRankingItem,
|
||||
type McpContactRankingsPayload,
|
||||
@@ -57,6 +63,33 @@ const resolveSessionArgsSchema = z.object({
|
||||
limit: z.number().int().positive().optional()
|
||||
})
|
||||
|
||||
const exportChatArgsSchema = z.object({
|
||||
sessionId: z.string().trim().min(1).optional(),
|
||||
query: z.string().trim().min(1).optional(),
|
||||
format: z.enum(['chatlab', 'chatlab-jsonl', 'json', 'excel', 'html']).optional(),
|
||||
dateRange: z.object({
|
||||
start: z.number().int().positive(),
|
||||
end: z.number().int().positive()
|
||||
}).optional(),
|
||||
mediaOptions: z.object({
|
||||
exportAvatars: z.boolean().optional(),
|
||||
exportImages: z.boolean().optional(),
|
||||
exportVideos: z.boolean().optional(),
|
||||
exportEmojis: z.boolean().optional(),
|
||||
exportVoices: z.boolean().optional()
|
||||
}).optional(),
|
||||
outputDir: z.string().trim().min(1).optional(),
|
||||
validateOnly: z.boolean().optional()
|
||||
}).superRefine((value, ctx) => {
|
||||
if (!value.sessionId && !value.query) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['sessionId'],
|
||||
message: 'sessionId or query is required'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const getMessagesArgsSchema = z.object({
|
||||
sessionId: z.string().trim().min(1),
|
||||
offset: z.number().int().nonnegative().optional(),
|
||||
@@ -126,6 +159,7 @@ const getSessionContextArgsSchema = z.object({
|
||||
|
||||
type ListSessionsArgs = z.infer<typeof listSessionsArgsSchema>
|
||||
type ResolveSessionArgs = z.infer<typeof resolveSessionArgsSchema>
|
||||
type ExportChatArgs = z.infer<typeof exportChatArgsSchema>
|
||||
type GetMessagesArgs = z.infer<typeof getMessagesArgsSchema>
|
||||
type ListContactsArgs = z.infer<typeof listContactsArgsSchema>
|
||||
type SearchMessagesArgs = z.infer<typeof searchMessagesArgsSchema>
|
||||
@@ -160,6 +194,8 @@ type McpStreamReporter = {
|
||||
partial?: <K extends keyof McpStreamPartialPayloadMap>(toolName: K, payload: McpStreamPartialPayloadMap[K]) => void | Promise<void>
|
||||
}
|
||||
|
||||
const SUPPORTED_EXPORT_FORMATS: McpExportFormat[] = ['chatlab', 'chatlab-jsonl', 'json', 'excel', 'html']
|
||||
|
||||
function toTimestampMs(value?: number | null): number {
|
||||
if (!value || !Number.isFinite(value) || value <= 0) return 0
|
||||
return value < 1_000_000_000_000 ? value * 1000 : value
|
||||
@@ -631,6 +667,134 @@ function buildSearchSessionSummaries(hits: McpSearchHit[]): McpSearchMessagesPay
|
||||
.sort((a, b) => b.hitCount - a.hitCount || b.topScore - a.topScore)
|
||||
}
|
||||
|
||||
function getDefaultExportPath(): string | null {
|
||||
const config = new ConfigService()
|
||||
try {
|
||||
const exportPath = String(config.get('exportPath') || '').trim()
|
||||
return exportPath || null
|
||||
} finally {
|
||||
config.close()
|
||||
}
|
||||
}
|
||||
|
||||
function isWritableDirectory(dir: string): boolean {
|
||||
try {
|
||||
if (!dir) return false
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true })
|
||||
}
|
||||
accessSync(dir, constants.W_OK)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function isCompleteMediaOptions(
|
||||
mediaOptions?: ExportChatArgs['mediaOptions']
|
||||
): mediaOptions is McpExportMediaOptions {
|
||||
return Boolean(
|
||||
mediaOptions
|
||||
&& typeof mediaOptions.exportAvatars === 'boolean'
|
||||
&& typeof mediaOptions.exportImages === 'boolean'
|
||||
&& typeof mediaOptions.exportVideos === 'boolean'
|
||||
&& typeof mediaOptions.exportEmojis === 'boolean'
|
||||
&& typeof mediaOptions.exportVoices === 'boolean'
|
||||
)
|
||||
}
|
||||
|
||||
function getNextExportQuestion(missingFields: McpExportMissingField[]): string | undefined {
|
||||
if (missingFields.includes('session')) {
|
||||
return '请先确认要导出哪个会话,可以提供 sessionId 或更具体的联系人线索。'
|
||||
}
|
||||
if (missingFields.includes('dateRange')) {
|
||||
return '请补充导出的时间范围,至少需要开始时间和结束时间。'
|
||||
}
|
||||
if (missingFields.includes('format')) {
|
||||
return '请确认导出格式,仅支持 chatlab、chatlab-jsonl、json、excel、html。'
|
||||
}
|
||||
if (missingFields.includes('mediaOptions')) {
|
||||
return '请明确是否导出头像、图片、视频、表情、语音。'
|
||||
}
|
||||
if (missingFields.includes('outputDir')) {
|
||||
return '默认导出目录不可用,请提供一个可写入的导出目录。'
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function buildExportFollowUpQuestions(missingFields: McpExportMissingField[]): Array<{
|
||||
field: McpExportMissingField
|
||||
question: string
|
||||
}> {
|
||||
const questions: Array<{ field: McpExportMissingField; question: string }> = []
|
||||
|
||||
for (const field of missingFields) {
|
||||
if (field === 'session') {
|
||||
questions.push({
|
||||
field,
|
||||
question: '你要导出哪个会话?可以给我更具体的联系人、备注名或 sessionId。'
|
||||
})
|
||||
} else if (field === 'dateRange') {
|
||||
questions.push({
|
||||
field,
|
||||
question: '这次导出的时间范围是什么?请给我开始时间和结束时间。'
|
||||
})
|
||||
} else if (field === 'format') {
|
||||
questions.push({
|
||||
field,
|
||||
question: '你要导出成哪种格式?目前支持 chatlab、chatlab-jsonl、json、excel、html。'
|
||||
})
|
||||
} else if (field === 'mediaOptions') {
|
||||
questions.push({
|
||||
field,
|
||||
question: '媒体要怎么导?请分别确认是否包含头像、图片、视频、表情、语音。'
|
||||
})
|
||||
} else if (field === 'outputDir') {
|
||||
questions.push({
|
||||
field,
|
||||
question: '默认导出目录不可用,请给我一个可写入的导出目录。'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return questions
|
||||
}
|
||||
|
||||
function buildPredictedExportPath(
|
||||
outputDir: string,
|
||||
resolvedSession: Pick<McpResolvedSessionCandidate, 'displayName'>,
|
||||
format: McpExportFormat,
|
||||
mediaOptions: McpExportMediaOptions
|
||||
): string {
|
||||
const safeName = resolvedSession.displayName.replace(/[<>:"/\\|?*]/g, '_').replace(/\.+$/, '').trim() || 'export'
|
||||
const ext = format === 'chatlab-jsonl'
|
||||
? '.jsonl'
|
||||
: format === 'excel'
|
||||
? '.xlsx'
|
||||
: format === 'html'
|
||||
? '.html'
|
||||
: '.json'
|
||||
const hasMedia = mediaOptions.exportImages || mediaOptions.exportVideos || mediaOptions.exportEmojis || mediaOptions.exportVoices
|
||||
const sessionOutputDir = hasMedia ? join(outputDir, safeName) : outputDir
|
||||
return join(sessionOutputDir, `${safeName}${ext}`)
|
||||
}
|
||||
|
||||
function toExportServiceOptions(
|
||||
format: McpExportFormat,
|
||||
dateRange: McpExportDateRange,
|
||||
mediaOptions: McpExportMediaOptions
|
||||
): ExportServiceOptions {
|
||||
return {
|
||||
format,
|
||||
dateRange,
|
||||
exportAvatars: mediaOptions.exportAvatars,
|
||||
exportImages: mediaOptions.exportImages,
|
||||
exportVideos: mediaOptions.exportVideos,
|
||||
exportEmojis: mediaOptions.exportEmojis,
|
||||
exportVoices: mediaOptions.exportVoices
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSessionRefStrict(
|
||||
rawInput: string,
|
||||
sessions: McpSessionItem[],
|
||||
@@ -1060,6 +1224,160 @@ export class McpReadService {
|
||||
return payload
|
||||
}
|
||||
|
||||
async exportChat(rawArgs: ExportChatArgs, reporter?: McpStreamReporter): Promise<McpExportChatPayload> {
|
||||
const args = exportChatArgsSchema.safeParse(rawArgs)
|
||||
if (!args.success) {
|
||||
throw new McpToolError('BAD_REQUEST', 'Invalid export_chat arguments.', args.error.message)
|
||||
}
|
||||
|
||||
const data = args.data
|
||||
const validateOnly = Boolean(data.validateOnly)
|
||||
await reportProgress(reporter, {
|
||||
stage: 'validating_export_request',
|
||||
message: 'Validating export request.'
|
||||
})
|
||||
|
||||
const [{ items: sessions, map: sessionMap }, { items: contacts, map: contactMap }] = await Promise.all([
|
||||
getSessionCatalog(),
|
||||
getContactCatalog()
|
||||
])
|
||||
|
||||
let resolvedSession: McpResolvedSessionCandidate | undefined
|
||||
let candidates: McpResolvedSessionCandidate[] = []
|
||||
|
||||
if (data.sessionId || data.query) {
|
||||
const query = data.sessionId || data.query || ''
|
||||
const matchedCandidates = findSessionCandidates(query, sessions, contacts).slice(0, 5)
|
||||
candidates = matchedCandidates.map((candidate) => toResolvedCandidate(candidate, query))
|
||||
|
||||
try {
|
||||
const resolved = await resolveSessionRefStrictWithProgress(query, sessions, sessionMap, contacts, contactMap, reporter)
|
||||
const matched = matchedCandidates.find((candidate) => candidate.entry.session.sessionId === resolved.sessionId)
|
||||
resolvedSession = matched ? toResolvedCandidate(matched, query) : {
|
||||
...resolved,
|
||||
score: 1000,
|
||||
confidence: 'high',
|
||||
aliases: [resolved.displayName, resolved.sessionId],
|
||||
evidence: ['Resolved directly from the provided session clue.']
|
||||
}
|
||||
} catch (error) {
|
||||
if (!(error instanceof McpToolError) || (error.code !== 'BAD_REQUEST' && error.code !== 'SESSION_NOT_FOUND')) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const missingFields: McpExportMissingField[] = []
|
||||
if (!resolvedSession) {
|
||||
missingFields.push('session')
|
||||
}
|
||||
if (!data.dateRange || !data.dateRange.start || !data.dateRange.end) {
|
||||
missingFields.push('dateRange')
|
||||
} else if (data.dateRange.start > data.dateRange.end) {
|
||||
throw new McpToolError('BAD_REQUEST', 'Invalid export date range.', 'dateRange.start must be earlier than or equal to dateRange.end.')
|
||||
}
|
||||
if (!data.format) {
|
||||
missingFields.push('format')
|
||||
} else if (!SUPPORTED_EXPORT_FORMATS.includes(data.format)) {
|
||||
throw new McpToolError('BAD_REQUEST', 'Unsupported export format.', `Only ${SUPPORTED_EXPORT_FORMATS.join(', ')} are supported.`)
|
||||
}
|
||||
if (!isCompleteMediaOptions(data.mediaOptions)) {
|
||||
missingFields.push('mediaOptions')
|
||||
}
|
||||
|
||||
const requestedOutputDir = String(data.outputDir || '').trim()
|
||||
const outputDir = requestedOutputDir || getDefaultExportPath() || ''
|
||||
if (!outputDir || !isWritableDirectory(outputDir)) {
|
||||
missingFields.push('outputDir')
|
||||
}
|
||||
|
||||
const nextQuestion = getNextExportQuestion(missingFields)
|
||||
const followUpQuestions = buildExportFollowUpQuestions(missingFields)
|
||||
const payload: McpExportChatPayload = {
|
||||
canExport: missingFields.length === 0,
|
||||
validateOnly,
|
||||
missingFields,
|
||||
nextQuestion,
|
||||
followUpQuestions,
|
||||
resolvedSession,
|
||||
candidates,
|
||||
outputDir: outputDir || undefined,
|
||||
format: data.format,
|
||||
dateRange: data.dateRange,
|
||||
mediaOptions: isCompleteMediaOptions(data.mediaOptions) ? data.mediaOptions : undefined,
|
||||
message: missingFields.length === 0
|
||||
? validateOnly
|
||||
? 'Export request is complete and ready to run.'
|
||||
: 'Export request validated and ready to execute.'
|
||||
: 'Export request is incomplete and needs more information.'
|
||||
}
|
||||
|
||||
await reportPartial(reporter, 'export_chat', payload)
|
||||
|
||||
if (missingFields.length > 0 || validateOnly) {
|
||||
return payload
|
||||
}
|
||||
|
||||
await reportProgress(reporter, {
|
||||
stage: 'preparing_export',
|
||||
message: `Preparing export for ${resolvedSession!.displayName}.`,
|
||||
candidates: [{ sessionId: resolvedSession!.sessionId, displayName: resolvedSession!.displayName, kind: resolvedSession!.kind }],
|
||||
candidateCount: 1
|
||||
})
|
||||
|
||||
const exportOptions = toExportServiceOptions(
|
||||
data.format!,
|
||||
data.dateRange!,
|
||||
data.mediaOptions as McpExportMediaOptions
|
||||
)
|
||||
|
||||
const predictedOutputPath = buildPredictedExportPath(
|
||||
outputDir,
|
||||
resolvedSession!,
|
||||
data.format!,
|
||||
data.mediaOptions as McpExportMediaOptions
|
||||
)
|
||||
|
||||
const result = await exportService.exportSessions(
|
||||
[resolvedSession!.sessionId],
|
||||
outputDir,
|
||||
exportOptions,
|
||||
(progress) => {
|
||||
const stage = progress.phase === 'writing'
|
||||
? 'writing'
|
||||
: progress.phase === 'exporting'
|
||||
? 'exporting'
|
||||
: progress.phase === 'complete'
|
||||
? 'completed'
|
||||
: 'preparing_export'
|
||||
|
||||
void reportProgress(reporter, {
|
||||
stage,
|
||||
message: progress.detail || progress.phase,
|
||||
sessionsScanned: progress.current,
|
||||
candidates: [{ sessionId: resolvedSession!.sessionId, displayName: resolvedSession!.displayName, kind: resolvedSession!.kind }],
|
||||
candidateCount: 1
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const completedPayload: McpExportChatPayload = {
|
||||
...payload,
|
||||
canExport: true,
|
||||
success: result.success,
|
||||
successCount: result.successCount,
|
||||
failCount: result.failCount,
|
||||
error: result.error,
|
||||
outputPath: predictedOutputPath,
|
||||
message: result.success
|
||||
? `Exported chat for ${resolvedSession!.displayName}.`
|
||||
: `Failed to export chat for ${resolvedSession!.displayName}.`
|
||||
}
|
||||
|
||||
await reportPartial(reporter, 'export_chat', completedPayload)
|
||||
return completedPayload
|
||||
}
|
||||
|
||||
async listSessions(rawArgs: ListSessionsArgs, reporter?: McpStreamReporter): Promise<McpSessionsPayload> {
|
||||
const args = listSessionsArgsSchema.safeParse(rawArgs)
|
||||
if (!args.success) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
McpActivityDistributionPayload,
|
||||
McpContactRankingsPayload,
|
||||
McpContactsPayload,
|
||||
McpExportChatPayload,
|
||||
McpGlobalStatisticsPayload,
|
||||
McpHealthPayload,
|
||||
McpMessagesPayload,
|
||||
@@ -270,6 +271,10 @@ export class McpReadService {
|
||||
return this.callProxy<McpResolveSessionPayload>('resolve_session', rawArgs)
|
||||
}
|
||||
|
||||
async exportChat(rawArgs: Record<string, unknown>): Promise<McpExportChatPayload> {
|
||||
return this.callProxy<McpExportChatPayload>('export_chat', rawArgs)
|
||||
}
|
||||
|
||||
async listSessions(rawArgs: Record<string, unknown>): Promise<McpSessionsPayload> {
|
||||
return this.callProxy<McpSessionsPayload>('list_sessions', rawArgs)
|
||||
}
|
||||
|
||||
@@ -47,6 +47,36 @@ export function registerCipherTalkMcpTools(server: any) {
|
||||
}
|
||||
})
|
||||
|
||||
server.registerTool('export_chat', {
|
||||
title: 'Export Chat',
|
||||
description: 'Validate and export chat history for one resolved session. This tool strictly checks target session, date range, export format, media selections, and output directory before exporting.',
|
||||
inputSchema: {
|
||||
sessionId: z.string().trim().min(1).optional().describe('Resolved sessionId when already known.'),
|
||||
query: z.string().trim().min(1).optional().describe('Fuzzy session clue when sessionId is not yet known.'),
|
||||
format: z.enum(['chatlab', 'chatlab-jsonl', 'json', 'excel', 'html']).optional().describe('Export format.'),
|
||||
dateRange: z.object({
|
||||
start: z.number().int().positive(),
|
||||
end: z.number().int().positive()
|
||||
}).optional().describe('Required export time range in seconds or milliseconds.'),
|
||||
mediaOptions: z.object({
|
||||
exportAvatars: z.boolean().optional(),
|
||||
exportImages: z.boolean().optional(),
|
||||
exportVideos: z.boolean().optional(),
|
||||
exportEmojis: z.boolean().optional(),
|
||||
exportVoices: z.boolean().optional()
|
||||
}).optional().describe('Required explicit media export selections.'),
|
||||
outputDir: z.string().trim().min(1).optional().describe('Optional output directory. If omitted, the configured default export path will be used when available.'),
|
||||
validateOnly: z.boolean().optional().describe('When true, only validate completeness and return missing fields without exporting.')
|
||||
}
|
||||
}, async (args: unknown) => {
|
||||
try {
|
||||
const payload = await readService.exportChat((args || {}) as any)
|
||||
return createToolSuccess(payload.message, payload)
|
||||
} catch (error) {
|
||||
return createToolError(error)
|
||||
}
|
||||
})
|
||||
|
||||
server.registerTool('get_global_statistics', {
|
||||
title: 'Get Global Statistics',
|
||||
description: 'Return global private-chat statistics for agent-side analysis.',
|
||||
|
||||
@@ -2,6 +2,7 @@ export const MCP_TOOL_NAMES = [
|
||||
'health_check',
|
||||
'get_status',
|
||||
'resolve_session',
|
||||
'export_chat',
|
||||
'list_sessions',
|
||||
'get_messages',
|
||||
'list_contacts',
|
||||
@@ -56,7 +57,11 @@ export type McpStreamProgressStage =
|
||||
| 'searching_contacts'
|
||||
| 'searching_sessions'
|
||||
| 'resolving_candidates'
|
||||
| 'validating_export_request'
|
||||
| 'preparing_export'
|
||||
| 'scanning_messages'
|
||||
| 'exporting'
|
||||
| 'writing'
|
||||
| 'streaming_hits'
|
||||
| 'completed'
|
||||
| 'failed'
|
||||
@@ -150,6 +155,51 @@ export interface McpResolveSessionPayload {
|
||||
message: string
|
||||
}
|
||||
|
||||
export type McpExportFormat = 'chatlab' | 'chatlab-jsonl' | 'json' | 'excel' | 'html'
|
||||
|
||||
export interface McpExportMediaOptions {
|
||||
exportAvatars: boolean
|
||||
exportImages: boolean
|
||||
exportVideos: boolean
|
||||
exportEmojis: boolean
|
||||
exportVoices: boolean
|
||||
}
|
||||
|
||||
export type McpExportMissingField =
|
||||
| 'session'
|
||||
| 'dateRange'
|
||||
| 'format'
|
||||
| 'mediaOptions'
|
||||
| 'outputDir'
|
||||
|
||||
export interface McpExportDateRange {
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
|
||||
export interface McpExportChatPayload {
|
||||
canExport: boolean
|
||||
validateOnly: boolean
|
||||
missingFields: McpExportMissingField[]
|
||||
nextQuestion?: string
|
||||
followUpQuestions?: Array<{
|
||||
field: McpExportMissingField
|
||||
question: string
|
||||
}>
|
||||
resolvedSession?: McpResolvedSessionCandidate
|
||||
candidates?: McpResolvedSessionCandidate[]
|
||||
outputDir?: string
|
||||
outputPath?: string
|
||||
format?: McpExportFormat
|
||||
dateRange?: McpExportDateRange
|
||||
mediaOptions?: McpExportMediaOptions
|
||||
success?: boolean
|
||||
successCount?: number
|
||||
failCount?: number
|
||||
error?: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface McpContactItem {
|
||||
contactId: string
|
||||
sessionId?: string
|
||||
@@ -309,6 +359,7 @@ export interface McpStreamProgressPayload {
|
||||
|
||||
export interface McpStreamPartialPayloadMap {
|
||||
resolve_session: Partial<McpResolveSessionPayload>
|
||||
export_chat: Partial<McpExportChatPayload>
|
||||
list_sessions: Partial<McpSessionsPayload>
|
||||
list_contacts: Partial<McpContactsPayload>
|
||||
get_messages: Partial<McpMessagesPayload>
|
||||
@@ -317,6 +368,7 @@ export interface McpStreamPartialPayloadMap {
|
||||
}
|
||||
|
||||
export type McpStreamPartialPayload =
|
||||
| McpStreamPartialPayloadMap['export_chat']
|
||||
| McpStreamPartialPayloadMap['list_sessions']
|
||||
| McpStreamPartialPayloadMap['list_contacts']
|
||||
| McpStreamPartialPayloadMap['get_messages']
|
||||
|
||||
Reference in New Issue
Block a user