feat: Implement delete message functionality and validation in session messages

This commit is contained in:
Vaayne 2025-09-23 16:08:32 +08:00
parent 98ebfd12b3
commit 6bcd941cc6
8 changed files with 162 additions and 25 deletions

View File

@ -228,3 +228,53 @@ export const createMessage = async (req: Request, res: Response): Promise<void>
res.end()
}
}
export const deleteMessage = async (req: Request, res: Response): Promise<Response> => {
try {
const { agentId, sessionId, messageId: messageIdParam } = req.params
const messageId = Number(messageIdParam)
await verifyAgentAndSession(agentId, sessionId)
const deleted = await sessionMessageService.deleteSessionMessage(sessionId, messageId)
if (!deleted) {
logger.warn(`Message ${messageId} not found for session ${sessionId}`)
return res.status(404).json({
error: {
message: 'Message not found for this session',
type: 'not_found',
code: 'session_message_not_found'
}
})
}
logger.info(`Message ${messageId} deleted successfully for session ${sessionId}`)
return res.status(204).send()
} catch (error: any) {
if (error?.status === 404) {
logger.warn('Delete message failed - missing resource', {
agentId: req.params.agentId,
sessionId: req.params.sessionId,
messageId: req.params.messageId,
error
})
return res.status(404).json({
error: {
message: error.message,
type: 'not_found',
code: error.code ?? 'session_message_not_found'
}
})
}
logger.error('Error deleting session message:', error)
return res.status(500).json({
error: {
message: 'Failed to delete session message',
type: 'internal_error',
code: 'session_message_delete_failed'
}
})
}
}

View File

@ -11,6 +11,7 @@ import {
validateSession,
validateSessionId,
validateSessionMessage,
validateSessionMessageId,
validateSessionReplace,
validateSessionUpdate
} from './validators'
@ -362,7 +363,7 @@ const agentsRouter = express.Router()
/**
* @swagger
* /api/agents:
* /agents:
* post:
* summary: Create a new agent
* tags: [Agents]
@ -391,7 +392,7 @@ agentsRouter.post('/', validateAgent, handleValidationErrors, agentHandlers.crea
/**
* @swagger
* /api/agents:
* /agents:
* get:
* summary: List all agents with pagination
* tags: [Agents]
@ -429,7 +430,7 @@ agentsRouter.get('/', validatePagination, handleValidationErrors, agentHandlers.
/**
* @swagger
* /api/agents/{agentId}:
* /agents/{agentId}:
* get:
* summary: Get agent by ID
* tags: [Agents]
@ -457,7 +458,7 @@ agentsRouter.get('/', validatePagination, handleValidationErrors, agentHandlers.
agentsRouter.get('/:agentId', validateAgentId, handleValidationErrors, agentHandlers.getAgent)
/**
* @swagger
* /api/agents/{agentId}:
* /agents/{agentId}:
* put:
* summary: Replace agent (full update)
* tags: [Agents]
@ -497,7 +498,7 @@ agentsRouter.get('/:agentId', validateAgentId, handleValidationErrors, agentHand
agentsRouter.put('/:agentId', validateAgentId, validateAgentReplace, handleValidationErrors, agentHandlers.updateAgent)
/**
* @swagger
* /api/agents/{agentId}:
* /agents/{agentId}:
* patch:
* summary: Update agent (partial update)
* tags: [Agents]
@ -537,7 +538,7 @@ agentsRouter.put('/:agentId', validateAgentId, validateAgentReplace, handleValid
agentsRouter.patch('/:agentId', validateAgentId, validateAgentUpdate, handleValidationErrors, agentHandlers.patchAgent)
/**
* @swagger
* /api/agents/{agentId}:
* /agents/{agentId}:
* delete:
* summary: Delete agent
* tags: [Agents]
@ -567,7 +568,7 @@ const createSessionsRouter = (): express.Router => {
// Session CRUD routes (nested under agent)
/**
* @swagger
* /api/agents/{agentId}/sessions:
* /agents/{agentId}/sessions:
* post:
* summary: Create a new session for an agent
* tags: [Sessions]
@ -608,7 +609,7 @@ const createSessionsRouter = (): express.Router => {
/**
* @swagger
* /api/agents/{agentId}/sessions:
* /agents/{agentId}/sessions:
* get:
* summary: List sessions for an agent
* tags: [Sessions]
@ -657,7 +658,7 @@ const createSessionsRouter = (): express.Router => {
sessionsRouter.get('/', validatePagination, handleValidationErrors, sessionHandlers.listSessions)
/**
* @swagger
* /api/agents/{agentId}/sessions/{sessionId}:
* /agents/{agentId}/sessions/{sessionId}:
* get:
* summary: Get session by ID
* tags: [Sessions]
@ -691,7 +692,7 @@ const createSessionsRouter = (): express.Router => {
sessionsRouter.get('/:sessionId', validateSessionId, handleValidationErrors, sessionHandlers.getSession)
/**
* @swagger
* /api/agents/{agentId}/sessions/{sessionId}:
* /agents/{agentId}/sessions/{sessionId}:
* put:
* summary: Replace session (full update)
* tags: [Sessions]
@ -743,7 +744,7 @@ const createSessionsRouter = (): express.Router => {
)
/**
* @swagger
* /api/agents/{agentId}/sessions/{sessionId}:
* /agents/{agentId}/sessions/{sessionId}:
* patch:
* summary: Update session (partial update)
* tags: [Sessions]
@ -795,7 +796,7 @@ const createSessionsRouter = (): express.Router => {
)
/**
* @swagger
* /api/agents/{agentId}/sessions/{sessionId}:
* /agents/{agentId}/sessions/{sessionId}:
* delete:
* summary: Delete session
* tags: [Sessions]
@ -834,7 +835,7 @@ const createMessagesRouter = (): express.Router => {
// Message CRUD routes (nested under agent/session)
/**
* @swagger
* /api/agents/{agentId}/sessions/{sessionId}/messages:
* /agents/{agentId}/sessions/{sessionId}/messages:
* post:
* summary: Create a new message in a session
* tags: [Messages]
@ -904,6 +905,43 @@ const createMessagesRouter = (): express.Router => {
* $ref: '#/components/schemas/ErrorResponse'
*/
messagesRouter.post('/', validateSessionMessage, handleValidationErrors, messageHandlers.createMessage)
/**
* @swagger
* /agents/{agentId}/sessions/{sessionId}/messages/{messageId}:
* delete:
* summary: Delete a message from a session
* tags: [Messages]
* parameters:
* - in: path
* name: agentId
* required: true
* schema:
* type: string
* description: Agent ID
* - in: path
* name: sessionId
* required: true
* schema:
* type: string
* description: Session ID
* - in: path
* name: messageId
* required: true
* schema:
* type: integer
* description: Message ID
* responses:
* 204:
* description: Message deleted successfully
* 404:
* description: Agent, session, or message not found
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ErrorResponse'
*/
messagesRouter.delete('/:messageId', validateSessionMessageId, handleValidationErrors, messageHandlers.deleteMessage)
return messagesRouter
}

View File

@ -1,7 +1,11 @@
import { CreateSessionMessageRequestSchema } from '@types'
import { CreateSessionMessageRequestSchema, SessionMessageIdParamSchema } from '@types'
import { createZodValidator } from './zodValidator'
export const validateSessionMessage = createZodValidator({
body: CreateSessionMessageRequestSchema
})
export const validateSessionMessageId = createZodValidator({
params: SessionMessageIdParamSchema
})

View File

@ -11,7 +11,16 @@ import { handleZoomFactor } from '@main/utils/zoom'
import { SpanEntity, TokenUsage } from '@mcp-trace/trace-core'
import { MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, UpgradeChannel } from '@shared/config/constant'
import { IpcChannel } from '@shared/IpcChannel'
import { FileMetadata, Notification, OcrProvider, Provider, Shortcut, SupportedOcrFile, ThemeMode } from '@types'
import {
AgentPersistedMessage,
FileMetadata,
Notification,
OcrProvider,
Provider,
Shortcut,
SupportedOcrFile,
ThemeMode,
} from "@types";
import checkDiskSpace from 'check-disk-space'
import { BrowserWindow, dialog, ipcMain, ProxyConfig, session, shell, systemPreferences, webContents } from 'electron'
import fontList from 'font-list'
@ -209,14 +218,20 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
}
})
ipcMain.handle(IpcChannel.AgentMessage_GetHistory, async (_event, { sessionId }: { sessionId: string }) => {
ipcMain.handle(
IpcChannel.AgentMessage_GetHistory,
async (
_event,
{ sessionId }: { sessionId: string }
): Promise<AgentPersistedMessage[]> => {
try {
return await agentMessageRepository.getSessionHistory(sessionId)
return await agentMessageRepository.getSessionHistory(sessionId);
} catch (error) {
logger.error('Failed to get agent session history', error as Error)
throw error
logger.error("Failed to get agent session history", error as Error);
throw error;
}
})
}
);
//only for mac
if (isMac) {

View File

@ -6,7 +6,7 @@ import type {
ListOptions
} from '@types'
import { TextStreamPart } from 'ai'
import { desc, eq } from 'drizzle-orm'
import { and, desc, eq } from 'drizzle-orm'
import { BaseService } from '../BaseService'
import { sessionMessagesTable } from '../database/schema'
@ -145,6 +145,16 @@ export class SessionMessageService extends BaseService {
return { messages }
}
async deleteSessionMessage(sessionId: string, messageId: number): Promise<boolean> {
this.ensureInitialized()
const result = await this.database
.delete(sessionMessagesTable)
.where(and(eq(sessionMessagesTable.id, messageId), eq(sessionMessagesTable.session_id, sessionId)))
return result.rowsAffected > 0
}
async createSessionMessage(
session: GetAgentSessionResponse,
messageData: CreateSessionMessageRequest,

View File

@ -206,6 +206,16 @@ export class AgentApiClient {
}
}
public async deleteSessionMessage(agentId: string, sessionId: string, messageId: number): Promise<void> {
const base = this.getSessionMessagesPath(agentId, sessionId)
const url = `${base}/${messageId}`
try {
await this.axios.delete(url)
} catch (error) {
throw processError(error, 'Failed to delete session message.')
}
}
public async updateSession(agentId: string, session: UpdateSessionForm): Promise<GetAgentSessionResponse> {
const url = this.getSessionPaths(agentId).withId(session.id)
try {

View File

@ -36,7 +36,13 @@ export const DEFAULT_MESSAGE_MENUBAR_BUTTON_IDS: MessageMenubarButtonId[] = [
'more-menu'
]
export const SESSION_MESSAGE_MENUBAR_BUTTON_IDS: MessageMenubarButtonId[] = ['copy', 'translate', 'notes', 'more-menu']
export const SESSION_MESSAGE_MENUBAR_BUTTON_IDS: MessageMenubarButtonId[] = [
'copy',
'translate',
'notes',
'delete',
'more-menu'
]
const messageMenubarRegistry = new Map<MessageMenubarScope, MessageMenubarScopeConfig>([
[DEFAULT_MESSAGE_MENUBAR_SCOPE, { buttonIds: [...DEFAULT_MESSAGE_MENUBAR_BUTTON_IDS] }],

View File

@ -267,6 +267,10 @@ export const SessionIdParamSchema = z.object({
sessionId: z.string().min(1, 'Session ID is required')
})
export const SessionMessageIdParamSchema = z.object({
messageId: z.coerce.number().int().positive('Message ID must be a positive integer')
})
// Query validation schemas
export const PaginationQuerySchema = z.object({
limit: z.coerce.number().int().min(1).max(100).optional().default(20),