mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 14:41:24 +08:00
✨ feat: add comprehensive PUT/PATCH support for agents and sessions APIs
- Add PATCH method to agents API for partial updates alongside existing PUT - Add PUT method to sessions API for complete replacement alongside existing PATCH - Update API documentation with clear PUT vs PATCH usage examples - Refactor session status updates to use standard PATCH endpoint - Ensure both methods use same validation middleware for consistency - Add comprehensive Swagger documentation for new endpoints This provides REST-compliant update operations where: - PUT: Complete resource replacement (idempotent) - PATCH: Partial resource updates (only specified fields) Both agents and sessions now support flexible update patterns for different use cases.
This commit is contained in:
parent
532bad8eb7
commit
aaba77c360
@ -32,7 +32,8 @@ This document provides comprehensive guidance for UI components to integrate wit
|
||||
| POST | `/agents` | Create new agent | `CreateAgentRequest` | `AgentEntity` |
|
||||
| GET | `/agents` | List agents (paginated) | Query params | `{ data: AgentEntity[], total: number }` |
|
||||
| GET | `/agents/{id}` | Get specific agent | - | `AgentEntity` |
|
||||
| PUT | `/agents/{id}` | Update agent | `UpdateAgentRequest` | `AgentEntity` |
|
||||
| PUT | `/agents/{id}` | Replace agent (complete update) | `UpdateAgentRequest` | `AgentEntity` |
|
||||
| PATCH | `/agents/{id}` | Partially update agent | `Partial<UpdateAgentRequest>` | `AgentEntity` |
|
||||
| DELETE | `/agents/{id}` | Delete agent | - | `204 No Content` |
|
||||
|
||||
### Session Management (`/agents/{agentId}/sessions`)
|
||||
@ -42,8 +43,8 @@ This document provides comprehensive guidance for UI components to integrate wit
|
||||
| POST | `/agents/{agentId}/sessions` | Create session | `CreateSessionRequest` | `AgentSessionEntity` |
|
||||
| GET | `/agents/{agentId}/sessions` | List agent sessions | Query params | `{ data: AgentSessionEntity[], total: number }` |
|
||||
| GET | `/agents/{agentId}/sessions/{id}` | Get specific session | - | `AgentSessionEntity` |
|
||||
| PUT | `/agents/{agentId}/sessions/{id}` | Update session | `UpdateSessionRequest` | `AgentSessionEntity` |
|
||||
| PATCH | `/agents/{agentId}/sessions/{id}/status` | Update session status | `{ status: SessionStatus }` | `AgentSessionEntity` |
|
||||
| PUT | `/agents/{agentId}/sessions/{id}` | Replace session (complete update) | `UpdateSessionRequest` | `AgentSessionEntity` |
|
||||
| PATCH | `/agents/{agentId}/sessions/{id}` | Partially update session | `Partial<UpdateSessionRequest>` | `AgentSessionEntity` |
|
||||
| DELETE | `/agents/{agentId}/sessions/{id}` | Delete session | - | `204 No Content` |
|
||||
|
||||
### Message Streaming (`/agents/{agentId}/sessions/{sessionId}/messages`)
|
||||
@ -53,6 +54,55 @@ This document provides comprehensive guidance for UI components to integrate wit
|
||||
| POST | `/agents/{agentId}/sessions/{sessionId}/messages` | Send message to agent | `CreateMessageRequest` | **Stream Response** |
|
||||
| GET | `/agents/{agentId}/sessions/{sessionId}/messages` | List session messages | Query params | `{ data: SessionMessageEntity[], total: number }` |
|
||||
|
||||
## HTTP Methods: PUT vs PATCH
|
||||
|
||||
Both agents and sessions support two types of update operations:
|
||||
|
||||
### PUT - Complete Replacement
|
||||
- **Purpose**: Replaces the entire resource with the provided data
|
||||
- **Behavior**: All fields in the request body will be applied to the resource
|
||||
- **Use Case**: When you want to completely update a resource with a new set of values
|
||||
- **Example**: Updating an agent's configuration completely
|
||||
|
||||
```typescript
|
||||
// PUT - Replace entire agent
|
||||
await fetch('/v1/agents/agent-123', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: 'New Agent Name',
|
||||
model: 'gpt-4',
|
||||
instructions: 'New instructions',
|
||||
built_in_tools: ['search', 'calculator'],
|
||||
// All other fields will be reset to defaults if not provided
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### PATCH - Partial Update
|
||||
- **Purpose**: Updates only the specified fields, leaving others unchanged
|
||||
- **Behavior**: Only the fields present in the request body will be modified
|
||||
- **Use Case**: When you want to update specific fields without affecting others
|
||||
- **Example**: Updating only an agent's name or instructions
|
||||
|
||||
```typescript
|
||||
// PATCH - Update only specific fields
|
||||
await fetch('/v1/agents/agent-123', {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: 'Updated Agent Name'
|
||||
// All other fields remain unchanged
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Validation
|
||||
Both methods use the same validation rules:
|
||||
- All fields are optional for both PUT and PATCH
|
||||
- When provided, fields must meet their validation criteria (e.g., `name` cannot be empty)
|
||||
- The same middleware (`validateAgentUpdate` for agents, `validateSessionUpdate` for sessions) handles both operations
|
||||
|
||||
## Data Types & Schemas
|
||||
|
||||
### AgentEntity
|
||||
@ -198,7 +248,42 @@ async function createSession(agentId: string, sessionData: CreateSessionRequest)
|
||||
}
|
||||
```
|
||||
|
||||
### Session Status Management
|
||||
### Session Updates
|
||||
Sessions can be updated using either PUT (complete replacement) or PATCH (partial update):
|
||||
|
||||
#### Complete Session Replacement (PUT)
|
||||
```typescript
|
||||
async function replaceSession(agentId: string, sessionId: string, sessionData: UpdateSessionRequest): Promise<AgentSessionEntity> {
|
||||
const response = await fetch(`/v1/agents/${agentId}/sessions/${sessionId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(sessionData) // Complete session configuration
|
||||
})
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
```
|
||||
|
||||
#### Partial Session Update (PATCH)
|
||||
```typescript
|
||||
async function updateSession(agentId: string, sessionId: string, updates: Partial<UpdateSessionRequest>): Promise<AgentSessionEntity> {
|
||||
const response = await fetch(`/v1/agents/${agentId}/sessions/${sessionId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(updates) // Only the fields to update
|
||||
})
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
```
|
||||
|
||||
#### Session Status Management
|
||||
Sessions have five possible statuses:
|
||||
- `idle`: Ready to process messages
|
||||
- `running`: Currently processing
|
||||
@ -207,17 +292,9 @@ Sessions have five possible statuses:
|
||||
- `stopped`: Manually stopped by user
|
||||
|
||||
```typescript
|
||||
// Update only the session status using PATCH
|
||||
async function updateSessionStatus(agentId: string, sessionId: string, status: SessionStatus): Promise<AgentSessionEntity> {
|
||||
const response = await fetch(`/v1/agents/${agentId}/sessions/${sessionId}/status`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ status })
|
||||
})
|
||||
|
||||
return await response.json()
|
||||
return await updateSession(agentId, sessionId, { status })
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -488,6 +488,144 @@ router.put(
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}:
|
||||
* patch:
|
||||
* summary: Partially update agent
|
||||
* description: Partially updates an existing agent with only the provided fields
|
||||
* tags: [Agents]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: agentId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Agent ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* 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
|
||||
* description: Only include the fields you want to update
|
||||
* 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.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)
|
||||
|
||||
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({
|
||||
error: {
|
||||
message: 'Failed to partially update agent',
|
||||
type: 'internal_error',
|
||||
code: 'agent_patch_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}:
|
||||
|
||||
@ -11,7 +11,6 @@ const router = express.Router()
|
||||
// Validation middleware
|
||||
const validateSession = [
|
||||
body('name').optional().isString(),
|
||||
body('main_agent_id').notEmpty().withMessage('Main agent ID is required'),
|
||||
body('sub_agent_ids').optional().isArray(),
|
||||
body('user_goal').optional().isString(),
|
||||
body('status').optional().isIn(['idle', 'running', 'completed', 'failed', 'stopped']),
|
||||
@ -545,6 +544,114 @@ function createSessionsRouter(): express.Router {
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}/sessions/{sessionId}:
|
||||
* put:
|
||||
* summary: Replace session
|
||||
* description: Completely replaces an existing session for the specified agent
|
||||
* tags: [Sessions]
|
||||
* 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/CreateSessionRequest'
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Session replaced successfully
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/AgentSessionEntity'
|
||||
* 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'
|
||||
*/
|
||||
sessionsRouter.put(
|
||||
'/:sessionId',
|
||||
validateAgentId,
|
||||
validateSessionId,
|
||||
checkAgentExists,
|
||||
validateSessionUpdate,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { agentId, sessionId } = req.params
|
||||
logger.info(`Replacing session: ${sessionId} for agent: ${agentId}`)
|
||||
logger.debug('Replace 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 replace: ${sessionId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Session not found',
|
||||
type: 'not_found',
|
||||
code: 'session_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Session replaced successfully: ${sessionId}`)
|
||||
return res.json(session)
|
||||
} catch (error: any) {
|
||||
logger.error('Error replacing session:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to replace session',
|
||||
type: 'internal_error',
|
||||
code: 'session_replace_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}/sessions/{sessionId}:
|
||||
* patch:
|
||||
* summary: Update session
|
||||
* description: Updates an existing session for the specified agent
|
||||
* tags: [Sessions]
|
||||
@ -593,7 +700,7 @@ function createSessionsRouter(): express.Router {
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
sessionsRouter.put(
|
||||
sessionsRouter.patch(
|
||||
'/:sessionId',
|
||||
validateAgentId,
|
||||
validateSessionId,
|
||||
@ -618,8 +725,8 @@ function createSessionsRouter(): express.Router {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const session = await sessionService.updateSession(sessionId, req.body)
|
||||
const updateSession = { ...existingSession, ...req.body }
|
||||
const session = await sessionService.updateSession(sessionId, updateSession)
|
||||
|
||||
if (!session) {
|
||||
logger.warn(`Session not found for update: ${sessionId}`)
|
||||
@ -647,119 +754,6 @@ function createSessionsRouter(): express.Router {
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}/sessions/{sessionId}/status:
|
||||
* patch:
|
||||
* summary: Update session status
|
||||
* description: Updates the status of a specific session
|
||||
* tags: [Sessions]
|
||||
* 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: object
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [idle, running, completed, failed, stopped]
|
||||
* required:
|
||||
* - status
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Session status updated successfully
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/AgentSessionEntity'
|
||||
* 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'
|
||||
*/
|
||||
sessionsRouter.patch(
|
||||
'/:sessionId/status',
|
||||
validateAgentId,
|
||||
validateSessionId,
|
||||
checkAgentExists,
|
||||
validateStatusUpdate,
|
||||
handleValidationErrors,
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { agentId, sessionId } = req.params
|
||||
const { status } = req.body
|
||||
|
||||
logger.info(`Updating session status: ${sessionId} for agent: ${agentId} to ${status}`)
|
||||
|
||||
// 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 session = await sessionService.updateSessionStatus(sessionId, status)
|
||||
|
||||
if (!session) {
|
||||
logger.warn(`Session not found for status update: ${sessionId}`)
|
||||
return res.status(404).json({
|
||||
error: {
|
||||
message: 'Session not found',
|
||||
type: 'not_found',
|
||||
code: 'session_not_found'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Session status updated successfully: ${sessionId} -> ${status}`)
|
||||
return res.json(session)
|
||||
} catch (error: any) {
|
||||
logger.error('Error updating session status:', error)
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
message: 'Failed to update session status',
|
||||
type: 'internal_error',
|
||||
code: 'session_status_update_failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /v1/agents/{agentId}/sessions/{sessionId}:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user