mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-09 14:59:27 +08:00
🚀 feat: add comprehensive REST API for agent management
Implement full REST API with Express routes for agents, sessions, and logs: - CRUD operations for agents with validation and OpenAPI documentation - Session management with nested resource endpoints - Hierarchical logging system with bulk operations support - Request validation using express-validator - Proper error handling and structured responses
This commit is contained in:
parent
d26d02babc
commit
2ec3b20b23
@ -6,9 +6,12 @@ import { v4 as uuidv4 } from 'uuid'
|
||||
import { authMiddleware } from './middleware/auth'
|
||||
import { errorHandler } from './middleware/error'
|
||||
import { setupOpenAPIDocumentation } from './middleware/openapi'
|
||||
import { agentsRoutes } from './routes/agents'
|
||||
import { chatRoutes } from './routes/chat'
|
||||
import { mcpRoutes } from './routes/mcp'
|
||||
import { modelsRoutes } from './routes/models'
|
||||
import { sessionLogsRoutes } from './routes/session-logs'
|
||||
import { sessionsRoutes } from './routes/sessions'
|
||||
|
||||
const logger = loggerService.withContext('ApiServer')
|
||||
|
||||
@ -104,7 +107,10 @@ app.get('/', (_req, res) => {
|
||||
health: 'GET /health',
|
||||
models: 'GET /v1/models',
|
||||
chat: 'POST /v1/chat/completions',
|
||||
mcp: 'GET /v1/mcps'
|
||||
mcp: 'GET /v1/mcps',
|
||||
agents: 'GET /v1/agents',
|
||||
sessions: 'GET /v1/sessions',
|
||||
logs: 'GET /v1/sessions/{sessionId}/logs'
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -117,6 +123,9 @@ apiRouter.use(express.json())
|
||||
apiRouter.use('/chat', chatRoutes)
|
||||
apiRouter.use('/mcps', mcpRoutes)
|
||||
apiRouter.use('/models', modelsRoutes)
|
||||
apiRouter.use('/agents', agentsRoutes)
|
||||
apiRouter.use('/sessions', sessionsRoutes)
|
||||
apiRouter.use('/', sessionLogsRoutes) // This handles /sessions/:sessionId/logs and /session-logs/:logId
|
||||
app.use('/v1', apiRouter)
|
||||
|
||||
// Setup OpenAPI documentation
|
||||
|
||||
564
src/main/apiServer/routes/agents.ts
Normal file
564
src/main/apiServer/routes/agents.ts
Normal file
@ -0,0 +1,564 @@
|
||||
import express, { Request, Response } from 'express'
|
||||
import { body, param, query, validationResult } from 'express-validator'
|
||||
|
||||
import { agentService } from '../../services/agents/AgentService'
|
||||
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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents:
|
||||
* post:
|
||||
* summary: Create a new agent
|
||||
* description: Creates a new autonomous agent with the specified configuration
|
||||
* tags: [Agents]
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/CreateAgentRequest'
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Agent created successfully
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/AgentEntity'
|
||||
* 400:
|
||||
* description: Validation error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Internal server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.post('/', validateAgent, handleValidationErrors, async (req: Request, res: Response) => {
|
||||
try {
|
||||
logger.info('Creating new agent')
|
||||
logger.debug('Agent data:', req.body)
|
||||
|
||||
const agent = await agentService.createAgent(req.body)
|
||||
|
||||
logger.info(`Agent created successfully: ${agent.id}`)
|
||||
return res.status(201).json(agent)
|
||||
} catch (error: any) {
|
||||
logger.error('Error creating agent:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to create agent',
|
||||
type: 'internal_error',
|
||||
code: 'agent_creation_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents:
|
||||
* get:
|
||||
* summary: List all agents
|
||||
* description: Retrieves a paginated list of all agents
|
||||
* tags: [Agents]
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: limit
|
||||
* schema:
|
||||
* type: integer
|
||||
* minimum: 1
|
||||
* maximum: 100
|
||||
* default: 20
|
||||
* description: Number of agents to return
|
||||
* - in: query
|
||||
* name: offset
|
||||
* schema:
|
||||
* type: integer
|
||||
* minimum: 0
|
||||
* default: 0
|
||||
* description: Number of agents to skip
|
||||
* responses:
|
||||
* 200:
|
||||
* description: List of agents
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/AgentEntity'
|
||||
* total:
|
||||
* type: integer
|
||||
* description: Total number of agents
|
||||
* limit:
|
||||
* type: integer
|
||||
* description: Number of agents returned
|
||||
* offset:
|
||||
* type: integer
|
||||
* description: Number of agents skipped
|
||||
* 400:
|
||||
* description: Validation error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Internal server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.get('/', validatePagination, handleValidationErrors, async (req: Request, res: 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
|
||||
|
||||
logger.info(`Listing agents with limit=${limit}, offset=${offset}`)
|
||||
|
||||
const result = await agentService.listAgents({ limit, offset })
|
||||
|
||||
logger.info(`Retrieved ${result.agents.length} agents (total: ${result.total})`)
|
||||
return res.json({
|
||||
data: result.agents,
|
||||
total: result.total,
|
||||
limit,
|
||||
offset
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error('Error listing agents:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to list agents',
|
||||
type: 'internal_error',
|
||||
code: 'agent_list_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}:
|
||||
* get:
|
||||
* summary: Get agent by ID
|
||||
* description: Retrieves a specific agent by its ID
|
||||
* tags: [Agents]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: agentId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Agent ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Agent details
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/AgentEntity'
|
||||
* 404:
|
||||
* description: Agent 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('/:agentId', validateAgentId, handleValidationErrors, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { agentId } = req.params
|
||||
logger.info(`Getting agent: ${agentId}`)
|
||||
|
||||
const agent = await agentService.getAgent(agentId)
|
||||
|
||||
if (!agent) {
|
||||
logger.warn(`Agent not found: ${agentId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Agent not found',
|
||||
type: 'not_found',
|
||||
code: 'agent_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Agent retrieved successfully: ${agentId}`)
|
||||
return res.json(agent)
|
||||
} catch (error: any) {
|
||||
logger.error('Error getting agent:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to get agent',
|
||||
type: 'internal_error',
|
||||
code: 'agent_get_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}:
|
||||
* put:
|
||||
* summary: Update agent
|
||||
* description: Updates an existing agent with the provided data
|
||||
* tags: [Agents]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: agentId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Agent ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/CreateAgentRequest'
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Agent updated successfully
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/AgentEntity'
|
||||
* 400:
|
||||
* description: Validation error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: Agent not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Internal server error
|
||||
* content:
|
||||
* application/json:
|
||||
* 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)
|
||||
|
||||
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({
|
||||
error: {
|
||||
message: 'Failed to update agent',
|
||||
type: 'internal_error',
|
||||
code: 'agent_update_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}:
|
||||
* delete:
|
||||
* summary: Delete agent
|
||||
* description: Deletes an agent and all associated sessions and logs
|
||||
* tags: [Agents]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: agentId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Agent ID
|
||||
* responses:
|
||||
* 204:
|
||||
* description: Agent deleted successfully
|
||||
* 404:
|
||||
* description: Agent not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Internal server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.delete('/:agentId', validateAgentId, handleValidationErrors, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { agentId } = req.params
|
||||
logger.info(`Deleting agent: ${agentId}`)
|
||||
|
||||
const deleted = await agentService.deleteAgent(agentId)
|
||||
|
||||
if (!deleted) {
|
||||
logger.warn(`Agent not found for deletion: ${agentId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Agent not found',
|
||||
type: 'not_found',
|
||||
code: 'agent_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Agent deleted successfully: ${agentId}`)
|
||||
return res.status(204).send()
|
||||
} catch (error: any) {
|
||||
logger.error('Error deleting agent:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to delete agent',
|
||||
type: 'internal_error',
|
||||
code: 'agent_delete_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Mount session routes as nested resources
|
||||
import { createSessionLogsRouter } from './session-logs'
|
||||
import { createSessionsRouter } from './sessions'
|
||||
|
||||
const sessionsRouter = createSessionsRouter()
|
||||
const sessionLogsRouter = createSessionLogsRouter()
|
||||
|
||||
// Mount nested routes
|
||||
router.use('/:agentId/sessions', sessionsRouter)
|
||||
router.use('/:agentId/sessions/:sessionId/logs', sessionLogsRouter)
|
||||
|
||||
export { router as agentsRoutes }
|
||||
986
src/main/apiServer/routes/session-logs.ts
Normal file
986
src/main/apiServer/routes/session-logs.ts
Normal file
@ -0,0 +1,986 @@
|
||||
import express, { Request, Response } from 'express'
|
||||
import { body, param, query, validationResult } from 'express-validator'
|
||||
|
||||
import { agentService } from '../../services/agents/AgentService'
|
||||
import { loggerService } from '../../services/LoggerService'
|
||||
|
||||
const logger = loggerService.withContext('ApiServerSessionLogsRoutes')
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
// Validation middleware
|
||||
const validateSessionLog = [
|
||||
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 validateSessionLogUpdate = [
|
||||
body('content').optional().isObject().withMessage('Content must be a valid object'),
|
||||
body('metadata').optional().isObject().withMessage('Metadata must be a valid object')
|
||||
]
|
||||
|
||||
const validateBulkSessionLogs = [
|
||||
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 validateLogId = [param('logId').isInt({ min: 1 }).withMessage('Log 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 agentService.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:
|
||||
* SessionLogEntity:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* description: Unique log entry identifier
|
||||
* session_id:
|
||||
* type: string
|
||||
* description: Reference to session
|
||||
* parent_id:
|
||||
* type: integer
|
||||
* description: Parent log entry ID for tree structure
|
||||
* role:
|
||||
* type: string
|
||||
* enum: [user, agent, system, tool]
|
||||
* description: Role that created the log entry
|
||||
* type:
|
||||
* type: string
|
||||
* description: Type of log entry
|
||||
* content:
|
||||
* type: object
|
||||
* description: JSON structured log 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
|
||||
* CreateSessionLogRequest:
|
||||
* type: object
|
||||
* properties:
|
||||
* parent_id:
|
||||
* type: integer
|
||||
* description: Parent log entry ID for tree structure
|
||||
* role:
|
||||
* type: string
|
||||
* enum: [user, agent, system, tool]
|
||||
* description: Role that created the log entry
|
||||
* type:
|
||||
* type: string
|
||||
* description: Type of log entry
|
||||
* content:
|
||||
* type: object
|
||||
* description: JSON structured log data
|
||||
* metadata:
|
||||
* type: object
|
||||
* description: Additional metadata
|
||||
* required:
|
||||
* - role
|
||||
* - type
|
||||
* - content
|
||||
*/
|
||||
|
||||
// Create nested session logs router
|
||||
function createSessionLogsRouter(): express.Router {
|
||||
const sessionLogsRouter = express.Router({ mergeParams: true })
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}/sessions/{sessionId}/logs:
|
||||
* post:
|
||||
* summary: Create a new log entry for a session
|
||||
* description: Creates a new log entry for the specified session
|
||||
* tags: [Session Logs]
|
||||
* 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/CreateSessionLogRequest'
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Log entry created successfully
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/SessionLogEntity'
|
||||
* 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'
|
||||
*/
|
||||
sessionLogsRouter.post(
|
||||
'/',
|
||||
validateAgentId,
|
||||
validateSessionId,
|
||||
checkAgentAndSessionExist,
|
||||
validateSessionLog,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { sessionId } = req.params
|
||||
const logData = { ...req.body, session_id: sessionId }
|
||||
|
||||
logger.info(`Creating new log entry for session: ${sessionId}`)
|
||||
logger.debug('Log data:', logData)
|
||||
|
||||
const log = await agentService.createSessionLog(logData)
|
||||
|
||||
logger.info(`Log entry created successfully: ${log.id}`)
|
||||
return res.status(201).json(log)
|
||||
} catch (error: any) {
|
||||
logger.error('Error creating session log:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to create log entry',
|
||||
type: 'internal_error',
|
||||
code: 'log_creation_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}/sessions/{sessionId}/logs/bulk:
|
||||
* post:
|
||||
* summary: Create multiple log entries for a session
|
||||
* description: Creates multiple log entries for the specified session in a single request
|
||||
* tags: [Session Logs]
|
||||
* 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/CreateSessionLogRequest'
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Log entries created successfully
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/SessionLogEntity'
|
||||
* count:
|
||||
* type: integer
|
||||
* description: Number of log 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'
|
||||
*/
|
||||
sessionLogsRouter.post(
|
||||
'/bulk',
|
||||
validateAgentId,
|
||||
validateSessionId,
|
||||
checkAgentAndSessionExist,
|
||||
validateBulkSessionLogs,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { sessionId } = req.params
|
||||
const logsData = req.body.map((logData: any) => ({ ...logData, session_id: sessionId }))
|
||||
|
||||
logger.info(`Creating ${logsData.length} log entries for session: ${sessionId}`)
|
||||
|
||||
const logs = await agentService.bulkCreateSessionLogs(logsData)
|
||||
|
||||
logger.info(`${logs.length} log entries created successfully for session: ${sessionId}`)
|
||||
return res.status(201).json({
|
||||
data: logs,
|
||||
count: logs.length
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error('Error creating bulk session logs:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to create log entries',
|
||||
type: 'internal_error',
|
||||
code: 'bulk_log_creation_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}/sessions/{sessionId}/logs:
|
||||
* get:
|
||||
* summary: List log entries for a session
|
||||
* description: Retrieves a paginated list of log entries for the specified session
|
||||
* tags: [Session Logs]
|
||||
* 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 log entries to return
|
||||
* - in: query
|
||||
* name: offset
|
||||
* schema:
|
||||
* type: integer
|
||||
* minimum: 0
|
||||
* default: 0
|
||||
* description: Number of log entries to skip
|
||||
* responses:
|
||||
* 200:
|
||||
* description: List of log entries
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/SessionLogEntity'
|
||||
* total:
|
||||
* type: integer
|
||||
* description: Total number of log entries
|
||||
* limit:
|
||||
* type: integer
|
||||
* description: Number of log entries returned
|
||||
* offset:
|
||||
* type: integer
|
||||
* description: Number of log 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'
|
||||
*/
|
||||
sessionLogsRouter.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 logs for session: ${sessionId} with limit=${limit}, offset=${offset}`)
|
||||
|
||||
const result = await agentService.listSessionLogs(sessionId, { limit, offset })
|
||||
|
||||
logger.info(`Retrieved ${result.logs.length} logs (total: ${result.total}) for session: ${sessionId}`)
|
||||
return res.json({
|
||||
data: result.logs,
|
||||
total: result.total,
|
||||
limit,
|
||||
offset
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error('Error listing session logs:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to list log entries',
|
||||
type: 'internal_error',
|
||||
code: 'log_list_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}/sessions/{sessionId}/logs/{logId}:
|
||||
* get:
|
||||
* summary: Get log entry by ID
|
||||
* description: Retrieves a specific log entry for the specified session
|
||||
* tags: [Session Logs]
|
||||
* 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: logId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: Log entry ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Log entry details
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/SessionLogEntity'
|
||||
* 404:
|
||||
* description: Agent, session, or 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'
|
||||
*/
|
||||
sessionLogsRouter.get(
|
||||
'/:logId',
|
||||
validateAgentId,
|
||||
validateSessionId,
|
||||
validateLogId,
|
||||
checkAgentAndSessionExist,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { sessionId, logId } = req.params
|
||||
const logIdNum = parseInt(logId)
|
||||
|
||||
logger.info(`Getting log entry: ${logId} for session: ${sessionId}`)
|
||||
|
||||
const log = await agentService.getSessionLog(logIdNum)
|
||||
|
||||
if (!log) {
|
||||
logger.warn(`Log entry not found: ${logId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Log entry not found',
|
||||
type: 'not_found',
|
||||
code: 'log_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Verify log belongs to the session
|
||||
if (log.session_id !== sessionId) {
|
||||
logger.warn(`Log entry ${logId} does not belong to session ${sessionId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Log entry not found for this session',
|
||||
type: 'not_found',
|
||||
code: 'log_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Log entry retrieved successfully: ${logId}`)
|
||||
return res.json(log)
|
||||
} catch (error: any) {
|
||||
logger.error('Error getting session log:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to get log entry',
|
||||
type: 'internal_error',
|
||||
code: 'log_get_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}/sessions/{sessionId}/logs/{logId}:
|
||||
* put:
|
||||
* summary: Update log entry
|
||||
* description: Updates an existing log entry for the specified session
|
||||
* tags: [Session Logs]
|
||||
* 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: logId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: Log entry ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* content:
|
||||
* type: object
|
||||
* description: Updated log content
|
||||
* metadata:
|
||||
* type: object
|
||||
* description: Updated metadata
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Log entry updated successfully
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/SessionLogEntity'
|
||||
* 400:
|
||||
* description: Validation error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: Agent, session, or 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'
|
||||
*/
|
||||
sessionLogsRouter.put(
|
||||
'/:logId',
|
||||
validateAgentId,
|
||||
validateSessionId,
|
||||
validateLogId,
|
||||
checkAgentAndSessionExist,
|
||||
validateSessionLogUpdate,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { sessionId, logId } = req.params
|
||||
const logIdNum = parseInt(logId)
|
||||
|
||||
logger.info(`Updating log entry: ${logId} for session: ${sessionId}`)
|
||||
logger.debug('Update data:', req.body)
|
||||
|
||||
// First check if log exists and belongs to session
|
||||
const existingLog = await agentService.getSessionLog(logIdNum)
|
||||
if (!existingLog || existingLog.session_id !== sessionId) {
|
||||
logger.warn(`Log entry ${logId} not found for session ${sessionId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Log entry not found for this session',
|
||||
type: 'not_found',
|
||||
code: 'log_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const log = await agentService.updateSessionLog(logIdNum, req.body)
|
||||
|
||||
if (!log) {
|
||||
logger.warn(`Log entry not found for update: ${logId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Log entry not found',
|
||||
type: 'not_found',
|
||||
code: 'log_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Log entry updated successfully: ${logId}`)
|
||||
return res.json(log)
|
||||
} catch (error: any) {
|
||||
logger.error('Error updating session log:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to update log entry',
|
||||
type: 'internal_error',
|
||||
code: 'log_update_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}/sessions/{sessionId}/logs/{logId}:
|
||||
* delete:
|
||||
* summary: Delete log entry
|
||||
* description: Deletes a specific log entry
|
||||
* tags: [Session Logs]
|
||||
* 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: logId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: Log entry ID
|
||||
* responses:
|
||||
* 204:
|
||||
* description: Log entry deleted successfully
|
||||
* 404:
|
||||
* description: Agent, session, or 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'
|
||||
*/
|
||||
sessionLogsRouter.delete(
|
||||
'/:logId',
|
||||
validateAgentId,
|
||||
validateSessionId,
|
||||
validateLogId,
|
||||
checkAgentAndSessionExist,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { sessionId, logId } = req.params
|
||||
const logIdNum = parseInt(logId)
|
||||
|
||||
logger.info(`Deleting log entry: ${logId} for session: ${sessionId}`)
|
||||
|
||||
// First check if log exists and belongs to session
|
||||
const existingLog = await agentService.getSessionLog(logIdNum)
|
||||
if (!existingLog || existingLog.session_id !== sessionId) {
|
||||
logger.warn(`Log entry ${logId} not found for session ${sessionId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Log entry not found for this session',
|
||||
type: 'not_found',
|
||||
code: 'log_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const deleted = await agentService.deleteSessionLog(logIdNum)
|
||||
|
||||
if (!deleted) {
|
||||
logger.warn(`Log entry not found for deletion: ${logId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Log entry not found',
|
||||
type: 'not_found',
|
||||
code: 'log_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Log entry deleted successfully: ${logId}`)
|
||||
return res.status(204).send()
|
||||
} catch (error: any) {
|
||||
logger.error('Error deleting session log:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to delete log entry',
|
||||
type: 'internal_error',
|
||||
code: 'log_delete_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return sessionLogsRouter
|
||||
}
|
||||
|
||||
// Convenience routes (standalone session logs without agent context)
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/sessions/{sessionId}/logs:
|
||||
* get:
|
||||
* summary: List log entries for a session (convenience endpoint)
|
||||
* description: Retrieves a paginated list of log entries for the specified session without requiring agent context
|
||||
* tags: [Session Logs]
|
||||
* 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 log entries to return
|
||||
* - in: query
|
||||
* name: offset
|
||||
* schema:
|
||||
* type: integer
|
||||
* minimum: 0
|
||||
* default: 0
|
||||
* description: Number of log entries to skip
|
||||
* responses:
|
||||
* 200:
|
||||
* description: List of log entries
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/SessionLogEntity'
|
||||
* total:
|
||||
* type: integer
|
||||
* description: Total number of log entries
|
||||
* limit:
|
||||
* type: integer
|
||||
* description: Number of log entries returned
|
||||
* offset:
|
||||
* type: integer
|
||||
* description: Number of log 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/logs',
|
||||
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 agentService.sessionExists(sessionId)
|
||||
if (!sessionExists) {
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Session not found',
|
||||
type: 'not_found',
|
||||
code: 'session_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Listing logs for session: ${sessionId} with limit=${limit}, offset=${offset}`)
|
||||
|
||||
const result = await agentService.listSessionLogs(sessionId, { limit, offset })
|
||||
|
||||
logger.info(`Retrieved ${result.logs.length} logs (total: ${result.total}) for session: ${sessionId}`)
|
||||
return res.json({
|
||||
data: result.logs,
|
||||
total: result.total,
|
||||
limit,
|
||||
offset
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error('Error listing session logs:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to list log entries',
|
||||
type: 'internal_error',
|
||||
code: 'log_list_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/session-logs/{logId}:
|
||||
* get:
|
||||
* summary: Get log entry by ID (convenience endpoint)
|
||||
* description: Retrieves a specific log entry without requiring agent or session context
|
||||
* tags: [Session Logs]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: logId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: Log entry ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Log entry details
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/SessionLogEntity'
|
||||
* 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-logs/:logId', validateLogId, handleValidationErrors, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { logId } = req.params
|
||||
const logIdNum = parseInt(logId)
|
||||
|
||||
logger.info(`Getting log entry: ${logId}`)
|
||||
|
||||
const log = await agentService.getSessionLog(logIdNum)
|
||||
|
||||
if (!log) {
|
||||
logger.warn(`Log entry not found: ${logId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Log entry not found',
|
||||
type: 'not_found',
|
||||
code: 'log_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Log entry retrieved successfully: ${logId}`)
|
||||
return res.json(log)
|
||||
} catch (error: any) {
|
||||
logger.error('Error getting session log:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to get log entry',
|
||||
type: 'internal_error',
|
||||
code: 'log_get_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export { createSessionLogsRouter, router as sessionLogsRoutes }
|
||||
1013
src/main/apiServer/routes/sessions.ts
Normal file
1013
src/main/apiServer/routes/sessions.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -28,6 +28,7 @@ import { TrayService } from './services/TrayService'
|
||||
import { windowService } from './services/WindowService'
|
||||
import process from 'node:process'
|
||||
import { apiServerService } from './services/ApiServerService'
|
||||
import { agentService } from './services/agents/AgentService'
|
||||
|
||||
const logger = loggerService.withContext('MainEntry')
|
||||
|
||||
@ -147,6 +148,14 @@ if (!app.requestSingleInstanceLock()) {
|
||||
//start selection assistant service
|
||||
initSelectionService()
|
||||
|
||||
// Initialize Agent Service
|
||||
try {
|
||||
await agentService.initialize()
|
||||
logger.info('Agent service initialized successfully')
|
||||
} catch (error: any) {
|
||||
logger.error('Failed to initialize Agent service:', error)
|
||||
}
|
||||
|
||||
// Start API server if enabled
|
||||
try {
|
||||
const config = await apiServerService.getCurrentConfig()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user