mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-03-01 17:41:03 +08:00
373 lines
14 KiB
Python
373 lines
14 KiB
Python
"""Pydantic models describing tool inputs and outputs."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any, Optional
|
|
|
|
from pydantic import BaseModel, Field, ConfigDict, field_validator
|
|
|
|
|
|
class SearchResultItem(BaseModel):
|
|
"""A single search result entry."""
|
|
|
|
type: str = Field(description="Entity type for the result (post, user, tag, etc.).")
|
|
id: Optional[int] = Field(default=None, description="Identifier of the matched entity.")
|
|
text: Optional[str] = Field(default=None, description="Primary text associated with the result.")
|
|
sub_text: Optional[str] = Field(
|
|
default=None,
|
|
alias="subText",
|
|
description="Secondary text, e.g. a username or excerpt.",
|
|
)
|
|
extra: Optional[str] = Field(default=None, description="Additional contextual information.")
|
|
post_id: Optional[int] = Field(
|
|
default=None,
|
|
alias="postId",
|
|
description="Associated post identifier when relevant.",
|
|
)
|
|
highlighted_text: Optional[str] = Field(
|
|
default=None,
|
|
alias="highlightedText",
|
|
description="Highlighted snippet of the primary text if available.",
|
|
)
|
|
highlighted_sub_text: Optional[str] = Field(
|
|
default=None,
|
|
alias="highlightedSubText",
|
|
description="Highlighted snippet of the secondary text if available.",
|
|
)
|
|
highlighted_extra: Optional[str] = Field(
|
|
default=None,
|
|
alias="highlightedExtra",
|
|
description="Highlighted snippet of extra information if available.",
|
|
)
|
|
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class SearchResponse(BaseModel):
|
|
"""Structured response returned by the search tool."""
|
|
|
|
keyword: str = Field(description="The keyword that was searched.")
|
|
total: int = Field(description="Total number of matches returned by the backend.")
|
|
results: list[SearchResultItem] = Field(
|
|
default_factory=list,
|
|
description="Ordered collection of search results.",
|
|
)
|
|
|
|
|
|
class AuthorInfo(BaseModel):
|
|
"""Summary of a post or comment author."""
|
|
|
|
id: Optional[int] = Field(default=None, description="Author identifier.")
|
|
username: Optional[str] = Field(default=None, description="Author username.")
|
|
avatar: Optional[str] = Field(default=None, description="URL of the author's avatar.")
|
|
display_medal: Optional[str] = Field(
|
|
default=None,
|
|
alias="displayMedal",
|
|
description="Medal displayed next to the author, when available.",
|
|
)
|
|
|
|
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
|
|
|
|
class CategoryInfo(BaseModel):
|
|
"""Basic information about a post category."""
|
|
|
|
id: Optional[int] = Field(default=None, description="Category identifier.")
|
|
name: Optional[str] = Field(default=None, description="Category name.")
|
|
description: Optional[str] = Field(
|
|
default=None, description="Human friendly description of the category."
|
|
)
|
|
icon: Optional[str] = Field(default=None, description="Icon URL associated with the category.")
|
|
small_icon: Optional[str] = Field(
|
|
default=None,
|
|
alias="smallIcon",
|
|
description="Compact icon URL for the category.",
|
|
)
|
|
count: Optional[int] = Field(default=None, description="Number of posts within the category.")
|
|
|
|
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
|
|
|
|
class TagInfo(BaseModel):
|
|
"""Details for a tag assigned to a post."""
|
|
|
|
id: Optional[int] = Field(default=None, description="Tag identifier.")
|
|
name: Optional[str] = Field(default=None, description="Tag name.")
|
|
description: Optional[str] = Field(default=None, description="Description of the tag.")
|
|
icon: Optional[str] = Field(default=None, description="Icon URL for the tag.")
|
|
small_icon: Optional[str] = Field(
|
|
default=None,
|
|
alias="smallIcon",
|
|
description="Compact icon URL for the tag.",
|
|
)
|
|
created_at: Optional[datetime] = Field(
|
|
default=None,
|
|
alias="createdAt",
|
|
description="When the tag was created.",
|
|
)
|
|
count: Optional[int] = Field(default=None, description="Number of posts using the tag.")
|
|
|
|
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
|
|
|
|
class ReactionInfo(BaseModel):
|
|
"""Representation of a reaction on a post or comment."""
|
|
|
|
id: Optional[int] = Field(default=None, description="Reaction identifier.")
|
|
type: Optional[str] = Field(default=None, description="Reaction type (emoji, like, etc.).")
|
|
user: Optional[str] = Field(default=None, description="Username of the reacting user.")
|
|
post_id: Optional[int] = Field(
|
|
default=None,
|
|
alias="postId",
|
|
description="Related post identifier when applicable.",
|
|
)
|
|
comment_id: Optional[int] = Field(
|
|
default=None,
|
|
alias="commentId",
|
|
description="Related comment identifier when applicable.",
|
|
)
|
|
message_id: Optional[int] = Field(
|
|
default=None,
|
|
alias="messageId",
|
|
description="Related message identifier when applicable.",
|
|
)
|
|
reward: Optional[int] = Field(default=None, description="Reward granted for the reaction, if any.")
|
|
|
|
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
|
|
|
|
class CommentData(BaseModel):
|
|
"""Comment information returned by the backend."""
|
|
|
|
id: Optional[int] = Field(default=None, description="Comment identifier.")
|
|
content: Optional[str] = Field(default=None, description="Markdown content of the comment.")
|
|
created_at: Optional[datetime] = Field(
|
|
default=None,
|
|
alias="createdAt",
|
|
description="Timestamp when the comment was created.",
|
|
)
|
|
pinned_at: Optional[datetime] = Field(
|
|
default=None,
|
|
alias="pinnedAt",
|
|
description="Timestamp when the comment was pinned, if applicable.",
|
|
)
|
|
author: Optional[AuthorInfo] = Field(default=None, description="Author of the comment.")
|
|
replies: list["CommentData"] = Field(
|
|
default_factory=list,
|
|
description="Nested replies associated with the comment.",
|
|
)
|
|
reactions: list[ReactionInfo] = Field(
|
|
default_factory=list,
|
|
description="Reactions applied to the comment.",
|
|
)
|
|
reward: Optional[int] = Field(default=None, description="Reward gained by posting the comment.")
|
|
point_reward: Optional[int] = Field(
|
|
default=None,
|
|
alias="pointReward",
|
|
description="Points rewarded for the comment.",
|
|
)
|
|
|
|
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
|
|
@field_validator("replies", "reactions", mode="before")
|
|
@classmethod
|
|
def _ensure_comment_lists(cls, value: Any) -> list[Any]:
|
|
"""Convert ``None`` payloads to empty lists for comment collections."""
|
|
|
|
if value is None:
|
|
return []
|
|
return value
|
|
|
|
|
|
class CommentReplyResult(BaseModel):
|
|
"""Structured response returned when replying to a comment."""
|
|
|
|
comment: CommentData = Field(description="Reply comment returned by the backend.")
|
|
|
|
|
|
class CommentCreateResult(BaseModel):
|
|
"""Structured response returned when creating a comment on a post."""
|
|
|
|
comment: CommentData = Field(description="Comment returned by the backend.")
|
|
|
|
|
|
class PostSummary(BaseModel):
|
|
"""Summary information for a post."""
|
|
|
|
id: Optional[int] = Field(default=None, description="Post identifier.")
|
|
title: Optional[str] = Field(default=None, description="Title of the post.")
|
|
content: Optional[str] = Field(default=None, description="Excerpt or content of the post.")
|
|
created_at: Optional[datetime] = Field(
|
|
default=None,
|
|
alias="createdAt",
|
|
description="When the post was created.",
|
|
)
|
|
author: Optional[AuthorInfo] = Field(default=None, description="Author who created the post.")
|
|
category: Optional[CategoryInfo] = Field(default=None, description="Category of the post.")
|
|
tags: list[TagInfo] = Field(default_factory=list, description="Tags assigned to the post.")
|
|
views: Optional[int] = Field(default=None, description="Total view count for the post.")
|
|
comment_count: Optional[int] = Field(
|
|
default=None,
|
|
alias="commentCount",
|
|
description="Number of comments on the post.",
|
|
)
|
|
status: Optional[str] = Field(default=None, description="Workflow status of the post.")
|
|
pinned_at: Optional[datetime] = Field(
|
|
default=None,
|
|
alias="pinnedAt",
|
|
description="When the post was pinned, if ever.",
|
|
)
|
|
last_reply_at: Optional[datetime] = Field(
|
|
default=None,
|
|
alias="lastReplyAt",
|
|
description="Timestamp of the most recent reply.",
|
|
)
|
|
reactions: list[ReactionInfo] = Field(
|
|
default_factory=list,
|
|
description="Reactions received by the post.",
|
|
)
|
|
participants: list[AuthorInfo] = Field(
|
|
default_factory=list,
|
|
description="Users participating in the discussion.",
|
|
)
|
|
subscribed: Optional[bool] = Field(
|
|
default=None,
|
|
description="Whether the current user is subscribed to the post.",
|
|
)
|
|
reward: Optional[int] = Field(default=None, description="Reward granted for the post.")
|
|
point_reward: Optional[int] = Field(
|
|
default=None,
|
|
alias="pointReward",
|
|
description="Points granted for the post.",
|
|
)
|
|
type: Optional[str] = Field(default=None, description="Type of the post.")
|
|
lottery: Optional[dict[str, Any]] = Field(
|
|
default=None, description="Lottery information for the post."
|
|
)
|
|
poll: Optional[dict[str, Any]] = Field(
|
|
default=None, description="Poll information for the post."
|
|
)
|
|
rss_excluded: Optional[bool] = Field(
|
|
default=None,
|
|
alias="rssExcluded",
|
|
description="Whether the post is excluded from RSS feeds.",
|
|
)
|
|
closed: Optional[bool] = Field(default=None, description="Whether the post is closed for replies.")
|
|
visible_scope: Optional[str] = Field(
|
|
default=None,
|
|
alias="visibleScope",
|
|
description="Visibility scope configuration for the post.",
|
|
)
|
|
|
|
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
|
|
@field_validator("tags", "reactions", "participants", mode="before")
|
|
@classmethod
|
|
def _ensure_post_lists(cls, value: Any) -> list[Any]:
|
|
"""Normalize ``None`` values returned by the backend to empty lists."""
|
|
|
|
if value is None:
|
|
return []
|
|
return value
|
|
|
|
|
|
class RecentPostsResponse(BaseModel):
|
|
"""Structured response for the recent posts tool."""
|
|
|
|
minutes: int = Field(description="Time window, in minutes, used for the query.")
|
|
total: int = Field(description="Number of posts returned by the backend.")
|
|
posts: list[PostSummary] = Field(
|
|
default_factory=list,
|
|
description="Posts created within the requested time window.",
|
|
)
|
|
|
|
|
|
CommentData.model_rebuild()
|
|
|
|
|
|
class PostDetail(PostSummary):
|
|
"""Detailed information for a single post, including comments."""
|
|
|
|
comments: list[CommentData] = Field(
|
|
default_factory=list,
|
|
description="Comments that belong to the post.",
|
|
)
|
|
|
|
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
|
|
@field_validator("comments", mode="before")
|
|
@classmethod
|
|
def _ensure_comments_list(cls, value: Any) -> list[Any]:
|
|
"""Treat ``None`` comments payloads as empty lists."""
|
|
|
|
if value is None:
|
|
return []
|
|
return value
|
|
|
|
|
|
class NotificationData(BaseModel):
|
|
"""Unread notification payload returned by the backend."""
|
|
|
|
id: Optional[int] = Field(default=None, description="Notification identifier.")
|
|
type: Optional[str] = Field(default=None, description="Type of the notification.")
|
|
post: Optional[PostSummary] = Field(
|
|
default=None, description="Post associated with the notification if applicable."
|
|
)
|
|
comment: Optional[CommentData] = Field(
|
|
default=None, description="Comment referenced by the notification when available."
|
|
)
|
|
parent_comment: Optional[CommentData] = Field(
|
|
default=None,
|
|
alias="parentComment",
|
|
description="Parent comment for nested replies, when present.",
|
|
)
|
|
from_user: Optional[AuthorInfo] = Field(
|
|
default=None,
|
|
alias="fromUser",
|
|
description="User who triggered the notification.",
|
|
)
|
|
reaction_type: Optional[str] = Field(
|
|
default=None,
|
|
alias="reactionType",
|
|
description="Reaction type for reaction-based notifications.",
|
|
)
|
|
content: Optional[str] = Field(
|
|
default=None, description="Additional content or message for the notification."
|
|
)
|
|
approved: Optional[bool] = Field(
|
|
default=None, description="Approval status for moderation notifications."
|
|
)
|
|
read: Optional[bool] = Field(default=None, description="Whether the notification is read.")
|
|
created_at: Optional[datetime] = Field(
|
|
default=None,
|
|
alias="createdAt",
|
|
description="Timestamp when the notification was created.",
|
|
)
|
|
|
|
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
|
|
|
|
class UnreadNotificationsResponse(BaseModel):
|
|
"""Structured response for unread notification queries."""
|
|
|
|
page: int = Field(description="Requested page index for the unread notifications.")
|
|
size: int = Field(description="Requested page size for the unread notifications.")
|
|
total: int = Field(description="Number of unread notifications returned in this page.")
|
|
notifications: list[NotificationData] = Field(
|
|
default_factory=list,
|
|
description="Unread notifications returned by the backend.",
|
|
)
|
|
|
|
|
|
class NotificationCleanupResult(BaseModel):
|
|
"""Structured response returned after marking notifications as read."""
|
|
|
|
processed_ids: list[int] = Field(
|
|
default_factory=list,
|
|
description="Identifiers that were marked as read in the backend.",
|
|
)
|
|
total_marked: int = Field(
|
|
description="Total number of notifications successfully marked as read.",
|
|
)
|