cherry-studio/packages/shared/data/api/schemas/messages.ts
fullex 7cac5b55f6 feat: enhance cursor-based pagination in API documentation and types
- Added detailed explanations and examples for cursor semantics in `api-types.md`, clarifying the exclusive nature of cursors in pagination.
- Updated `CursorPaginationParams` interface to emphasize the cursor's role as an exclusive boundary in responses.
- Refactored `BranchMessagesQueryParams` to extend `CursorPaginationParams`, aligning with the new pagination logic.
- Modified `MessageService` to implement cursor-based pagination semantics, ensuring accurate message retrieval and response structure.
- Enhanced documentation throughout to provide clearer guidance on pagination behavior and usage patterns.
2026-01-04 22:31:15 +08:00

214 lines
6.3 KiB
TypeScript

/**
* Message API Schema definitions
*
* Contains all message-related endpoints for tree operations and message management.
* Includes endpoints for tree visualization and conversation view.
*/
import type { CursorPaginationParams } from '@shared/data/api/apiTypes'
import type {
BranchMessagesResponse,
Message,
MessageData,
MessageRole,
MessageStats,
MessageStatus,
TreeResponse
} from '@shared/data/types/message'
import type { AssistantMeta, ModelMeta } from '@shared/data/types/meta'
// ============================================================================
// DTOs
// ============================================================================
/**
* DTO for creating a new message
*/
export interface CreateMessageDto {
/**
* Parent message ID for positioning this message in the conversation tree.
*
* Behavior:
* - `undefined` (omitted): Auto-resolve parent based on topic state:
* - If topic has no messages: create as root (parentId = null)
* - If topic has messages and activeNodeId is set: attach to activeNodeId
* - If topic has messages but no activeNodeId: throw INVALID_OPERATION error
* - `null` (explicit): Create as root message. Throws INVALID_OPERATION if
* topic already has a root message (only one root allowed per topic).
* - `string` (message ID): Attach to specified parent. Throws NOT_FOUND if
* parent doesn't exist, or INVALID_OPERATION if parent belongs to different topic.
*/
parentId?: string | null
/** Message role */
role: MessageRole
/** Message content */
data: MessageData
/** Message status */
status?: MessageStatus
/** Siblings group ID (0 = normal, >0 = multi-model group) */
siblingsGroupId?: number
/** Assistant ID */
assistantId?: string
/** Preserved assistant info */
assistantMeta?: AssistantMeta
/** Model identifier */
modelId?: string
/** Preserved model info */
modelMeta?: ModelMeta
/** Trace ID */
traceId?: string
/** Statistics */
stats?: MessageStats
/** Set this message as the active node in the topic (default: true) */
setAsActive?: boolean
}
/**
* DTO for updating an existing message
*/
export interface UpdateMessageDto {
/** Updated message content */
data?: MessageData
/** Move message to new parent */
parentId?: string | null
/** Change siblings group */
siblingsGroupId?: number
/** Update status */
status?: MessageStatus
/** Update trace ID */
traceId?: string | null
/** Update statistics */
stats?: MessageStats | null
}
/**
* Strategy for updating activeNodeId when the active message is deleted
*/
export type ActiveNodeStrategy = 'parent' | 'clear'
/**
* Response for delete operation
*/
export interface DeleteMessageResponse {
/** IDs of deleted messages */
deletedIds: string[]
/** IDs of reparented children (only when cascade=false) */
reparentedIds?: string[]
/** New activeNodeId for the topic (only if activeNodeId was affected by deletion) */
newActiveNodeId?: string | null
}
// ============================================================================
// Query Parameters
// ============================================================================
/**
* Query parameters for GET /topics/:id/tree
*/
export interface TreeQueryParams {
/** Root node ID (defaults to tree root) */
rootId?: string
/** End node ID (defaults to topic.activeNodeId) */
nodeId?: string
/** Depth to expand beyond active path (-1 = all, 0 = path only, 1+ = layers) */
depth?: number
}
/**
* Query parameters for GET /topics/:id/messages
*
* Uses "before cursor" semantics for loading historical messages:
* - First request (no cursor): Returns the most recent `limit` messages
* - Subsequent requests: Pass `nextCursor` from previous response as `cursor`
* to load older messages towards root
* - The cursor message itself is NOT included in the response
*/
export interface BranchMessagesQueryParams extends CursorPaginationParams {
/** End node ID (defaults to topic.activeNodeId) */
nodeId?: string
/** Whether to include siblingsGroup in response */
includeSiblings?: boolean
}
// ============================================================================
// API Schema Definitions
// ============================================================================
/**
* Message API Schema definitions
*
* Organized by domain responsibility:
* - /topics/:id/tree - Tree visualization
* - /topics/:id/messages - Branch messages for conversation
* - /messages/:id - Individual message operations
*/
export interface MessageSchemas {
/**
* Tree query endpoint for visualization
* @example GET /topics/abc123/tree?depth=1
*/
'/topics/:topicId/tree': {
/** Get tree structure for visualization */
GET: {
params: { topicId: string }
query?: TreeQueryParams
response: TreeResponse
}
}
/**
* Branch messages endpoint for conversation view
* @example GET /topics/abc123/messages?limit=20
* @example POST /topics/abc123/messages { "parentId": "msg1", "role": "user", "data": {...} }
*/
'/topics/:topicId/messages': {
/** Get messages along active branch with pagination */
GET: {
params: { topicId: string }
query?: BranchMessagesQueryParams
response: BranchMessagesResponse
}
/** Create a new message in the topic */
POST: {
params: { topicId: string }
body: CreateMessageDto
response: Message
}
}
/**
* Individual message endpoint
* @example GET /messages/msg123
* @example PATCH /messages/msg123 { "data": {...} }
* @example DELETE /messages/msg123?cascade=true
*/
'/messages/:id': {
/** Get a single message by ID */
GET: {
params: { id: string }
response: Message
}
/** Update a message (content, move to new parent, etc.) */
PATCH: {
params: { id: string }
body: UpdateMessageDto
response: Message
}
/**
* Delete a message
* - cascade=true: deletes message and all descendants
* - cascade=false: reparents children to grandparent
* - activeNodeStrategy='parent' (default): sets activeNodeId to parent if affected
* - activeNodeStrategy='clear': sets activeNodeId to null if affected
*/
DELETE: {
params: { id: string }
query?: {
cascade?: boolean
activeNodeStrategy?: ActiveNodeStrategy
}
response: DeleteMessageResponse
}
}
}