syntax = "proto3"; package chatto.api.v1; import "buf/validate/validate.proto"; import "chatto/api/v1/message_types.proto"; import "chatto/api/v1/room_timeline.proto"; import "chatto/api/v1/rooms.proto"; import "chatto/api/v1/threads.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; // Ordering for message or grouped-thread search results. enum MessageSearchOrder { // Use relevance order. MESSAGE_SEARCH_ORDER_UNSPECIFIED = 0; // Highest message score first; grouped results use their best matching message. MESSAGE_SEARCH_ORDER_RELEVANCE = 1; // Newest matching message first, also when results are grouped. MESSAGE_SEARCH_ORDER_NEWEST = 2; // Return threads with the most recent activity first. Requires thread grouping. MESSAGE_SEARCH_ORDER_THREAD_ACTIVITY = 3; // Newest root-message creation first, with unknown dates last. Requires thread // grouping. Older servers can reject this order. MESSAGE_SEARCH_ORDER_THREAD_CREATED = 4; } // Candidate scope, intersected with the caller's current access and query filters. enum MessageSearchScope { // Search all accessible messages. This is the existing default. MESSAGE_SEARCH_SCOPE_UNSPECIFIED = 0; // Search roots and replies in followed threads, including direct messages. MESSAGE_SEARCH_SCOPE_FOLLOWED_THREADS = 1; } // Determines the result type and pagination unit. enum MessageSearchGroupBy { // Return individual matching messages. MESSAGE_SEARCH_GROUP_BY_UNSPECIFIED = 0; // Return distinct thread rows. A root without replies is also a thread. MESSAGE_SEARCH_GROUP_BY_THREAD = 1; } // Current availability of message search on this server. enum MessageSearchState { // The server did not report a meaningful state. MESSAGE_SEARCH_STATE_UNSPECIFIED = 0; // Search is disabled by the server operator. MESSAGE_SEARCH_STATE_DISABLED = 1; // Search is enabled but its provider has not established index state yet. MESSAGE_SEARCH_STATE_STARTING = 2; // The provider is rebuilding or catching up and cannot answer queries yet. MESSAGE_SEARCH_STATE_INDEXING = 3; // Search is ready to answer queries. MESSAGE_SEARCH_STATE_READY = 4; // Search can answer queries but has a known non-fatal limitation. MESSAGE_SEARCH_STATE_DEGRADED = 5; // Search is enabled but no provider can currently answer. MESSAGE_SEARCH_STATE_UNAVAILABLE = 6; } // Request to search current message bodies visible to the authenticated user. message SearchMessagesRequest { // Required query expression. Words are required terms, quoted text is an // exact phrase, and `AND` may separate terms. The query also accepts `in:`, // `from:`, `before:`, `after:`, and `has:attachment` filters; a recognized // filter may be used without a word or phrase. string query = 1 [(buf.validate.field).string = { min_len: 1 max_bytes: 1024 }]; // Optional room-ID scope. The server intersects this room with rooms the // current user may read and with any `in:` filters in query. optional string room_id = 2 [(buf.validate.field).string = { min_len: 1 max_bytes: 128 }]; // Optional author-ID scope. The server intersects this author with any // `from:` filters in query. optional string author_id = 3 [(buf.validate.field).string = { min_len: 1 max_bytes: 128 }]; // Exclusive lower bound on message creation time. The stricter bound wins // when query also contains `after:`. google.protobuf.Timestamp created_after = 4; // Exclusive upper bound on message creation time. The stricter bound wins // when query also contains `before:`. google.protobuf.Timestamp created_before = 5; // When true, return only messages that currently have attachments. bool has_attachments = 6; // Defaults to relevance in both result modes. Thread activity order requires // thread grouping; newest order uses matching-message creation time. MessageSearchOrder order = 7 [(buf.validate.field).enum.defined_only = true]; // Maximum results to return (messages or distinct threads). Zero uses 50; the // maximum is 100. Stale or no-longer-visible provider hits are omitted, so a // page may contain fewer messages than requested even when next_cursor is // present. uint32 page_size = 8 [(buf.validate.field).uint32.lte = 100]; // Opaque cursor returned by the preceding response. It is bound to the // authenticated user and every other query field. string cursor = 9 [(buf.validate.field).string.max_bytes = 8192]; // Candidate scope. Followed threads are selected before result pagination. MessageSearchScope scope = 10 [(buf.validate.field).enum.defined_only = true]; // Result grouping. The server deduplicates threads before pagination. MessageSearchGroupBy group_by = 11 [(buf.validate.field).enum.defined_only = true]; // Select root authors within FOLLOWED_THREADS before search and pagination. // Other scopes require UNSPECIFIED. This does not restrict reply authors; // author_id and from: still select the authors of matching messages. ThreadRootAuthorFilter root_author_filter = 12 [(buf.validate.field).enum.defined_only = true]; } // One current, authorized match. With thread grouping, each result represents // one thread and message is its best match in the selected order. message MessageSearchResult { // Current renderable message that matched the query. Message message = 1; // Search-provider relevance score. Higher values are more relevant for the // same query. Clients may merge results from servers using compatible // search implementations by this value. double relevance_score = 2; // Present only with THREAD grouping. The matching message can be a reply; // the root and latest reply in this context need not match the query. ThreadSearchContext thread_context = 3; } // One ordered page of current, authorized message or thread results. Pagination reads a live // search index rather than a pinned snapshot, so results may move, repeat, or // disappear between page requests while the index advances. message SearchMessagesResponse { // Ordered matches. With THREAD grouping, one result per matching thread. // Otherwise each result is one message, without thread_context. repeated MessageSearchResult results = 1; // Opaque cursor for the next page in the selected result mode. // Empty means no more matches. Live activity can reorder thread pages. string next_cursor = 2; // Total distinct matching threads. Present only when group_by is THREAD. optional uint64 thread_total_count = 4; // Related entities for grouped results, avoiding per-row reads. RoomTimelineIncludes includes = 5; } // Current context for a grouped match. The viewer need not follow this thread. // Search evidence stays on the containing MessageSearchResult. message ThreadSearchContext { // Renderable root message, when still visible. Message root_message = 1; // Room containing this thread. RoomSummary room = 2; // Current thread metadata and viewer follow/read state. ThreadSummary thread = 3; // Most recent visible reply, when present; it need not match the query. Message latest_reply = 4; // Conversation-label participants for direct messages; empty for channels. repeated string direct_message_participant_user_ids = 5; } // Request for current message-search availability. message GetStatusRequest {} // Current message-search availability. message GetStatusResponse { reserved 2, 3; reserved "indexed_event_count", "target_event_count"; // Current feature/provider state. MessageSearchState state = 1; // Suggested delay before checking again while search is not ready. google.protobuf.Duration retry_after = 4; } // Searches current message content visible to an authenticated user. service MessageSearchService { // Reports whether the operator enabled Search and whether its provider can // currently answer. This method remains available when Search is disabled. rpc GetStatus(GetStatusRequest) returns (GetStatusResponse); // Searches current message bodies in rooms where the caller is a member. // Scope and grouping are independent, including for direct messages. THREAD // grouping returns one row per matching root and its replies. Scope, grouping, // and ordering are part of the cursor scope. Unsupported combinations return // INVALID_ARGUMENT. Existing requests return individual messages by default. // Results also require message.read or a matching thread relationship with // message.read-interactions. Results are authorized and hydrated from current Chatto state // after provider matching. Returns FAILED_PRECONDITION when Search is // disabled and UNAVAILABLE when its provider is not ready. rpc SearchMessages(SearchMessagesRequest) returns (SearchMessagesResponse); }