mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-08 14:29:15 +08:00
Refactor: Remove sessions route and related validation logic
- Deleted the sessions route file, consolidating session management logic. - Removed validation middleware and error handling for session operations. - Updated the SessionMessageService to improve query formatting for retrieving session messages.
This commit is contained in:
parent
b55f419a95
commit
95a1e210b6
@ -10,8 +10,6 @@ import { agentsRoutes } from './routes/agents'
|
||||
import { chatRoutes } from './routes/chat'
|
||||
import { mcpRoutes } from './routes/mcp'
|
||||
import { modelsRoutes } from './routes/models'
|
||||
import { sessionMessagesRoutes } from './routes/session-messages'
|
||||
import { sessionsRoutes } from './routes/sessions'
|
||||
|
||||
const logger = loggerService.withContext('ApiServer')
|
||||
|
||||
@ -104,13 +102,7 @@ app.get('/', (_req, res) => {
|
||||
name: 'Cherry Studio API',
|
||||
version: '1.0.0',
|
||||
endpoints: {
|
||||
health: 'GET /health',
|
||||
models: 'GET /v1/models',
|
||||
chat: 'POST /v1/chat/completions',
|
||||
mcp: 'GET /v1/mcps',
|
||||
agents: 'GET /v1/agents',
|
||||
sessions: 'GET /v1/sessions',
|
||||
logs: 'GET /v1/sessions/{sessionId}/logs'
|
||||
health: 'GET /health'
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -124,8 +116,6 @@ apiRouter.use('/chat', chatRoutes)
|
||||
apiRouter.use('/mcps', mcpRoutes)
|
||||
apiRouter.use('/models', modelsRoutes)
|
||||
apiRouter.use('/agents', agentsRoutes)
|
||||
apiRouter.use('/sessions', sessionsRoutes)
|
||||
apiRouter.use('/', sessionMessagesRoutes) // This handles /sessions/:sessionId/messages and /session-messages/:messageId
|
||||
app.use('/v1', apiRouter)
|
||||
|
||||
// Setup OpenAPI documentation
|
||||
|
||||
@ -1,202 +1,9 @@
|
||||
import express, { Request, Response } from 'express'
|
||||
import { body, param, query, validationResult } from 'express-validator'
|
||||
import { Request, Response } from 'express'
|
||||
|
||||
import { agentService } from '../../services/agents'
|
||||
import { loggerService } from '../../services/LoggerService'
|
||||
import { agentService } from '../../../../services/agents'
|
||||
import { loggerService } from '../../../../services/LoggerService'
|
||||
|
||||
const logger = loggerService.withContext('ApiServerAgentsRoutes')
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
// Validation middleware
|
||||
const validateAgent = [
|
||||
body('name').notEmpty().withMessage('Name is required'),
|
||||
body('model').notEmpty().withMessage('Model is required'),
|
||||
body('description').optional().isString(),
|
||||
body('avatar').optional().isString(),
|
||||
body('instructions').optional().isString(),
|
||||
body('plan_model').optional().isString(),
|
||||
body('small_model').optional().isString(),
|
||||
body('built_in_tools').optional().isArray(),
|
||||
body('mcps').optional().isArray(),
|
||||
body('knowledges').optional().isArray(),
|
||||
body('configuration').optional().isObject(),
|
||||
body('accessible_paths').optional().isArray(),
|
||||
body('permission_mode').optional().isIn(['readOnly', 'acceptEdits', 'bypassPermissions']),
|
||||
body('max_steps').optional().isInt({ min: 1 })
|
||||
]
|
||||
|
||||
const validateAgentUpdate = [
|
||||
body('name').optional().notEmpty().withMessage('Name cannot be empty'),
|
||||
body('model').optional().notEmpty().withMessage('Model cannot be empty'),
|
||||
body('description').optional().isString(),
|
||||
body('avatar').optional().isString(),
|
||||
body('instructions').optional().isString(),
|
||||
body('plan_model').optional().isString(),
|
||||
body('small_model').optional().isString(),
|
||||
body('built_in_tools').optional().isArray(),
|
||||
body('mcps').optional().isArray(),
|
||||
body('knowledges').optional().isArray(),
|
||||
body('configuration').optional().isObject(),
|
||||
body('accessible_paths').optional().isArray(),
|
||||
body('permission_mode').optional().isIn(['readOnly', 'acceptEdits', 'bypassPermissions']),
|
||||
body('max_steps').optional().isInt({ min: 1 })
|
||||
]
|
||||
|
||||
const validateAgentId = [param('agentId').notEmpty().withMessage('Agent ID is required')]
|
||||
|
||||
const validatePagination = [
|
||||
query('limit').optional().isInt({ min: 1, max: 100 }).withMessage('Limit must be between 1 and 100'),
|
||||
query('offset').optional().isInt({ min: 0 }).withMessage('Offset must be non-negative')
|
||||
]
|
||||
|
||||
// Error handler for validation
|
||||
const handleValidationErrors = (req: Request, res: Response, next: any): void => {
|
||||
const errors = validationResult(req)
|
||||
if (!errors.isEmpty()) {
|
||||
res.status(400).json({
|
||||
error: {
|
||||
message: 'Validation failed',
|
||||
type: 'validation_error',
|
||||
details: errors.array()
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* AgentEntity:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: string
|
||||
* description: Unique agent identifier
|
||||
* name:
|
||||
* type: string
|
||||
* description: Agent name
|
||||
* description:
|
||||
* type: string
|
||||
* description: Agent description
|
||||
* avatar:
|
||||
* type: string
|
||||
* description: Agent avatar URL
|
||||
* instructions:
|
||||
* type: string
|
||||
* description: System prompt/instructions
|
||||
* model:
|
||||
* type: string
|
||||
* description: Main model ID
|
||||
* plan_model:
|
||||
* type: string
|
||||
* description: Optional planning model ID
|
||||
* small_model:
|
||||
* type: string
|
||||
* description: Optional small/fast model ID
|
||||
* built_in_tools:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: Built-in tool IDs
|
||||
* mcps:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: MCP tool IDs
|
||||
* knowledges:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: Knowledge base IDs
|
||||
* configuration:
|
||||
* type: object
|
||||
* description: Extensible settings
|
||||
* accessible_paths:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: Accessible directory paths
|
||||
* permission_mode:
|
||||
* type: string
|
||||
* enum: [readOnly, acceptEdits, bypassPermissions]
|
||||
* description: Permission mode
|
||||
* max_steps:
|
||||
* type: integer
|
||||
* description: Maximum steps the agent can take
|
||||
* created_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* updated_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* required:
|
||||
* - id
|
||||
* - name
|
||||
* - model
|
||||
* - created_at
|
||||
* - updated_at
|
||||
* CreateAgentRequest:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: Agent name
|
||||
* description:
|
||||
* type: string
|
||||
* description: Agent description
|
||||
* avatar:
|
||||
* type: string
|
||||
* description: Agent avatar URL
|
||||
* instructions:
|
||||
* type: string
|
||||
* description: System prompt/instructions
|
||||
* model:
|
||||
* type: string
|
||||
* description: Main model ID
|
||||
* plan_model:
|
||||
* type: string
|
||||
* description: Optional planning model ID
|
||||
* small_model:
|
||||
* type: string
|
||||
* description: Optional small/fast model ID
|
||||
* built_in_tools:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: Built-in tool IDs
|
||||
* mcps:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: MCP tool IDs
|
||||
* knowledges:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: Knowledge base IDs
|
||||
* configuration:
|
||||
* type: object
|
||||
* description: Extensible settings
|
||||
* accessible_paths:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: Accessible directory paths
|
||||
* permission_mode:
|
||||
* type: string
|
||||
* enum: [readOnly, acceptEdits, bypassPermissions]
|
||||
* description: Permission mode
|
||||
* max_steps:
|
||||
* type: integer
|
||||
* description: Maximum steps the agent can take
|
||||
* required:
|
||||
* - name
|
||||
* - model
|
||||
*/
|
||||
const logger = loggerService.withContext('ApiServerAgentsHandlers')
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@ -231,7 +38,7 @@ const handleValidationErrors = (req: Request, res: Response, next: any): void =>
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.post('/', validateAgent, handleValidationErrors, async (req: Request, res: Response) => {
|
||||
export const createAgent = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
logger.info('Creating new agent')
|
||||
logger.debug('Agent data:', req.body)
|
||||
@ -250,7 +57,7 @@ router.post('/', validateAgent, handleValidationErrors, async (req: Request, res
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@ -309,7 +116,7 @@ router.post('/', validateAgent, handleValidationErrors, async (req: Request, res
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.get('/', validatePagination, handleValidationErrors, async (req: Request, res: Response) => {
|
||||
export const listAgents = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const limit = req.query.limit ? parseInt(req.query.limit as string) : 20
|
||||
const offset = req.query.offset ? parseInt(req.query.offset as string) : 0
|
||||
@ -335,7 +142,7 @@ router.get('/', validatePagination, handleValidationErrors, async (req: Request,
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@ -371,7 +178,7 @@ router.get('/', validatePagination, handleValidationErrors, async (req: Request,
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.get('/:agentId', validateAgentId, handleValidationErrors, async (req: Request, res: Response) => {
|
||||
export const getAgent = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { agentId } = req.params
|
||||
logger.info(`Getting agent: ${agentId}`)
|
||||
@ -401,7 +208,7 @@ router.get('/:agentId', validateAgentId, handleValidationErrors, async (req: Req
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@ -449,44 +256,38 @@ router.get('/:agentId', validateAgentId, handleValidationErrors, async (req: Req
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.put(
|
||||
'/:agentId',
|
||||
validateAgentId,
|
||||
validateAgentUpdate,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { agentId } = req.params
|
||||
logger.info(`Updating agent: ${agentId}`)
|
||||
logger.debug('Update data:', req.body)
|
||||
export const updateAgent = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { agentId } = req.params
|
||||
logger.info(`Updating agent: ${agentId}`)
|
||||
logger.debug('Update data:', req.body)
|
||||
|
||||
const agent = await agentService.updateAgent(agentId, req.body)
|
||||
const agent = await agentService.updateAgent(agentId, req.body)
|
||||
|
||||
if (!agent) {
|
||||
logger.warn(`Agent not found for update: ${agentId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Agent not found',
|
||||
type: 'not_found',
|
||||
code: 'agent_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Agent updated successfully: ${agentId}`)
|
||||
return res.json(agent)
|
||||
} catch (error: any) {
|
||||
logger.error('Error updating agent:', error)
|
||||
return res.status(500).json({
|
||||
if (!agent) {
|
||||
logger.warn(`Agent not found for update: ${agentId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Failed to update agent',
|
||||
type: 'internal_error',
|
||||
code: 'agent_update_failed'
|
||||
message: 'Agent not found',
|
||||
type: 'not_found',
|
||||
code: 'agent_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Agent updated successfully: ${agentId}`)
|
||||
return res.json(agent)
|
||||
} catch (error: any) {
|
||||
logger.error('Error updating agent:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to update agent',
|
||||
type: 'internal_error',
|
||||
code: 'agent_update_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@ -587,44 +388,38 @@ router.put(
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.patch(
|
||||
'/:agentId',
|
||||
validateAgentId,
|
||||
validateAgentUpdate,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { agentId } = req.params
|
||||
logger.info(`Partially updating agent: ${agentId}`)
|
||||
logger.debug('Partial update data:', req.body)
|
||||
export const patchAgent = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { agentId } = req.params
|
||||
logger.info(`Partially updating agent: ${agentId}`)
|
||||
logger.debug('Partial update data:', req.body)
|
||||
|
||||
const agent = await agentService.updateAgent(agentId, req.body)
|
||||
const agent = await agentService.updateAgent(agentId, req.body)
|
||||
|
||||
if (!agent) {
|
||||
logger.warn(`Agent not found for partial update: ${agentId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Agent not found',
|
||||
type: 'not_found',
|
||||
code: 'agent_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Agent partially updated successfully: ${agentId}`)
|
||||
return res.json(agent)
|
||||
} catch (error: any) {
|
||||
logger.error('Error partially updating agent:', error)
|
||||
return res.status(500).json({
|
||||
if (!agent) {
|
||||
logger.warn(`Agent not found for partial update: ${agentId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Failed to partially update agent',
|
||||
type: 'internal_error',
|
||||
code: 'agent_patch_failed'
|
||||
message: 'Agent not found',
|
||||
type: 'not_found',
|
||||
code: 'agent_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Agent partially updated successfully: ${agentId}`)
|
||||
return res.json(agent)
|
||||
} catch (error: any) {
|
||||
logger.error('Error partially updating agent:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to partially update agent',
|
||||
type: 'internal_error',
|
||||
code: 'agent_patch_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@ -656,7 +451,7 @@ router.patch(
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.delete('/:agentId', validateAgentId, handleValidationErrors, async (req: Request, res: Response) => {
|
||||
export const deleteAgent = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { agentId } = req.params
|
||||
logger.info(`Deleting agent: ${agentId}`)
|
||||
@ -686,17 +481,4 @@ router.delete('/:agentId', validateAgentId, handleValidationErrors, async (req:
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Mount session routes as nested resources
|
||||
import { createSessionMessagesRouter } from './session-messages'
|
||||
import { createSessionsRouter } from './sessions'
|
||||
|
||||
const sessionsRouter = createSessionsRouter()
|
||||
const sessionMessagesRouter = createSessionMessagesRouter()
|
||||
|
||||
// Mount nested routes
|
||||
router.use('/:agentId/sessions', sessionsRouter)
|
||||
router.use('/:agentId/sessions/:sessionId/messages', sessionMessagesRouter)
|
||||
|
||||
export { router as agentsRoutes }
|
||||
}
|
||||
3
src/main/apiServer/routes/agents/handlers/index.ts
Normal file
3
src/main/apiServer/routes/agents/handlers/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * as agentHandlers from './agents'
|
||||
export * as messageHandlers from './messages'
|
||||
export * as sessionHandlers from './sessions'
|
||||
315
src/main/apiServer/routes/agents/handlers/messages.ts
Normal file
315
src/main/apiServer/routes/agents/handlers/messages.ts
Normal file
@ -0,0 +1,315 @@
|
||||
import { Request, Response } from 'express'
|
||||
|
||||
import { agentService, sessionMessageService, sessionService } from '../../../../services/agents'
|
||||
import { loggerService } from '../../../../services/LoggerService'
|
||||
|
||||
const logger = loggerService.withContext('ApiServerMessagesHandlers')
|
||||
|
||||
// Helper function to verify agent and session exist and belong together
|
||||
const verifyAgentAndSession = async (agentId: string, sessionId: string) => {
|
||||
const agentExists = await agentService.agentExists(agentId)
|
||||
if (!agentExists) {
|
||||
throw { status: 404, code: 'agent_not_found', message: 'Agent not found' }
|
||||
}
|
||||
|
||||
const session = await sessionService.getSession(sessionId)
|
||||
if (!session) {
|
||||
throw { status: 404, code: 'session_not_found', message: 'Session not found' }
|
||||
}
|
||||
|
||||
if (session.main_agent_id !== agentId) {
|
||||
throw { status: 404, code: 'session_not_found', message: 'Session not found for this agent' }
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
export const createMessage = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { agentId, sessionId } = req.params
|
||||
|
||||
await verifyAgentAndSession(agentId, sessionId)
|
||||
|
||||
const messageData = { ...req.body, session_id: sessionId }
|
||||
|
||||
logger.info(`Creating new message for session: ${sessionId}`)
|
||||
logger.debug('Message data:', messageData)
|
||||
|
||||
const message = await sessionMessageService.createSessionMessage(messageData)
|
||||
|
||||
logger.info(`Message created successfully: ${message.id}`)
|
||||
return res.status(201).json(message)
|
||||
} catch (error: any) {
|
||||
if (error.status) {
|
||||
return res.status(error.status).json({
|
||||
error: {
|
||||
message: error.message,
|
||||
type: 'not_found',
|
||||
code: error.code
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.error('Error creating message:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to create message',
|
||||
type: 'internal_error',
|
||||
code: 'message_creation_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const createBulkMessages = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { agentId, sessionId } = req.params
|
||||
|
||||
await verifyAgentAndSession(agentId, sessionId)
|
||||
|
||||
const messagesData = req.body.map((msg: any) => ({ ...msg, session_id: sessionId }))
|
||||
|
||||
logger.info(`Creating ${messagesData.length} messages for session: ${sessionId}`)
|
||||
logger.debug('Messages data:', messagesData)
|
||||
|
||||
const messages = await sessionMessageService.bulkCreateSessionMessages(messagesData)
|
||||
|
||||
logger.info(`${messages.length} messages created successfully for session: ${sessionId}`)
|
||||
return res.status(201).json(messages)
|
||||
} catch (error: any) {
|
||||
if (error.status) {
|
||||
return res.status(error.status).json({
|
||||
error: {
|
||||
message: error.message,
|
||||
type: 'not_found',
|
||||
code: error.code
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.error('Error creating bulk messages:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to create messages',
|
||||
type: 'internal_error',
|
||||
code: 'bulk_message_creation_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const listMessages = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { agentId, sessionId } = req.params
|
||||
|
||||
await verifyAgentAndSession(agentId, sessionId)
|
||||
|
||||
const limit = req.query.limit ? parseInt(req.query.limit as string) : 50
|
||||
const offset = req.query.offset ? parseInt(req.query.offset as string) : 0
|
||||
|
||||
logger.info(`Listing messages for session: ${sessionId} with limit=${limit}, offset=${offset}`)
|
||||
|
||||
const result = await sessionMessageService.listSessionMessages(sessionId, { limit, offset })
|
||||
|
||||
logger.info(`Retrieved ${result.messages.length} messages (total: ${result.total}) for session: ${sessionId}`)
|
||||
return res.json({
|
||||
data: result.messages,
|
||||
total: result.total,
|
||||
limit,
|
||||
offset
|
||||
})
|
||||
} catch (error: any) {
|
||||
if (error.status) {
|
||||
return res.status(error.status).json({
|
||||
error: {
|
||||
message: error.message,
|
||||
type: 'not_found',
|
||||
code: error.code
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.error('Error listing messages:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to list messages',
|
||||
type: 'internal_error',
|
||||
code: 'message_list_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const getMessage = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { agentId, sessionId, messageId } = req.params
|
||||
|
||||
await verifyAgentAndSession(agentId, sessionId)
|
||||
|
||||
logger.info(`Getting message: ${messageId} for session: ${sessionId}`)
|
||||
|
||||
const message = await sessionMessageService.getSessionMessage(parseInt(messageId))
|
||||
|
||||
if (!message) {
|
||||
logger.warn(`Message not found: ${messageId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Message not found',
|
||||
type: 'not_found',
|
||||
code: 'message_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Verify message belongs to the session
|
||||
if (message.session_id !== sessionId) {
|
||||
logger.warn(`Message ${messageId} does not belong to session ${sessionId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Message not found for this session',
|
||||
type: 'not_found',
|
||||
code: 'message_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Message retrieved successfully: ${messageId}`)
|
||||
return res.json(message)
|
||||
} catch (error: any) {
|
||||
if (error.status) {
|
||||
return res.status(error.status).json({
|
||||
error: {
|
||||
message: error.message,
|
||||
type: 'not_found',
|
||||
code: error.code
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.error('Error getting message:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to get message',
|
||||
type: 'internal_error',
|
||||
code: 'message_get_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const updateMessage = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { agentId, sessionId, messageId } = req.params
|
||||
|
||||
await verifyAgentAndSession(agentId, sessionId)
|
||||
|
||||
logger.info(`Updating message: ${messageId} for session: ${sessionId}`)
|
||||
logger.debug('Update data:', req.body)
|
||||
|
||||
// First check if message exists and belongs to session
|
||||
const existingMessage = await sessionMessageService.getSessionMessage(parseInt(messageId))
|
||||
if (!existingMessage || existingMessage.session_id !== sessionId) {
|
||||
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: 'message_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const message = await sessionMessageService.updateSessionMessage(parseInt(messageId), req.body)
|
||||
|
||||
if (!message) {
|
||||
logger.warn(`Message not found for update: ${messageId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Message not found',
|
||||
type: 'not_found',
|
||||
code: 'message_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Message updated successfully: ${messageId}`)
|
||||
return res.json(message)
|
||||
} catch (error: any) {
|
||||
if (error.status) {
|
||||
return res.status(error.status).json({
|
||||
error: {
|
||||
message: error.message,
|
||||
type: 'not_found',
|
||||
code: error.code
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.error('Error updating message:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to update message',
|
||||
type: 'internal_error',
|
||||
code: 'message_update_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteMessage = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { agentId, sessionId, messageId } = req.params
|
||||
|
||||
await verifyAgentAndSession(agentId, sessionId)
|
||||
|
||||
logger.info(`Deleting message: ${messageId} for session: ${sessionId}`)
|
||||
|
||||
// First check if message exists and belongs to session
|
||||
const existingMessage = await sessionMessageService.getSessionMessage(parseInt(messageId))
|
||||
if (!existingMessage || existingMessage.session_id !== sessionId) {
|
||||
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: 'message_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const deleted = await sessionMessageService.deleteSessionMessage(parseInt(messageId))
|
||||
|
||||
if (!deleted) {
|
||||
logger.warn(`Message not found for deletion: ${messageId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Message not found',
|
||||
type: 'not_found',
|
||||
code: 'message_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Message deleted successfully: ${messageId}`)
|
||||
return res.status(204).send()
|
||||
} catch (error: any) {
|
||||
if (error.status) {
|
||||
return res.status(error.status).json({
|
||||
error: {
|
||||
message: error.message,
|
||||
type: 'not_found',
|
||||
code: error.code
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.error('Error deleting message:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to delete message',
|
||||
type: 'internal_error',
|
||||
code: 'message_delete_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
306
src/main/apiServer/routes/agents/handlers/sessions.ts
Normal file
306
src/main/apiServer/routes/agents/handlers/sessions.ts
Normal file
@ -0,0 +1,306 @@
|
||||
import { Request, Response } from 'express'
|
||||
|
||||
import { sessionService } from '../../../../services/agents'
|
||||
import { loggerService } from '../../../../services/LoggerService'
|
||||
|
||||
const logger = loggerService.withContext('ApiServerSessionsHandlers')
|
||||
|
||||
export const createSession = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { agentId } = req.params
|
||||
const sessionData = { ...req.body, main_agent_id: agentId }
|
||||
|
||||
logger.info(`Creating new session for agent: ${agentId}`)
|
||||
logger.debug('Session data:', sessionData)
|
||||
|
||||
const session = await sessionService.createSession(sessionData)
|
||||
|
||||
logger.info(`Session created successfully: ${session.id}`)
|
||||
return res.status(201).json(session)
|
||||
} catch (error: any) {
|
||||
logger.error('Error creating session:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to create session',
|
||||
type: 'internal_error',
|
||||
code: 'session_creation_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const listSessions = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { agentId } = req.params
|
||||
const limit = req.query.limit ? parseInt(req.query.limit as string) : 20
|
||||
const offset = req.query.offset ? parseInt(req.query.offset as string) : 0
|
||||
const status = req.query.status as any
|
||||
|
||||
logger.info(`Listing sessions for agent: ${agentId} with limit=${limit}, offset=${offset}, status=${status}`)
|
||||
|
||||
const result = await sessionService.listSessions(agentId, { limit, offset, status })
|
||||
|
||||
logger.info(`Retrieved ${result.sessions.length} sessions (total: ${result.total}) for agent: ${agentId}`)
|
||||
return res.json({
|
||||
data: result.sessions,
|
||||
total: result.total,
|
||||
limit,
|
||||
offset
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error('Error listing sessions:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to list sessions',
|
||||
type: 'internal_error',
|
||||
code: 'session_list_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const getSession = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { agentId, sessionId } = req.params
|
||||
logger.info(`Getting session: ${sessionId} for agent: ${agentId}`)
|
||||
|
||||
const session = await sessionService.getSession(sessionId)
|
||||
|
||||
if (!session) {
|
||||
logger.warn(`Session not found: ${sessionId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Session not found',
|
||||
type: 'not_found',
|
||||
code: 'session_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Verify session belongs to the agent
|
||||
if (session.main_agent_id !== agentId) {
|
||||
logger.warn(`Session ${sessionId} does not belong to agent ${agentId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Session not found for this agent',
|
||||
type: 'not_found',
|
||||
code: 'session_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Session retrieved successfully: ${sessionId}`)
|
||||
return res.json(session)
|
||||
} catch (error: any) {
|
||||
logger.error('Error getting session:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to get session',
|
||||
type: 'internal_error',
|
||||
code: 'session_get_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const updateSession = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { agentId, sessionId } = req.params
|
||||
logger.info(`Updating session: ${sessionId} for agent: ${agentId}`)
|
||||
logger.debug('Update data:', req.body)
|
||||
|
||||
// First check if session exists and belongs to agent
|
||||
const existingSession = await sessionService.getSession(sessionId)
|
||||
if (!existingSession || existingSession.main_agent_id !== agentId) {
|
||||
logger.warn(`Session ${sessionId} not found for agent ${agentId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Session not found for this agent',
|
||||
type: 'not_found',
|
||||
code: 'session_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// For PUT, we replace the entire resource
|
||||
const sessionData = { ...req.body, main_agent_id: agentId }
|
||||
const session = await sessionService.updateSession(sessionId, sessionData)
|
||||
|
||||
if (!session) {
|
||||
logger.warn(`Session not found for update: ${sessionId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Session not found',
|
||||
type: 'not_found',
|
||||
code: 'session_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Session updated successfully: ${sessionId}`)
|
||||
return res.json(session)
|
||||
} catch (error: any) {
|
||||
logger.error('Error updating session:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to update session',
|
||||
type: 'internal_error',
|
||||
code: 'session_update_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const patchSession = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { agentId, sessionId } = req.params
|
||||
logger.info(`Patching session: ${sessionId} for agent: ${agentId}`)
|
||||
logger.debug('Patch data:', req.body)
|
||||
|
||||
// First check if session exists and belongs to agent
|
||||
const existingSession = await sessionService.getSession(sessionId)
|
||||
if (!existingSession || existingSession.main_agent_id !== agentId) {
|
||||
logger.warn(`Session ${sessionId} not found for agent ${agentId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Session not found for this agent',
|
||||
type: 'not_found',
|
||||
code: 'session_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const updateSession = { ...existingSession, ...req.body }
|
||||
const session = await sessionService.updateSession(sessionId, updateSession)
|
||||
|
||||
if (!session) {
|
||||
logger.warn(`Session not found for patch: ${sessionId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Session not found',
|
||||
type: 'not_found',
|
||||
code: 'session_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Session patched successfully: ${sessionId}`)
|
||||
return res.json(session)
|
||||
} catch (error: any) {
|
||||
logger.error('Error patching session:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to patch session',
|
||||
type: 'internal_error',
|
||||
code: 'session_patch_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteSession = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { agentId, sessionId } = req.params
|
||||
logger.info(`Deleting session: ${sessionId} for agent: ${agentId}`)
|
||||
|
||||
// First check if session exists and belongs to agent
|
||||
const existingSession = await sessionService.getSession(sessionId)
|
||||
if (!existingSession || existingSession.main_agent_id !== agentId) {
|
||||
logger.warn(`Session ${sessionId} not found for agent ${agentId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Session not found for this agent',
|
||||
type: 'not_found',
|
||||
code: 'session_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const deleted = await sessionService.deleteSession(sessionId)
|
||||
|
||||
if (!deleted) {
|
||||
logger.warn(`Session not found for deletion: ${sessionId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Session not found',
|
||||
type: 'not_found',
|
||||
code: 'session_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Session deleted successfully: ${sessionId}`)
|
||||
return res.status(204).send()
|
||||
} catch (error: any) {
|
||||
logger.error('Error deleting session:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to delete session',
|
||||
type: 'internal_error',
|
||||
code: 'session_delete_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience endpoints for sessions without agent context
|
||||
export const listAllSessions = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const limit = req.query.limit ? parseInt(req.query.limit as string) : 20
|
||||
const offset = req.query.offset ? parseInt(req.query.offset as string) : 0
|
||||
const status = req.query.status as any
|
||||
|
||||
logger.info(`Listing all sessions with limit=${limit}, offset=${offset}, status=${status}`)
|
||||
|
||||
const result = await sessionService.listSessions(undefined, { limit, offset, status })
|
||||
|
||||
logger.info(`Retrieved ${result.sessions.length} sessions (total: ${result.total})`)
|
||||
return res.json({
|
||||
data: result.sessions,
|
||||
total: result.total,
|
||||
limit,
|
||||
offset
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error('Error listing all sessions:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to list sessions',
|
||||
type: 'internal_error',
|
||||
code: 'session_list_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const getSessionById = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { sessionId } = req.params
|
||||
logger.info(`Getting session: ${sessionId}`)
|
||||
|
||||
const session = await sessionService.getSession(sessionId)
|
||||
|
||||
if (!session) {
|
||||
logger.warn(`Session not found: ${sessionId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Session not found',
|
||||
type: 'not_found',
|
||||
code: 'session_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Session retrieved successfully: ${sessionId}`)
|
||||
return res.json(session)
|
||||
} catch (error: any) {
|
||||
logger.error('Error getting session:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to get session',
|
||||
type: 'internal_error',
|
||||
code: 'session_get_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
227
src/main/apiServer/routes/agents/index.ts
Normal file
227
src/main/apiServer/routes/agents/index.ts
Normal file
@ -0,0 +1,227 @@
|
||||
import express from 'express'
|
||||
|
||||
import { agentHandlers, messageHandlers, sessionHandlers } from './handlers'
|
||||
import { checkAgentExists, handleValidationErrors } from './middleware'
|
||||
import {
|
||||
validateAgent,
|
||||
validateAgentId,
|
||||
validateAgentUpdate,
|
||||
validateBulkSessionMessages,
|
||||
validateMessageId,
|
||||
validatePagination,
|
||||
validateSession,
|
||||
validateSessionId,
|
||||
validateSessionMessage,
|
||||
validateSessionMessageUpdate,
|
||||
validateSessionUpdate
|
||||
} from './validators'
|
||||
|
||||
// Create main agents router
|
||||
const agentsRouter = express.Router()
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* AgentEntity:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: string
|
||||
* description: Unique agent identifier
|
||||
* name:
|
||||
* type: string
|
||||
* description: Agent name
|
||||
* description:
|
||||
* type: string
|
||||
* description: Agent description
|
||||
* avatar:
|
||||
* type: string
|
||||
* description: Agent avatar URL
|
||||
* instructions:
|
||||
* type: string
|
||||
* description: System prompt/instructions
|
||||
* model:
|
||||
* type: string
|
||||
* description: Main model ID
|
||||
* plan_model:
|
||||
* type: string
|
||||
* description: Optional planning model ID
|
||||
* small_model:
|
||||
* type: string
|
||||
* description: Optional small/fast model ID
|
||||
* built_in_tools:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: Built-in tool IDs
|
||||
* mcps:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: MCP tool IDs
|
||||
* knowledges:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: Knowledge base IDs
|
||||
* configuration:
|
||||
* type: object
|
||||
* description: Extensible settings
|
||||
* accessible_paths:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: Accessible directory paths
|
||||
* permission_mode:
|
||||
* type: string
|
||||
* enum: [readOnly, acceptEdits, bypassPermissions]
|
||||
* description: Permission mode
|
||||
* max_steps:
|
||||
* type: integer
|
||||
* description: Maximum steps the agent can take
|
||||
* created_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* updated_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* required:
|
||||
* - id
|
||||
* - name
|
||||
* - model
|
||||
* - created_at
|
||||
* - updated_at
|
||||
* CreateAgentRequest:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: Agent name
|
||||
* description:
|
||||
* type: string
|
||||
* description: Agent description
|
||||
* avatar:
|
||||
* type: string
|
||||
* description: Agent avatar URL
|
||||
* instructions:
|
||||
* type: string
|
||||
* description: System prompt/instructions
|
||||
* model:
|
||||
* type: string
|
||||
* description: Main model ID
|
||||
* plan_model:
|
||||
* type: string
|
||||
* description: Optional planning model ID
|
||||
* small_model:
|
||||
* type: string
|
||||
* description: Optional small/fast model ID
|
||||
* built_in_tools:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: Built-in tool IDs
|
||||
* mcps:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: MCP tool IDs
|
||||
* knowledges:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: Knowledge base IDs
|
||||
* configuration:
|
||||
* type: object
|
||||
* description: Extensible settings
|
||||
* accessible_paths:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: Accessible directory paths
|
||||
* permission_mode:
|
||||
* type: string
|
||||
* enum: [readOnly, acceptEdits, bypassPermissions]
|
||||
* description: Permission mode
|
||||
* max_steps:
|
||||
* type: integer
|
||||
* description: Maximum steps the agent can take
|
||||
* required:
|
||||
* - name
|
||||
* - model
|
||||
*/
|
||||
|
||||
// Agent CRUD routes
|
||||
agentsRouter.post('/', validateAgent, handleValidationErrors, agentHandlers.createAgent)
|
||||
agentsRouter.get('/', validatePagination, handleValidationErrors, agentHandlers.listAgents)
|
||||
agentsRouter.get('/:agentId', validateAgentId, handleValidationErrors, agentHandlers.getAgent)
|
||||
agentsRouter.put('/:agentId', validateAgentId, validateAgentUpdate, handleValidationErrors, agentHandlers.updateAgent)
|
||||
agentsRouter.patch('/:agentId', validateAgentId, validateAgentUpdate, handleValidationErrors, agentHandlers.patchAgent)
|
||||
agentsRouter.delete('/:agentId', validateAgentId, handleValidationErrors, agentHandlers.deleteAgent)
|
||||
|
||||
// Create sessions router with agent context
|
||||
const createSessionsRouter = (): express.Router => {
|
||||
const sessionsRouter = express.Router({ mergeParams: true })
|
||||
|
||||
// Session CRUD routes (nested under agent)
|
||||
sessionsRouter.post('/', validateSession, handleValidationErrors, sessionHandlers.createSession)
|
||||
sessionsRouter.get('/', validatePagination, handleValidationErrors, sessionHandlers.listSessions)
|
||||
sessionsRouter.get('/:sessionId', validateSessionId, handleValidationErrors, sessionHandlers.getSession)
|
||||
sessionsRouter.put(
|
||||
'/:sessionId',
|
||||
validateSessionId,
|
||||
validateSessionUpdate,
|
||||
handleValidationErrors,
|
||||
sessionHandlers.updateSession
|
||||
)
|
||||
sessionsRouter.patch(
|
||||
'/:sessionId',
|
||||
validateSessionId,
|
||||
validateSessionUpdate,
|
||||
handleValidationErrors,
|
||||
sessionHandlers.patchSession
|
||||
)
|
||||
sessionsRouter.delete('/:sessionId', validateSessionId, handleValidationErrors, sessionHandlers.deleteSession)
|
||||
|
||||
return sessionsRouter
|
||||
}
|
||||
|
||||
// Create messages router with agent and session context
|
||||
const createMessagesRouter = (): express.Router => {
|
||||
const messagesRouter = express.Router({ mergeParams: true })
|
||||
|
||||
// Message CRUD routes (nested under agent/session)
|
||||
messagesRouter.post('/', validateSessionMessage, handleValidationErrors, messageHandlers.createMessage)
|
||||
messagesRouter.post('/bulk', validateBulkSessionMessages, handleValidationErrors, messageHandlers.createBulkMessages)
|
||||
messagesRouter.get('/', validatePagination, handleValidationErrors, messageHandlers.listMessages)
|
||||
messagesRouter.get('/:messageId', validateMessageId, handleValidationErrors, messageHandlers.getMessage)
|
||||
messagesRouter.put(
|
||||
'/:messageId',
|
||||
validateMessageId,
|
||||
validateSessionMessageUpdate,
|
||||
handleValidationErrors,
|
||||
messageHandlers.updateMessage
|
||||
)
|
||||
messagesRouter.delete('/:messageId', validateMessageId, handleValidationErrors, messageHandlers.deleteMessage)
|
||||
|
||||
return messagesRouter
|
||||
}
|
||||
|
||||
// Mount nested resources with clear hierarchy
|
||||
const sessionsRouter = createSessionsRouter()
|
||||
const messagesRouter = createMessagesRouter()
|
||||
|
||||
// Mount sessions under specific agent
|
||||
agentsRouter.use('/:agentId/sessions', validateAgentId, checkAgentExists, handleValidationErrors, sessionsRouter)
|
||||
|
||||
// Mount messages under specific agent/session
|
||||
agentsRouter.use(
|
||||
'/:agentId/sessions/:sessionId/messages',
|
||||
validateAgentId,
|
||||
validateSessionId,
|
||||
handleValidationErrors,
|
||||
messagesRouter
|
||||
)
|
||||
|
||||
// Export main router and convenience router
|
||||
export const agentsRoutes = agentsRouter
|
||||
53
src/main/apiServer/routes/agents/middleware/common.ts
Normal file
53
src/main/apiServer/routes/agents/middleware/common.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { Request, Response } from 'express'
|
||||
import { validationResult } from 'express-validator'
|
||||
|
||||
import { agentService } from '../../../../services/agents'
|
||||
import { loggerService } from '../../../../services/LoggerService'
|
||||
|
||||
const logger = loggerService.withContext('ApiServerMiddleware')
|
||||
|
||||
// Error handler for validation
|
||||
export const handleValidationErrors = (req: Request, res: Response, next: any): void => {
|
||||
const errors = validationResult(req)
|
||||
if (!errors.isEmpty()) {
|
||||
res.status(400).json({
|
||||
error: {
|
||||
message: 'Validation failed',
|
||||
type: 'validation_error',
|
||||
details: errors.array()
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
next()
|
||||
}
|
||||
|
||||
// Middleware to check if agent exists
|
||||
export const checkAgentExists = async (req: Request, res: Response, next: any): Promise<void> => {
|
||||
try {
|
||||
const { agentId } = req.params
|
||||
const exists = await agentService.agentExists(agentId)
|
||||
|
||||
if (!exists) {
|
||||
res.status(404).json({
|
||||
error: {
|
||||
message: 'Agent not found',
|
||||
type: 'not_found',
|
||||
code: 'agent_not_found'
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
} catch (error) {
|
||||
logger.error('Error checking agent existence:', error as Error)
|
||||
res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to validate agent',
|
||||
type: 'internal_error',
|
||||
code: 'agent_validation_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
1
src/main/apiServer/routes/agents/middleware/index.ts
Normal file
1
src/main/apiServer/routes/agents/middleware/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './common'
|
||||
37
src/main/apiServer/routes/agents/validators/agents.ts
Normal file
37
src/main/apiServer/routes/agents/validators/agents.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { body, param } from 'express-validator'
|
||||
|
||||
export const validateAgent = [
|
||||
body('name').notEmpty().withMessage('Name is required'),
|
||||
body('model').notEmpty().withMessage('Model is required'),
|
||||
body('description').optional().isString(),
|
||||
body('avatar').optional().isString(),
|
||||
body('instructions').optional().isString(),
|
||||
body('plan_model').optional().isString(),
|
||||
body('small_model').optional().isString(),
|
||||
body('built_in_tools').optional().isArray(),
|
||||
body('mcps').optional().isArray(),
|
||||
body('knowledges').optional().isArray(),
|
||||
body('configuration').optional().isObject(),
|
||||
body('accessible_paths').optional().isArray(),
|
||||
body('permission_mode').optional().isIn(['readOnly', 'acceptEdits', 'bypassPermissions']),
|
||||
body('max_steps').optional().isInt({ min: 1 })
|
||||
]
|
||||
|
||||
export const validateAgentUpdate = [
|
||||
body('name').optional().notEmpty().withMessage('Name cannot be empty'),
|
||||
body('model').optional().notEmpty().withMessage('Model cannot be empty'),
|
||||
body('description').optional().isString(),
|
||||
body('avatar').optional().isString(),
|
||||
body('instructions').optional().isString(),
|
||||
body('plan_model').optional().isString(),
|
||||
body('small_model').optional().isString(),
|
||||
body('built_in_tools').optional().isArray(),
|
||||
body('mcps').optional().isArray(),
|
||||
body('knowledges').optional().isArray(),
|
||||
body('configuration').optional().isObject(),
|
||||
body('accessible_paths').optional().isArray(),
|
||||
body('permission_mode').optional().isIn(['readOnly', 'acceptEdits', 'bypassPermissions']),
|
||||
body('max_steps').optional().isInt({ min: 1 })
|
||||
]
|
||||
|
||||
export const validateAgentId = [param('agentId').notEmpty().withMessage('Agent ID is required')]
|
||||
10
src/main/apiServer/routes/agents/validators/common.ts
Normal file
10
src/main/apiServer/routes/agents/validators/common.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { query } from 'express-validator'
|
||||
|
||||
export const validatePagination = [
|
||||
query('limit').optional().isInt({ min: 1, max: 100 }).withMessage('Limit must be between 1 and 100'),
|
||||
query('offset').optional().isInt({ min: 0 }).withMessage('Offset must be non-negative'),
|
||||
query('status')
|
||||
.optional()
|
||||
.isIn(['idle', 'running', 'completed', 'failed', 'stopped'])
|
||||
.withMessage('Invalid status filter')
|
||||
]
|
||||
4
src/main/apiServer/routes/agents/validators/index.ts
Normal file
4
src/main/apiServer/routes/agents/validators/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './agents'
|
||||
export * from './common'
|
||||
export * from './messages'
|
||||
export * from './sessions'
|
||||
27
src/main/apiServer/routes/agents/validators/messages.ts
Normal file
27
src/main/apiServer/routes/agents/validators/messages.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { body, param } from 'express-validator'
|
||||
|
||||
export const validateSessionMessage = [
|
||||
body('parent_id').optional().isInt({ min: 1 }).withMessage('Parent ID must be a positive integer'),
|
||||
body('role').notEmpty().isIn(['user', 'agent', 'system', 'tool']).withMessage('Valid role is required'),
|
||||
body('type').notEmpty().isString().withMessage('Type is required'),
|
||||
body('content').notEmpty().isObject().withMessage('Content must be a valid object'),
|
||||
body('metadata').optional().isObject().withMessage('Metadata must be a valid object')
|
||||
]
|
||||
|
||||
export const validateSessionMessageUpdate = [
|
||||
body('content').optional().isObject().withMessage('Content must be a valid object'),
|
||||
body('metadata').optional().isObject().withMessage('Metadata must be a valid object')
|
||||
]
|
||||
|
||||
export const validateBulkSessionMessages = [
|
||||
body().isArray().withMessage('Request body must be an array'),
|
||||
body('*.parent_id').optional().isInt({ min: 1 }).withMessage('Parent ID must be a positive integer'),
|
||||
body('*.role').notEmpty().isIn(['user', 'agent', 'system', 'tool']).withMessage('Valid role is required'),
|
||||
body('*.type').notEmpty().isString().withMessage('Type is required'),
|
||||
body('*.content').notEmpty().isObject().withMessage('Content must be a valid object'),
|
||||
body('*.metadata').optional().isObject().withMessage('Metadata must be a valid object')
|
||||
]
|
||||
|
||||
export const validateMessageId = [
|
||||
param('messageId').isInt({ min: 1 }).withMessage('Message ID must be a positive integer')
|
||||
]
|
||||
47
src/main/apiServer/routes/agents/validators/sessions.ts
Normal file
47
src/main/apiServer/routes/agents/validators/sessions.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { body, param } from 'express-validator'
|
||||
|
||||
export const validateSession = [
|
||||
body('name').optional().isString(),
|
||||
body('sub_agent_ids').optional().isArray(),
|
||||
body('user_goal').optional().isString(),
|
||||
body('status').optional().isIn(['idle', 'running', 'completed', 'failed', 'stopped']),
|
||||
body('external_session_id').optional().isString(),
|
||||
body('model').optional().isString(),
|
||||
body('plan_model').optional().isString(),
|
||||
body('small_model').optional().isString(),
|
||||
body('built_in_tools').optional().isArray(),
|
||||
body('mcps').optional().isArray(),
|
||||
body('knowledges').optional().isArray(),
|
||||
body('configuration').optional().isObject(),
|
||||
body('accessible_paths').optional().isArray(),
|
||||
body('permission_mode').optional().isIn(['readOnly', 'acceptEdits', 'bypassPermissions']),
|
||||
body('max_steps').optional().isInt({ min: 1 })
|
||||
]
|
||||
|
||||
export const validateSessionUpdate = [
|
||||
body('name').optional().isString(),
|
||||
body('main_agent_id').optional().notEmpty().withMessage('Main agent ID cannot be empty'),
|
||||
body('sub_agent_ids').optional().isArray(),
|
||||
body('user_goal').optional().isString(),
|
||||
body('status').optional().isIn(['idle', 'running', 'completed', 'failed', 'stopped']),
|
||||
body('external_session_id').optional().isString(),
|
||||
body('model').optional().isString(),
|
||||
body('plan_model').optional().isString(),
|
||||
body('small_model').optional().isString(),
|
||||
body('built_in_tools').optional().isArray(),
|
||||
body('mcps').optional().isArray(),
|
||||
body('knowledges').optional().isArray(),
|
||||
body('configuration').optional().isObject(),
|
||||
body('accessible_paths').optional().isArray(),
|
||||
body('permission_mode').optional().isIn(['readOnly', 'acceptEdits', 'bypassPermissions']),
|
||||
body('max_steps').optional().isInt({ min: 1 })
|
||||
]
|
||||
|
||||
export const validateStatusUpdate = [
|
||||
body('status')
|
||||
.notEmpty()
|
||||
.isIn(['idle', 'running', 'completed', 'failed', 'stopped'])
|
||||
.withMessage('Valid status is required')
|
||||
]
|
||||
|
||||
export const validateSessionId = [param('sessionId').notEmpty().withMessage('Session ID is required')]
|
||||
@ -1,991 +0,0 @@
|
||||
import express, { Request, Response } from 'express'
|
||||
import { body, param, query, validationResult } from 'express-validator'
|
||||
|
||||
import { agentService, sessionMessageService, sessionService } from '../../services/agents'
|
||||
import { loggerService } from '../../services/LoggerService'
|
||||
|
||||
const logger = loggerService.withContext('ApiServerSessionMessagesRoutes')
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
// Validation middleware
|
||||
const validateSessionMessage = [
|
||||
body('parent_id').optional().isInt({ min: 1 }).withMessage('Parent ID must be a positive integer'),
|
||||
body('role').notEmpty().isIn(['user', 'agent', 'system', 'tool']).withMessage('Valid role is required'),
|
||||
body('type').notEmpty().isString().withMessage('Type is required'),
|
||||
body('content').notEmpty().isObject().withMessage('Content must be a valid object'),
|
||||
body('metadata').optional().isObject().withMessage('Metadata must be a valid object')
|
||||
]
|
||||
|
||||
const validateSessionMessageUpdate = [
|
||||
body('content').optional().isObject().withMessage('Content must be a valid object'),
|
||||
body('metadata').optional().isObject().withMessage('Metadata must be a valid object')
|
||||
]
|
||||
|
||||
const validateBulkSessionMessages = [
|
||||
body().isArray().withMessage('Request body must be an array'),
|
||||
body('*.parent_id').optional().isInt({ min: 1 }).withMessage('Parent ID must be a positive integer'),
|
||||
body('*.role').notEmpty().isIn(['user', 'agent', 'system', 'tool']).withMessage('Valid role is required'),
|
||||
body('*.type').notEmpty().isString().withMessage('Type is required'),
|
||||
body('*.content').notEmpty().isObject().withMessage('Content must be a valid object'),
|
||||
body('*.metadata').optional().isObject().withMessage('Metadata must be a valid object')
|
||||
]
|
||||
|
||||
const validateAgentId = [param('agentId').notEmpty().withMessage('Agent ID is required')]
|
||||
|
||||
const validateSessionId = [param('sessionId').notEmpty().withMessage('Session ID is required')]
|
||||
|
||||
const validateMessageId = [param('messageId').isInt({ min: 1 }).withMessage('Message ID must be a positive integer')]
|
||||
|
||||
const validatePagination = [
|
||||
query('limit').optional().isInt({ min: 1, max: 100 }).withMessage('Limit must be between 1 and 100'),
|
||||
query('offset').optional().isInt({ min: 0 }).withMessage('Offset must be non-negative')
|
||||
]
|
||||
|
||||
// Error handler for validation
|
||||
const handleValidationErrors = (req: Request, res: Response, next: any): void => {
|
||||
const errors = validationResult(req)
|
||||
if (!errors.isEmpty()) {
|
||||
res.status(400).json({
|
||||
error: {
|
||||
message: 'Validation failed',
|
||||
type: 'validation_error',
|
||||
details: errors.array()
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
next()
|
||||
}
|
||||
|
||||
// Middleware to check if agent and session exist
|
||||
const checkAgentAndSessionExist = async (req: Request, res: Response, next: any): Promise<void> => {
|
||||
try {
|
||||
const { agentId, sessionId } = req.params
|
||||
|
||||
const agentExists = await agentService.agentExists(agentId)
|
||||
if (!agentExists) {
|
||||
res.status(404).json({
|
||||
error: {
|
||||
message: 'Agent not found',
|
||||
type: 'not_found',
|
||||
code: 'agent_not_found'
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const session = await sessionService.getSession(sessionId)
|
||||
if (!session) {
|
||||
res.status(404).json({
|
||||
error: {
|
||||
message: 'Session not found',
|
||||
type: 'not_found',
|
||||
code: 'session_not_found'
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Verify session belongs to the agent
|
||||
if (session.main_agent_id !== agentId) {
|
||||
res.status(404).json({
|
||||
error: {
|
||||
message: 'Session not found for this agent',
|
||||
type: 'not_found',
|
||||
code: 'session_not_found'
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
} catch (error) {
|
||||
logger.error('Error checking agent and session existence:', error as Error)
|
||||
res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to validate agent and session',
|
||||
type: 'internal_error',
|
||||
code: 'validation_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* SessionMessageEntity:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* description: Unique message entry identifier
|
||||
* session_id:
|
||||
* type: string
|
||||
* description: Reference to session
|
||||
* parent_id:
|
||||
* type: integer
|
||||
* description: Parent message entry ID for tree structure
|
||||
* role:
|
||||
* type: string
|
||||
* enum: [user, agent, system, tool]
|
||||
* description: Role that created the message entry
|
||||
* type:
|
||||
* type: string
|
||||
* description: Type of message entry
|
||||
* content:
|
||||
* type: object
|
||||
* description: JSON structured message data
|
||||
* metadata:
|
||||
* type: object
|
||||
* description: Additional metadata
|
||||
* created_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* updated_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* required:
|
||||
* - id
|
||||
* - session_id
|
||||
* - role
|
||||
* - type
|
||||
* - content
|
||||
* - created_at
|
||||
* - updated_at
|
||||
* CreateSessionMessageRequest:
|
||||
* type: object
|
||||
* properties:
|
||||
* parent_id:
|
||||
* type: integer
|
||||
* description: Parent message entry ID for tree structure
|
||||
* role:
|
||||
* type: string
|
||||
* enum: [user, agent, system, tool]
|
||||
* description: Role that created the message entry
|
||||
* type:
|
||||
* type: string
|
||||
* description: Type of message entry
|
||||
* content:
|
||||
* type: object
|
||||
* description: JSON structured message data
|
||||
* metadata:
|
||||
* type: object
|
||||
* description: Additional metadata
|
||||
* required:
|
||||
* - role
|
||||
* - type
|
||||
* - content
|
||||
*/
|
||||
|
||||
// Create nested session messages router
|
||||
function createSessionMessagesRouter(): express.Router {
|
||||
const sessionMessagesRouter = express.Router({ mergeParams: true })
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}/sessions/{sessionId}/messages:
|
||||
* post:
|
||||
* summary: Create a new message entry for a session
|
||||
* description: Creates a new message entry for the specified session
|
||||
* tags: [Session 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
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/CreateSessionMessageRequest'
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Log entry created successfully
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/SessionMessageEntity'
|
||||
* 400:
|
||||
* description: Validation error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: Agent or session not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Internal server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
sessionMessagesRouter.post(
|
||||
'/',
|
||||
validateAgentId,
|
||||
validateSessionId,
|
||||
checkAgentAndSessionExist,
|
||||
validateSessionMessage,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { sessionId } = req.params
|
||||
const messageData = { ...req.body, session_id: sessionId }
|
||||
|
||||
logger.info(`Creating new message entry for session: ${sessionId}`)
|
||||
logger.debug('Message data:', messageData)
|
||||
|
||||
const message = await sessionMessageService.createSessionMessage(messageData)
|
||||
|
||||
logger.info(`Message entry created successfully: ${message.id}`)
|
||||
return res.status(201).json(message)
|
||||
} catch (error: any) {
|
||||
logger.error('Error creating session message:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to create message entry',
|
||||
type: 'internal_error',
|
||||
code: 'message_creation_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}/sessions/{sessionId}/messages/bulk:
|
||||
* post:
|
||||
* summary: Create multiple message entries for a session
|
||||
* description: Creates multiple message entries for the specified session in a single request
|
||||
* tags: [Session 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
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/CreateSessionMessageRequest'
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Log entries created successfully
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/SessionMessageEntity'
|
||||
* count:
|
||||
* type: integer
|
||||
* description: Number of message entries created
|
||||
* 400:
|
||||
* description: Validation error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: Agent or session not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Internal server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
sessionMessagesRouter.post(
|
||||
'/bulk',
|
||||
validateAgentId,
|
||||
validateSessionId,
|
||||
checkAgentAndSessionExist,
|
||||
validateBulkSessionMessages,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { sessionId } = req.params
|
||||
const messagesData = req.body.map((messageData: any) => ({ ...messageData, session_id: sessionId }))
|
||||
|
||||
logger.info(`Creating ${messagesData.length} message entries for session: ${sessionId}`)
|
||||
|
||||
const messages = await sessionMessageService.bulkCreateSessionMessages(messagesData)
|
||||
|
||||
logger.info(`${messages.length} message entries created successfully for session: ${sessionId}`)
|
||||
return res.status(201).json({
|
||||
data: messages,
|
||||
count: messages.length
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error('Error creating bulk session messages:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to create message entries',
|
||||
type: 'internal_error',
|
||||
code: 'bulk_message_creation_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}/sessions/{sessionId}/messages:
|
||||
* get:
|
||||
* summary: List message entries for a session
|
||||
* description: Retrieves a paginated list of message entries for the specified session
|
||||
* tags: [Session 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: query
|
||||
* name: limit
|
||||
* schema:
|
||||
* type: integer
|
||||
* minimum: 1
|
||||
* maximum: 100
|
||||
* default: 50
|
||||
* description: Number of message entries to return
|
||||
* - in: query
|
||||
* name: offset
|
||||
* schema:
|
||||
* type: integer
|
||||
* minimum: 0
|
||||
* default: 0
|
||||
* description: Number of message entries to skip
|
||||
* responses:
|
||||
* 200:
|
||||
* description: List of message entries
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/SessionMessageEntity'
|
||||
* total:
|
||||
* type: integer
|
||||
* description: Total number of message entries
|
||||
* limit:
|
||||
* type: integer
|
||||
* description: Number of message entries returned
|
||||
* offset:
|
||||
* type: integer
|
||||
* description: Number of message entries skipped
|
||||
* 400:
|
||||
* description: Validation error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: Agent or session not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Internal server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
sessionMessagesRouter.get(
|
||||
'/',
|
||||
validateAgentId,
|
||||
validateSessionId,
|
||||
checkAgentAndSessionExist,
|
||||
validatePagination,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { sessionId } = req.params
|
||||
const limit = req.query.limit ? parseInt(req.query.limit as string) : 50
|
||||
const offset = req.query.offset ? parseInt(req.query.offset as string) : 0
|
||||
|
||||
logger.info(`Listing messages for session: ${sessionId} with limit=${limit}, offset=${offset}`)
|
||||
|
||||
const result = await sessionMessageService.listSessionMessages(sessionId, { limit, offset })
|
||||
|
||||
logger.info(`Retrieved ${result.messages.length} messages (total: ${result.total}) for session: ${sessionId}`)
|
||||
return res.json({
|
||||
data: result.messages,
|
||||
total: result.total,
|
||||
limit,
|
||||
offset
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error('Error listing session messages:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to list message entries',
|
||||
type: 'internal_error',
|
||||
code: 'message_list_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}/sessions/{sessionId}/messages/{messageId}:
|
||||
* get:
|
||||
* summary: Get message entry by ID
|
||||
* description: Retrieves a specific message entry for the specified session
|
||||
* tags: [Session 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: Log entry ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Log entry details
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/SessionMessageEntity'
|
||||
* 404:
|
||||
* description: Agent, session, or message entry not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Internal server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
sessionMessagesRouter.get(
|
||||
'/:messageId',
|
||||
validateAgentId,
|
||||
validateSessionId,
|
||||
validateMessageId,
|
||||
checkAgentAndSessionExist,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { sessionId, messageId } = req.params
|
||||
const messageIdNum = parseInt(messageId)
|
||||
|
||||
logger.info(`Getting message entry: ${messageId} for session: ${sessionId}`)
|
||||
|
||||
const message = await sessionMessageService.getSessionMessage(messageIdNum)
|
||||
|
||||
if (!message) {
|
||||
logger.warn(`Message entry not found: ${messageId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Message entry not found',
|
||||
type: 'not_found',
|
||||
code: 'message_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Verify message belongs to the session
|
||||
if (message.session_id !== sessionId) {
|
||||
logger.warn(`Message entry ${messageId} does not belong to session ${sessionId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Message entry not found for this session',
|
||||
type: 'not_found',
|
||||
code: 'message_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Message entry retrieved successfully: ${messageId}`)
|
||||
return res.json(message)
|
||||
} catch (error: any) {
|
||||
logger.error('Error getting session message:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to get message entry',
|
||||
type: 'internal_error',
|
||||
code: 'message_get_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}/sessions/{sessionId}/messages/{messageId}:
|
||||
* put:
|
||||
* summary: Update message entry
|
||||
* description: Updates an existing message entry for the specified session
|
||||
* tags: [Session 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: Log entry ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* content:
|
||||
* type: object
|
||||
* description: Updated message content
|
||||
* metadata:
|
||||
* type: object
|
||||
* description: Updated metadata
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Log entry updated successfully
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/SessionMessageEntity'
|
||||
* 400:
|
||||
* description: Validation error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: Agent, session, or message entry not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Internal server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
sessionMessagesRouter.put(
|
||||
'/:messageId',
|
||||
validateAgentId,
|
||||
validateSessionId,
|
||||
validateMessageId,
|
||||
checkAgentAndSessionExist,
|
||||
validateSessionMessageUpdate,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { sessionId, messageId } = req.params
|
||||
const messageIdNum = parseInt(messageId)
|
||||
|
||||
logger.info(`Updating message entry: ${messageId} for session: ${sessionId}`)
|
||||
logger.debug('Update data:', req.body)
|
||||
|
||||
// First check if log exists and belongs to session
|
||||
const existingMessage = await sessionMessageService.getSessionMessage(messageIdNum)
|
||||
if (!existingMessage || existingMessage.session_id !== sessionId) {
|
||||
logger.warn(`Log entry ${messageId} not found for session ${sessionId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Message entry not found for this session',
|
||||
type: 'not_found',
|
||||
code: 'message_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const message = await sessionMessageService.updateSessionMessage(messageIdNum, req.body)
|
||||
|
||||
if (!message) {
|
||||
logger.warn(`Log entry not found for update: ${messageId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Message entry not found',
|
||||
type: 'not_found',
|
||||
code: 'message_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Log entry updated successfully: ${messageId}`)
|
||||
return res.json(message)
|
||||
} catch (error: any) {
|
||||
logger.error('Error updating session message:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to update message entry',
|
||||
type: 'internal_error',
|
||||
code: 'message_update_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}/sessions/{sessionId}/messages/{messageId}:
|
||||
* delete:
|
||||
* summary: Delete message entry
|
||||
* description: Deletes a specific message entry
|
||||
* tags: [Session 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: Log entry ID
|
||||
* responses:
|
||||
* 204:
|
||||
* description: Log entry deleted successfully
|
||||
* 404:
|
||||
* description: Agent, session, or message entry not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Internal server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
sessionMessagesRouter.delete(
|
||||
'/:messageId',
|
||||
validateAgentId,
|
||||
validateSessionId,
|
||||
validateMessageId,
|
||||
checkAgentAndSessionExist,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { sessionId, messageId } = req.params
|
||||
const messageIdNum = parseInt(messageId)
|
||||
|
||||
logger.info(`Deleting message entry: ${messageId} for session: ${sessionId}`)
|
||||
|
||||
// First check if log exists and belongs to session
|
||||
const existingMessage = await sessionMessageService.getSessionMessage(messageIdNum)
|
||||
if (!existingMessage || existingMessage.session_id !== sessionId) {
|
||||
logger.warn(`Log entry ${messageId} not found for session ${sessionId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Message entry not found for this session',
|
||||
type: 'not_found',
|
||||
code: 'message_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const deleted = await sessionMessageService.deleteSessionMessage(messageIdNum)
|
||||
|
||||
if (!deleted) {
|
||||
logger.warn(`Log entry not found for deletion: ${messageId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Message entry not found',
|
||||
type: 'not_found',
|
||||
code: 'message_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Log entry deleted successfully: ${messageId}`)
|
||||
return res.status(204).send()
|
||||
} catch (error: any) {
|
||||
logger.error('Error deleting session message:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to delete message entry',
|
||||
type: 'internal_error',
|
||||
code: 'message_delete_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return sessionMessagesRouter
|
||||
}
|
||||
|
||||
// Convenience routes (standalone session messages without agent context)
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/sessions/{sessionId}/messages:
|
||||
* get:
|
||||
* summary: List message entries for a session (convenience endpoint)
|
||||
* description: Retrieves a paginated list of message entries for the specified session without requiring agent context
|
||||
* tags: [Session Messages]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session ID
|
||||
* - in: query
|
||||
* name: limit
|
||||
* schema:
|
||||
* type: integer
|
||||
* minimum: 1
|
||||
* maximum: 100
|
||||
* default: 50
|
||||
* description: Number of message entries to return
|
||||
* - in: query
|
||||
* name: offset
|
||||
* schema:
|
||||
* type: integer
|
||||
* minimum: 0
|
||||
* default: 0
|
||||
* description: Number of message entries to skip
|
||||
* responses:
|
||||
* 200:
|
||||
* description: List of message entries
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/SessionMessageEntity'
|
||||
* total:
|
||||
* type: integer
|
||||
* description: Total number of message entries
|
||||
* limit:
|
||||
* type: integer
|
||||
* description: Number of message entries returned
|
||||
* offset:
|
||||
* type: integer
|
||||
* description: Number of message entries skipped
|
||||
* 400:
|
||||
* description: Validation error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: Session not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Internal server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.get(
|
||||
'/:sessionId/messages',
|
||||
validateSessionId,
|
||||
validatePagination,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { sessionId } = req.params
|
||||
const limit = req.query.limit ? parseInt(req.query.limit as string) : 50
|
||||
const offset = req.query.offset ? parseInt(req.query.offset as string) : 0
|
||||
|
||||
// Check if session exists
|
||||
const sessionExists = await sessionService.sessionExists(sessionId)
|
||||
if (!sessionExists) {
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Session not found',
|
||||
type: 'not_found',
|
||||
code: 'session_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Listing messages for session: ${sessionId} with limit=${limit}, offset=${offset}`)
|
||||
|
||||
const result = await sessionMessageService.listSessionMessages(sessionId, { limit, offset })
|
||||
|
||||
logger.info(`Retrieved ${result.messages.length} messages (total: ${result.total}) for session: ${sessionId}`)
|
||||
return res.json({
|
||||
data: result.messages,
|
||||
total: result.total,
|
||||
limit,
|
||||
offset
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error('Error listing session messages:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to list message entries',
|
||||
type: 'internal_error',
|
||||
code: 'message_list_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/session-messages/{messageId}:
|
||||
* get:
|
||||
* summary: Get message entry by ID (convenience endpoint)
|
||||
* description: Retrieves a specific message entry without requiring agent or session context
|
||||
* tags: [Session Messages]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: messageId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: Log entry ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Log entry details
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/SessionMessageEntity'
|
||||
* 404:
|
||||
* description: Log entry not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Internal server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.get(
|
||||
'/session-messages/:messageId',
|
||||
validateMessageId,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { messageId } = req.params
|
||||
const messageIdNum = parseInt(messageId)
|
||||
|
||||
logger.info(`Getting message entry: ${messageId}`)
|
||||
|
||||
const message = await sessionMessageService.getSessionMessage(messageIdNum)
|
||||
|
||||
if (!message) {
|
||||
logger.warn(`Log entry not found: ${messageId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Log entry not found',
|
||||
type: 'not_found',
|
||||
code: 'message_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Log entry retrieved successfully: ${messageId}`)
|
||||
return res.json(message)
|
||||
} catch (error: any) {
|
||||
logger.error('Error getting session message:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to get message entry',
|
||||
type: 'internal_error',
|
||||
code: 'message_get_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export { createSessionMessagesRouter, router as sessionMessagesRoutes }
|
||||
File diff suppressed because it is too large
Load Diff
@ -80,7 +80,11 @@ export class SessionMessageService extends BaseService {
|
||||
async getSessionMessage(id: number): Promise<SessionMessageEntity | null> {
|
||||
this.ensureInitialized()
|
||||
|
||||
const result = await this.database.select().from(sessionMessagesTable).where(eq(sessionMessagesTable.id, id)).limit(1)
|
||||
const result = await this.database
|
||||
.select()
|
||||
.from(sessionMessagesTable)
|
||||
.where(eq(sessionMessagesTable.id, id))
|
||||
.limit(1)
|
||||
|
||||
if (!result[0]) {
|
||||
return null
|
||||
|
||||
Loading…
Reference in New Issue
Block a user