mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-08 22:39:36 +08:00
✨ feat: implement comprehensive CRUD APIs for agent management with type support
This commit is contained in:
parent
e3f5033bc4
commit
5eaa90a7a2
224
plan.md
Normal file
224
plan.md
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
Overview
|
||||||
|
|
||||||
|
Implement comprehensive CRUD APIs for agent, agentSession, and agentSessionLogs management
|
||||||
|
in Cherry Studio's API server using RESTful URL conventions.
|
||||||
|
|
||||||
|
Architecture Overview
|
||||||
|
|
||||||
|
1. Service Layer
|
||||||
|
|
||||||
|
- Create AgentService class in src/main/services/agents/AgentService.ts
|
||||||
|
- Handles database operations using SQL queries from db.ts
|
||||||
|
- Manages SQLite database initialization and connections
|
||||||
|
- Provides business logic for agent operations
|
||||||
|
|
||||||
|
2. API Routes
|
||||||
|
|
||||||
|
- Create route files in src/main/apiServer/routes/:
|
||||||
|
- agents.ts - Agent CRUD endpoints
|
||||||
|
- sessions.ts - Session CRUD endpoints
|
||||||
|
- session-logs.ts - Session logs CRUD endpoints
|
||||||
|
|
||||||
|
3. Database Integration
|
||||||
|
|
||||||
|
- Use SQLite with @libsql/client (following MemoryService pattern)
|
||||||
|
- Database location: userData/agents.db
|
||||||
|
- Leverage existing SQL queries in src/main/services/agents/db.ts
|
||||||
|
|
||||||
|
Implementation Steps
|
||||||
|
|
||||||
|
Phase 1: Database Service Setup
|
||||||
|
|
||||||
|
1. Create AgentService class with database initialization
|
||||||
|
2. Implement database connection management
|
||||||
|
3. Add database initialization to main process startup
|
||||||
|
4. Create helper methods for JSON field serialization/deserialization
|
||||||
|
|
||||||
|
Phase 2: Agent CRUD Operations
|
||||||
|
|
||||||
|
1. Implement service methods:
|
||||||
|
- createAgent(agent: Omit<AgentEntity, 'id' | 'created_at' | 'updated_at'>)
|
||||||
|
- getAgent(id: string)
|
||||||
|
- listAgents(options?: { limit?: number, offset?: number })
|
||||||
|
- updateAgent(id: string, updates: Partial<AgentEntity>)
|
||||||
|
- deleteAgent(id: string)
|
||||||
|
2. Create API routes:
|
||||||
|
- POST /v1/agents - Create agent
|
||||||
|
- GET /v1/agents - List all agents
|
||||||
|
- GET /v1/agents/:agentId - Get agent by ID
|
||||||
|
- PUT /v1/agents/:agentId - Update agent
|
||||||
|
- DELETE /v1/agents/:agentId - Delete agent
|
||||||
|
|
||||||
|
Phase 3: Session CRUD Operations
|
||||||
|
|
||||||
|
1. Implement service methods:
|
||||||
|
- createSession(session: Omit<AgentSessionEntity, 'id' | 'created_at' | 'updated_at'>)
|
||||||
|
- getSession(id: string)
|
||||||
|
- listSessions(agentId?: string, options?: { status?: SessionStatus, limit?: number,
|
||||||
|
offset?: number })
|
||||||
|
- updateSession(id: string, updates: Partial<AgentSessionEntity>)
|
||||||
|
- updateSessionStatus(id: string, status: SessionStatus)
|
||||||
|
- deleteSession(id: string)
|
||||||
|
- getSessionWithAgent(id: string) - Get session with merged agent configuration
|
||||||
|
2. Create API routes (RESTful nested resources):
|
||||||
|
- POST /v1/agents/:agentId/sessions - Create session for specific agent
|
||||||
|
- GET /v1/agents/:agentId/sessions - List sessions for specific agent
|
||||||
|
- GET /v1/agents/:agentId/sessions/:sessionId - Get specific session
|
||||||
|
- PUT /v1/agents/:agentId/sessions/:sessionId - Update session
|
||||||
|
- PATCH /v1/agents/:agentId/sessions/:sessionId/status - Update session status
|
||||||
|
- DELETE /v1/agents/:agentId/sessions/:sessionId - Delete session
|
||||||
|
|
||||||
|
Additional convenience endpoints:
|
||||||
|
- GET /v1/sessions - List all sessions (across all agents)
|
||||||
|
- GET /v1/sessions/:sessionId - Get session by ID (without agent context)
|
||||||
|
|
||||||
|
Phase 4: Session Logs CRUD Operations
|
||||||
|
|
||||||
|
1. Implement service methods:
|
||||||
|
- createSessionLog(log: Omit<SessionLogEntity, 'id' | 'created_at' | 'updated_at'>)
|
||||||
|
- getSessionLog(id: number)
|
||||||
|
- listSessionLogs(sessionId: string, options?: { limit?: number, offset?: number })
|
||||||
|
- updateSessionLog(id: number, updates: { content?: any, metadata?: any })
|
||||||
|
- deleteSessionLog(id: number)
|
||||||
|
- getSessionLogTree(sessionId: string) - Get logs with parent-child relationships
|
||||||
|
- bulkCreateSessionLogs(logs: Array<...>) - Batch insert logs
|
||||||
|
2. Create API routes (RESTful nested resources):
|
||||||
|
- POST /v1/agents/:agentId/sessions/:sessionId/logs - Create log entry
|
||||||
|
- GET /v1/agents/:agentId/sessions/:sessionId/logs - List logs for session
|
||||||
|
- GET /v1/agents/:agentId/sessions/:sessionId/logs/:logId - Get specific log
|
||||||
|
- PUT /v1/agents/:agentId/sessions/:sessionId/logs/:logId - Update log
|
||||||
|
- DELETE /v1/agents/:agentId/sessions/:sessionId/logs/:logId - Delete log
|
||||||
|
- POST /v1/agents/:agentId/sessions/:sessionId/logs/bulk - Bulk create logs
|
||||||
|
|
||||||
|
Additional convenience endpoints:
|
||||||
|
- GET /v1/sessions/:sessionId/logs - Get logs without agent context
|
||||||
|
- GET /v1/session-logs/:logId - Get specific log by ID
|
||||||
|
|
||||||
|
Phase 5: Route Organization
|
||||||
|
|
||||||
|
1. Mount routes with proper nesting:
|
||||||
|
// In app.ts
|
||||||
|
apiRouter.use('/agents', agentsRoutes)
|
||||||
|
// agentsRoutes will handle:
|
||||||
|
// - /agents/*
|
||||||
|
// - /agents/:agentId/sessions/*
|
||||||
|
// - /agents/:agentId/sessions/:sessionId/logs/*
|
||||||
|
|
||||||
|
// Convenience routes
|
||||||
|
apiRouter.use('/sessions', sessionsRoutes)
|
||||||
|
apiRouter.use('/session-logs', sessionLogsRoutes)
|
||||||
|
|
||||||
|
2. Use Express Router mergeParams for nested routes:
|
||||||
|
// In agents.ts
|
||||||
|
const sessionsRouter = express.Router({ mergeParams: true })
|
||||||
|
router.use('/:agentId/sessions', sessionsRouter)
|
||||||
|
|
||||||
|
Phase 6: OpenAPI Documentation
|
||||||
|
|
||||||
|
1. Add Swagger schemas for new entities:
|
||||||
|
- AgentEntity schema
|
||||||
|
- AgentSessionEntity schema
|
||||||
|
- SessionLogEntity schema
|
||||||
|
- Request/Response schemas
|
||||||
|
2. Document all new endpoints with:
|
||||||
|
- Clear path parameters (agentId, sessionId, logId)
|
||||||
|
- Request body schemas
|
||||||
|
- Response examples
|
||||||
|
- Error responses
|
||||||
|
- Proper grouping by resource
|
||||||
|
|
||||||
|
Phase 7: Validation & Error Handling
|
||||||
|
|
||||||
|
1. Add path parameter validation:
|
||||||
|
- Validate agentId exists before processing session requests
|
||||||
|
- Validate sessionId belongs to agentId
|
||||||
|
- Validate logId belongs to sessionId
|
||||||
|
2. Implement middleware for:
|
||||||
|
- Request validation using express-validator
|
||||||
|
- Resource existence checks
|
||||||
|
- Permission validation (future consideration)
|
||||||
|
- Transaction support for complex operations
|
||||||
|
|
||||||
|
Phase 8: Testing
|
||||||
|
|
||||||
|
1. Unit tests for service methods
|
||||||
|
2. Integration tests for API endpoints
|
||||||
|
3. Test nested resource validation
|
||||||
|
4. Test cascading deletes
|
||||||
|
5. Test transaction rollbacks
|
||||||
|
|
||||||
|
File Structure
|
||||||
|
|
||||||
|
src/
|
||||||
|
├── main/
|
||||||
|
│ └── services/
|
||||||
|
│ └── agents/
|
||||||
|
│ ├── index.ts (existing)
|
||||||
|
│ ├── db.ts (existing)
|
||||||
|
│ └── AgentService.ts (new)
|
||||||
|
├── main/
|
||||||
|
│ └── apiServer/
|
||||||
|
│ └── routes/
|
||||||
|
│ ├── agents.ts (new - includes nested routes)
|
||||||
|
│ ├── sessions.ts (new - convenience endpoints)
|
||||||
|
│ └── session-logs.ts (new - convenience endpoints)
|
||||||
|
└── renderer/
|
||||||
|
└── src/
|
||||||
|
└── types/
|
||||||
|
└── agent.ts (existing)
|
||||||
|
|
||||||
|
API Endpoint Summary
|
||||||
|
|
||||||
|
Agent Endpoints
|
||||||
|
|
||||||
|
- POST /v1/agents
|
||||||
|
- GET /v1/agents
|
||||||
|
- GET /v1/agents/:agentId
|
||||||
|
- PUT /v1/agents/:agentId
|
||||||
|
- DELETE /v1/agents/:agentId
|
||||||
|
|
||||||
|
Session Endpoints (RESTful)
|
||||||
|
|
||||||
|
- POST /v1/agents/:agentId/sessions
|
||||||
|
- GET /v1/agents/:agentId/sessions
|
||||||
|
- GET /v1/agents/:agentId/sessions/:sessionId
|
||||||
|
- PUT /v1/agents/:agentId/sessions/:sessionId
|
||||||
|
- PATCH /v1/agents/:agentId/sessions/:sessionId/status
|
||||||
|
- DELETE /v1/agents/:agentId/sessions/:sessionId
|
||||||
|
|
||||||
|
Session Convenience Endpoints
|
||||||
|
|
||||||
|
- GET /v1/sessions
|
||||||
|
- GET /v1/sessions/:sessionId
|
||||||
|
|
||||||
|
Session Log Endpoints (RESTful)
|
||||||
|
|
||||||
|
- POST /v1/agents/:agentId/sessions/:sessionId/logs
|
||||||
|
- GET /v1/agents/:agentId/sessions/:sessionId/logs
|
||||||
|
- GET /v1/agents/:agentId/sessions/:sessionId/logs/:logId
|
||||||
|
- PUT /v1/agents/:agentId/sessions/:sessionId/logs/:logId
|
||||||
|
- DELETE /v1/agents/:agentId/sessions/:sessionId/logs/:logId
|
||||||
|
- POST /v1/agents/:agentId/sessions/:sessionId/logs/bulk
|
||||||
|
|
||||||
|
Session Log Convenience Endpoints
|
||||||
|
|
||||||
|
- GET /v1/sessions/:sessionId/logs
|
||||||
|
- GET /v1/session-logs/:logId
|
||||||
|
|
||||||
|
Key Considerations
|
||||||
|
|
||||||
|
- Follow RESTful URL conventions with proper resource nesting
|
||||||
|
- Validate parent-child relationships in nested routes
|
||||||
|
- Use Express Router with mergeParams for nested routing
|
||||||
|
- Implement proper cascading deletes
|
||||||
|
- Add transaction support for data consistency
|
||||||
|
- Follow existing patterns from MemoryService
|
||||||
|
- Ensure backward compatibility
|
||||||
|
- Add rate limiting for write operations
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
|
||||||
|
- @libsql/client - SQLite database client
|
||||||
|
- express-validator - Request validation
|
||||||
|
- swagger-jsdoc - API documentation
|
||||||
|
- Existing types from @types/agent.ts
|
||||||
@ -1,6 +1,13 @@
|
|||||||
import { Client, createClient } from '@libsql/client'
|
import { Client, createClient } from '@libsql/client'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import type { AgentEntity, AgentSessionEntity, PermissionMode, SessionLogEntity, SessionStatus } from '@types'
|
import type {
|
||||||
|
AgentEntity,
|
||||||
|
AgentSessionEntity,
|
||||||
|
AgentType,
|
||||||
|
PermissionMode,
|
||||||
|
SessionLogEntity,
|
||||||
|
SessionStatus
|
||||||
|
} from '@types'
|
||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
@ -9,6 +16,7 @@ import { AgentQueries } from './db'
|
|||||||
const logger = loggerService.withContext('AgentService')
|
const logger = loggerService.withContext('AgentService')
|
||||||
|
|
||||||
export interface CreateAgentRequest {
|
export interface CreateAgentRequest {
|
||||||
|
type: AgentType
|
||||||
name: string
|
name: string
|
||||||
description?: string
|
description?: string
|
||||||
avatar?: string
|
avatar?: string
|
||||||
@ -201,6 +209,7 @@ export class AgentService {
|
|||||||
|
|
||||||
const values = [
|
const values = [
|
||||||
id,
|
id,
|
||||||
|
serializedData.type,
|
||||||
serializedData.name,
|
serializedData.name,
|
||||||
serializedData.description || null,
|
serializedData.description || null,
|
||||||
serializedData.avatar || null,
|
serializedData.avatar || null,
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export const AgentQueries = {
|
|||||||
agents: `
|
agents: `
|
||||||
CREATE TABLE IF NOT EXISTS agents (
|
CREATE TABLE IF NOT EXISTS agents (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
|
type TEXT NOT NULL DEFAULT 'custom', -- 'claudeCode', 'codex', 'custom'
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
avatar TEXT,
|
avatar TEXT,
|
||||||
@ -72,6 +73,7 @@ export const AgentQueries = {
|
|||||||
// Index creation queries
|
// Index creation queries
|
||||||
createIndexes: {
|
createIndexes: {
|
||||||
agentsName: 'CREATE INDEX IF NOT EXISTS idx_agents_name ON agents(name)',
|
agentsName: 'CREATE INDEX IF NOT EXISTS idx_agents_name ON agents(name)',
|
||||||
|
agentsType: 'CREATE INDEX IF NOT EXISTS idx_agents_type ON agents(type)',
|
||||||
agentsModel: 'CREATE INDEX IF NOT EXISTS idx_agents_model ON agents(model)',
|
agentsModel: 'CREATE INDEX IF NOT EXISTS idx_agents_model ON agents(model)',
|
||||||
agentsPlanModel: 'CREATE INDEX IF NOT EXISTS idx_agents_plan_model ON agents(plan_model)',
|
agentsPlanModel: 'CREATE INDEX IF NOT EXISTS idx_agents_plan_model ON agents(plan_model)',
|
||||||
agentsSmallModel: 'CREATE INDEX IF NOT EXISTS idx_agents_small_model ON agents(small_model)',
|
agentsSmallModel: 'CREATE INDEX IF NOT EXISTS idx_agents_small_model ON agents(small_model)',
|
||||||
@ -99,8 +101,8 @@ export const AgentQueries = {
|
|||||||
// Agent operations
|
// Agent operations
|
||||||
agents: {
|
agents: {
|
||||||
insert: `
|
insert: `
|
||||||
INSERT INTO agents (id, name, description, avatar, instructions, model, plan_model, small_model, built_in_tools, mcps, knowledges, configuration, accessible_paths, permission_mode, max_steps, created_at, updated_at)
|
INSERT INTO agents (id, type, name, description, avatar, instructions, model, plan_model, small_model, built_in_tools, mcps, knowledges, configuration, accessible_paths, permission_mode, max_steps, created_at, updated_at)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`,
|
`,
|
||||||
|
|
||||||
update: `
|
update: `
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
export type SessionStatus = 'idle' | 'running' | 'completed' | 'failed' | 'stopped'
|
export type SessionStatus = 'idle' | 'running' | 'completed' | 'failed' | 'stopped'
|
||||||
export type PermissionMode = 'readOnly' | 'acceptEdits' | 'bypassPermissions'
|
export type PermissionMode = 'readOnly' | 'acceptEdits' | 'bypassPermissions'
|
||||||
export type SessionLogRole = 'user' | 'agent' | 'system' | 'tool'
|
export type SessionLogRole = 'user' | 'agent' | 'system' | 'tool'
|
||||||
|
export type AgentType = 'claude-code' | 'codex' | 'qwen-cli' | 'gemini-cli' | 'custom'
|
||||||
|
|
||||||
export type SessionLogType =
|
export type SessionLogType =
|
||||||
| 'message' // User or agent message
|
| 'message' // User or agent message
|
||||||
@ -38,6 +39,7 @@ export interface AgentConfiguration {
|
|||||||
// Agent entity representing an autonomous agent configuration
|
// Agent entity representing an autonomous agent configuration
|
||||||
export interface AgentEntity extends AgentConfiguration {
|
export interface AgentEntity extends AgentConfiguration {
|
||||||
id: string
|
id: string
|
||||||
|
type: AgentType
|
||||||
name: string
|
name: string
|
||||||
description?: string
|
description?: string
|
||||||
avatar?: string
|
avatar?: string
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user