From a50cd3a886fb9d9406e16537d90a6b07b530913a Mon Sep 17 00:00:00 2001 From: Kaien Date: Mon, 10 Aug 2026 13:48:58 +0300 Subject: [PATCH] feat(api): add participant registry RPCs for Stage 7.1 Expose Get/Batch/List/GetMy participant reads and authenticated agent RegisterSelfProfile, SyncMyTopicMemberships, and avatar self-management. Co-authored-by: Cursor --- chat.pb.go | 902 +++++++++++++++++++++++++++++++++++++++++++++--- chat.proto | 123 +++++++ chat_grpc.pb.go | 368 +++++++++++++++++++- 3 files changed, 1329 insertions(+), 64 deletions(-) diff --git a/chat.pb.go b/chat.pb.go index 66c42b3..079d434 100644 --- a/chat.pb.go +++ b/chat.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.8 -// protoc (unknown) +// protoc v7.35.1 // source: chat.proto package l_ai_chat_proto @@ -1229,6 +1229,698 @@ func (x *AcknowledgeResponse) GetLastAckSequence() int64 { return 0 } +// Participant is the public presentation of a chat identity. +// Internal platform IDs (telegram_id, discord_id, email, OAuth subject, +// global_user_id) are NEVER exposed here. +type Participant struct { + state protoimpl.MessageState `protogen:"open.v1"` + AuthorId string `protobuf:"bytes,1,opt,name=author_id,json=authorId,proto3" json:"author_id,omitempty"` // canonical UUID + AuthorType AuthorType `protobuf:"varint,2,opt,name=author_type,json=authorType,proto3,enum=chat.AuthorType" json:"author_type,omitempty"` + DisplayName string `protobuf:"bytes,3,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` // may be empty for stubs + // Locale key → localized alias (e.g. "ru" → "Мира"). Not unique. + Aliases map[string]string `protobuf:"bytes,4,rep,name=aliases,proto3" json:"aliases,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Role string `protobuf:"bytes,5,opt,name=role,proto3" json:"role,omitempty"` // e.g. "Coordinator"; may be empty + Bio string `protobuf:"bytes,6,opt,name=bio,proto3" json:"bio,omitempty"` + StatusText string `protobuf:"bytes,7,opt,name=status_text,json=statusText,proto3" json:"status_text,omitempty"` // participant-owned; distinct from presence + AvatarHash string `protobuf:"bytes,8,opt,name=avatar_hash,json=avatarHash,proto3" json:"avatar_hash,omitempty"` // empty when no avatar; used for ETag / skip-upload + Online bool `protobuf:"varint,9,opt,name=online,proto3" json:"online,omitempty"` // server-owned global ONLINE/OFFLINE + LastSeenAt *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=last_seen_at,json=lastSeenAt,proto3" json:"last_seen_at,omitempty"` + ProfileVersion int64 `protobuf:"varint,11,opt,name=profile_version,json=profileVersion,proto3" json:"profile_version,omitempty"` // monotonic; bumps on profile/avatar changes + PresenceVersion int64 `protobuf:"varint,12,opt,name=presence_version,json=presenceVersion,proto3" json:"presence_version,omitempty"` // monotonic; bumps on ONLINE/OFFLINE transitions + // Logical agent id (e.g. "coordinator") — set only for AUTHOR_TYPE_AGENT. + AgentId string `protobuf:"bytes,13,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,14,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,15,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Participant) Reset() { + *x = Participant{} + mi := &file_chat_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Participant) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Participant) ProtoMessage() {} + +func (x *Participant) ProtoReflect() protoreflect.Message { + mi := &file_chat_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Participant.ProtoReflect.Descriptor instead. +func (*Participant) Descriptor() ([]byte, []int) { + return file_chat_proto_rawDescGZIP(), []int{17} +} + +func (x *Participant) GetAuthorId() string { + if x != nil { + return x.AuthorId + } + return "" +} + +func (x *Participant) GetAuthorType() AuthorType { + if x != nil { + return x.AuthorType + } + return AuthorType_AUTHOR_TYPE_UNSPECIFIED +} + +func (x *Participant) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +func (x *Participant) GetAliases() map[string]string { + if x != nil { + return x.Aliases + } + return nil +} + +func (x *Participant) GetRole() string { + if x != nil { + return x.Role + } + return "" +} + +func (x *Participant) GetBio() string { + if x != nil { + return x.Bio + } + return "" +} + +func (x *Participant) GetStatusText() string { + if x != nil { + return x.StatusText + } + return "" +} + +func (x *Participant) GetAvatarHash() string { + if x != nil { + return x.AvatarHash + } + return "" +} + +func (x *Participant) GetOnline() bool { + if x != nil { + return x.Online + } + return false +} + +func (x *Participant) GetLastSeenAt() *timestamppb.Timestamp { + if x != nil { + return x.LastSeenAt + } + return nil +} + +func (x *Participant) GetProfileVersion() int64 { + if x != nil { + return x.ProfileVersion + } + return 0 +} + +func (x *Participant) GetPresenceVersion() int64 { + if x != nil { + return x.PresenceVersion + } + return 0 +} + +func (x *Participant) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +func (x *Participant) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *Participant) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +type GetParticipantRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AuthorId string `protobuf:"bytes,1,opt,name=author_id,json=authorId,proto3" json:"author_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetParticipantRequest) Reset() { + *x = GetParticipantRequest{} + mi := &file_chat_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetParticipantRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetParticipantRequest) ProtoMessage() {} + +func (x *GetParticipantRequest) ProtoReflect() protoreflect.Message { + mi := &file_chat_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetParticipantRequest.ProtoReflect.Descriptor instead. +func (*GetParticipantRequest) Descriptor() ([]byte, []int) { + return file_chat_proto_rawDescGZIP(), []int{18} +} + +func (x *GetParticipantRequest) GetAuthorId() string { + if x != nil { + return x.AuthorId + } + return "" +} + +type BatchGetParticipantsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AuthorIds []string `protobuf:"bytes,1,rep,name=author_ids,json=authorIds,proto3" json:"author_ids,omitempty"` // server caps size (e.g. 100) + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BatchGetParticipantsRequest) Reset() { + *x = BatchGetParticipantsRequest{} + mi := &file_chat_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchGetParticipantsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchGetParticipantsRequest) ProtoMessage() {} + +func (x *BatchGetParticipantsRequest) ProtoReflect() protoreflect.Message { + mi := &file_chat_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BatchGetParticipantsRequest.ProtoReflect.Descriptor instead. +func (*BatchGetParticipantsRequest) Descriptor() ([]byte, []int) { + return file_chat_proto_rawDescGZIP(), []int{19} +} + +func (x *BatchGetParticipantsRequest) GetAuthorIds() []string { + if x != nil { + return x.AuthorIds + } + return nil +} + +type BatchGetParticipantsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Participants []*Participant `protobuf:"bytes,1,rep,name=participants,proto3" json:"participants,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BatchGetParticipantsResponse) Reset() { + *x = BatchGetParticipantsResponse{} + mi := &file_chat_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchGetParticipantsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchGetParticipantsResponse) ProtoMessage() {} + +func (x *BatchGetParticipantsResponse) ProtoReflect() protoreflect.Message { + mi := &file_chat_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BatchGetParticipantsResponse.ProtoReflect.Descriptor instead. +func (*BatchGetParticipantsResponse) Descriptor() ([]byte, []int) { + return file_chat_proto_rawDescGZIP(), []int{20} +} + +func (x *BatchGetParticipantsResponse) GetParticipants() []*Participant { + if x != nil { + return x.Participants + } + return nil +} + +type ListTopicParticipantsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + TopicId string `protobuf:"bytes,1,opt,name=topic_id,json=topicId,proto3" json:"topic_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListTopicParticipantsRequest) Reset() { + *x = ListTopicParticipantsRequest{} + mi := &file_chat_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListTopicParticipantsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTopicParticipantsRequest) ProtoMessage() {} + +func (x *ListTopicParticipantsRequest) ProtoReflect() protoreflect.Message { + mi := &file_chat_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTopicParticipantsRequest.ProtoReflect.Descriptor instead. +func (*ListTopicParticipantsRequest) Descriptor() ([]byte, []int) { + return file_chat_proto_rawDescGZIP(), []int{21} +} + +func (x *ListTopicParticipantsRequest) GetTopicId() string { + if x != nil { + return x.TopicId + } + return "" +} + +type ListTopicParticipantsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Participants []*Participant `protobuf:"bytes,1,rep,name=participants,proto3" json:"participants,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListTopicParticipantsResponse) Reset() { + *x = ListTopicParticipantsResponse{} + mi := &file_chat_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListTopicParticipantsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTopicParticipantsResponse) ProtoMessage() {} + +func (x *ListTopicParticipantsResponse) ProtoReflect() protoreflect.Message { + mi := &file_chat_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTopicParticipantsResponse.ProtoReflect.Descriptor instead. +func (*ListTopicParticipantsResponse) Descriptor() ([]byte, []int) { + return file_chat_proto_rawDescGZIP(), []int{22} +} + +func (x *ListTopicParticipantsResponse) GetParticipants() []*Participant { + if x != nil { + return x.Participants + } + return nil +} + +type GetMyParticipantRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetMyParticipantRequest) Reset() { + *x = GetMyParticipantRequest{} + mi := &file_chat_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetMyParticipantRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetMyParticipantRequest) ProtoMessage() {} + +func (x *GetMyParticipantRequest) ProtoReflect() protoreflect.Message { + mi := &file_chat_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetMyParticipantRequest.ProtoReflect.Descriptor instead. +func (*GetMyParticipantRequest) Descriptor() ([]byte, []int) { + return file_chat_proto_rawDescGZIP(), []int{23} +} + +// RegisterSelfProfileRequest carries only mutable public profile fields. +// author_id / author_type / agent_id are derived server-side from the +// authenticated principal and MUST NOT be trusted from the request. +type RegisterSelfProfileRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + DisplayName string `protobuf:"bytes,1,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + Aliases map[string]string `protobuf:"bytes,2,rep,name=aliases,proto3" json:"aliases,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Role string `protobuf:"bytes,3,opt,name=role,proto3" json:"role,omitempty"` + Bio string `protobuf:"bytes,4,opt,name=bio,proto3" json:"bio,omitempty"` + StatusText string `protobuf:"bytes,5,opt,name=status_text,json=statusText,proto3" json:"status_text,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegisterSelfProfileRequest) Reset() { + *x = RegisterSelfProfileRequest{} + mi := &file_chat_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterSelfProfileRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterSelfProfileRequest) ProtoMessage() {} + +func (x *RegisterSelfProfileRequest) ProtoReflect() protoreflect.Message { + mi := &file_chat_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterSelfProfileRequest.ProtoReflect.Descriptor instead. +func (*RegisterSelfProfileRequest) Descriptor() ([]byte, []int) { + return file_chat_proto_rawDescGZIP(), []int{24} +} + +func (x *RegisterSelfProfileRequest) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +func (x *RegisterSelfProfileRequest) GetAliases() map[string]string { + if x != nil { + return x.Aliases + } + return nil +} + +func (x *RegisterSelfProfileRequest) GetRole() string { + if x != nil { + return x.Role + } + return "" +} + +func (x *RegisterSelfProfileRequest) GetBio() string { + if x != nil { + return x.Bio + } + return "" +} + +func (x *RegisterSelfProfileRequest) GetStatusText() string { + if x != nil { + return x.StatusText + } + return "" +} + +type SyncMyTopicMembershipsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Full desired set of topic UUIDs for the authenticated agent. + TopicIds []string `protobuf:"bytes,1,rep,name=topic_ids,json=topicIds,proto3" json:"topic_ids,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SyncMyTopicMembershipsRequest) Reset() { + *x = SyncMyTopicMembershipsRequest{} + mi := &file_chat_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncMyTopicMembershipsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncMyTopicMembershipsRequest) ProtoMessage() {} + +func (x *SyncMyTopicMembershipsRequest) ProtoReflect() protoreflect.Message { + mi := &file_chat_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncMyTopicMembershipsRequest.ProtoReflect.Descriptor instead. +func (*SyncMyTopicMembershipsRequest) Descriptor() ([]byte, []int) { + return file_chat_proto_rawDescGZIP(), []int{25} +} + +func (x *SyncMyTopicMembershipsRequest) GetTopicIds() []string { + if x != nil { + return x.TopicIds + } + return nil +} + +type SyncMyTopicMembershipsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + TopicIds []string `protobuf:"bytes,1,rep,name=topic_ids,json=topicIds,proto3" json:"topic_ids,omitempty"` // authoritative memberships after sync + Added int32 `protobuf:"varint,2,opt,name=added,proto3" json:"added,omitempty"` + Removed int32 `protobuf:"varint,3,opt,name=removed,proto3" json:"removed,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SyncMyTopicMembershipsResponse) Reset() { + *x = SyncMyTopicMembershipsResponse{} + mi := &file_chat_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncMyTopicMembershipsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncMyTopicMembershipsResponse) ProtoMessage() {} + +func (x *SyncMyTopicMembershipsResponse) ProtoReflect() protoreflect.Message { + mi := &file_chat_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncMyTopicMembershipsResponse.ProtoReflect.Descriptor instead. +func (*SyncMyTopicMembershipsResponse) Descriptor() ([]byte, []int) { + return file_chat_proto_rawDescGZIP(), []int{26} +} + +func (x *SyncMyTopicMembershipsResponse) GetTopicIds() []string { + if x != nil { + return x.TopicIds + } + return nil +} + +func (x *SyncMyTopicMembershipsResponse) GetAdded() int32 { + if x != nil { + return x.Added + } + return 0 +} + +func (x *SyncMyTopicMembershipsResponse) GetRemoved() int32 { + if x != nil { + return x.Removed + } + return 0 +} + +type UploadSelfAvatarRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Raw image bytes (PNG/JPEG/WebP). Content-Type is validated from magic + // bytes + decode, not trusted from metadata alone. + Content []byte `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"` + // Optional declared content type; must match detected type when set. + ContentType string `protobuf:"bytes,2,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UploadSelfAvatarRequest) Reset() { + *x = UploadSelfAvatarRequest{} + mi := &file_chat_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UploadSelfAvatarRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UploadSelfAvatarRequest) ProtoMessage() {} + +func (x *UploadSelfAvatarRequest) ProtoReflect() protoreflect.Message { + mi := &file_chat_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UploadSelfAvatarRequest.ProtoReflect.Descriptor instead. +func (*UploadSelfAvatarRequest) Descriptor() ([]byte, []int) { + return file_chat_proto_rawDescGZIP(), []int{27} +} + +func (x *UploadSelfAvatarRequest) GetContent() []byte { + if x != nil { + return x.Content + } + return nil +} + +func (x *UploadSelfAvatarRequest) GetContentType() string { + if x != nil { + return x.ContentType + } + return "" +} + +type DeleteSelfAvatarRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteSelfAvatarRequest) Reset() { + *x = DeleteSelfAvatarRequest{} + mi := &file_chat_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteSelfAvatarRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteSelfAvatarRequest) ProtoMessage() {} + +func (x *DeleteSelfAvatarRequest) ProtoReflect() protoreflect.Message { + mi := &file_chat_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteSelfAvatarRequest.ProtoReflect.Descriptor instead. +func (*DeleteSelfAvatarRequest) Descriptor() ([]byte, []int) { + return file_chat_proto_rawDescGZIP(), []int{28} +} + var File_chat_proto protoreflect.FileDescriptor const file_chat_proto_rawDesc = "" + @@ -1317,7 +2009,65 @@ const file_chat_proto_rawDesc = "" + "\x0fsubscription_id\x18\x03 \x01(\tR\x0esubscriptionId\x12\x14\n" + "\x05ready\x18\x04 \x01(\bR\x05ready\"A\n" + "\x13AcknowledgeResponse\x12*\n" + - "\x11last_ack_sequence\x18\x01 \x01(\x03R\x0flastAckSequence*\x8c\x01\n" + + "\x11last_ack_sequence\x18\x01 \x01(\x03R\x0flastAckSequence\"\x99\x05\n" + + "\vParticipant\x12\x1b\n" + + "\tauthor_id\x18\x01 \x01(\tR\bauthorId\x121\n" + + "\vauthor_type\x18\x02 \x01(\x0e2\x10.chat.AuthorTypeR\n" + + "authorType\x12!\n" + + "\fdisplay_name\x18\x03 \x01(\tR\vdisplayName\x128\n" + + "\aaliases\x18\x04 \x03(\v2\x1e.chat.Participant.AliasesEntryR\aaliases\x12\x12\n" + + "\x04role\x18\x05 \x01(\tR\x04role\x12\x10\n" + + "\x03bio\x18\x06 \x01(\tR\x03bio\x12\x1f\n" + + "\vstatus_text\x18\a \x01(\tR\n" + + "statusText\x12\x1f\n" + + "\vavatar_hash\x18\b \x01(\tR\n" + + "avatarHash\x12\x16\n" + + "\x06online\x18\t \x01(\bR\x06online\x12<\n" + + "\flast_seen_at\x18\n" + + " \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "lastSeenAt\x12'\n" + + "\x0fprofile_version\x18\v \x01(\x03R\x0eprofileVersion\x12)\n" + + "\x10presence_version\x18\f \x01(\x03R\x0fpresenceVersion\x12\x19\n" + + "\bagent_id\x18\r \x01(\tR\aagentId\x129\n" + + "\n" + + "created_at\x18\x0e \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + + "\n" + + "updated_at\x18\x0f \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\x1a:\n" + + "\fAliasesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"4\n" + + "\x15GetParticipantRequest\x12\x1b\n" + + "\tauthor_id\x18\x01 \x01(\tR\bauthorId\"<\n" + + "\x1bBatchGetParticipantsRequest\x12\x1d\n" + + "\n" + + "author_ids\x18\x01 \x03(\tR\tauthorIds\"U\n" + + "\x1cBatchGetParticipantsResponse\x125\n" + + "\fparticipants\x18\x01 \x03(\v2\x11.chat.ParticipantR\fparticipants\"9\n" + + "\x1cListTopicParticipantsRequest\x12\x19\n" + + "\btopic_id\x18\x01 \x01(\tR\atopicId\"V\n" + + "\x1dListTopicParticipantsResponse\x125\n" + + "\fparticipants\x18\x01 \x03(\v2\x11.chat.ParticipantR\fparticipants\"\x19\n" + + "\x17GetMyParticipantRequest\"\x8b\x02\n" + + "\x1aRegisterSelfProfileRequest\x12!\n" + + "\fdisplay_name\x18\x01 \x01(\tR\vdisplayName\x12G\n" + + "\aaliases\x18\x02 \x03(\v2-.chat.RegisterSelfProfileRequest.AliasesEntryR\aaliases\x12\x12\n" + + "\x04role\x18\x03 \x01(\tR\x04role\x12\x10\n" + + "\x03bio\x18\x04 \x01(\tR\x03bio\x12\x1f\n" + + "\vstatus_text\x18\x05 \x01(\tR\n" + + "statusText\x1a:\n" + + "\fAliasesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"<\n" + + "\x1dSyncMyTopicMembershipsRequest\x12\x1b\n" + + "\ttopic_ids\x18\x01 \x03(\tR\btopicIds\"m\n" + + "\x1eSyncMyTopicMembershipsResponse\x12\x1b\n" + + "\ttopic_ids\x18\x01 \x03(\tR\btopicIds\x12\x14\n" + + "\x05added\x18\x02 \x01(\x05R\x05added\x12\x18\n" + + "\aremoved\x18\x03 \x01(\x05R\aremoved\"V\n" + + "\x17UploadSelfAvatarRequest\x12\x18\n" + + "\acontent\x18\x01 \x01(\fR\acontent\x12!\n" + + "\fcontent_type\x18\x02 \x01(\tR\vcontentType\"\x19\n" + + "\x17DeleteSelfAvatarRequest*\x8c\x01\n" + "\n" + "AuthorType\x12\x1b\n" + "\x17AUTHOR_TYPE_UNSPECIFIED\x10\x00\x12\x15\n" + @@ -1330,7 +2080,7 @@ const file_chat_proto_rawDesc = "" + "\x12MESSAGE_SOURCE_WEB\x10\x01\x12\x1b\n" + "\x17MESSAGE_SOURCE_TELEGRAM\x10\x02\x12\x18\n" + "\x14MESSAGE_SOURCE_AGENT\x10\x03\x12\x19\n" + - "\x15MESSAGE_SOURCE_SYSTEM\x10\x042\xec\x04\n" + + "\x15MESSAGE_SOURCE_SYSTEM\x10\x042\xf2\t\n" + "\vChatService\x12?\n" + "\n" + "ListTopics\x12\x17.chat.ListTopicsRequest\x1a\x18.chat.ListTopicsResponse\x12.\n" + @@ -1343,7 +2093,15 @@ const file_chat_proto_rawDesc = "" + "GetMessage\x12\x17.chat.GetMessageRequest\x1a\r.chat.Message\x126\n" + "\aHistory\x12\x14.chat.HistoryRequest\x1a\x15.chat.HistoryResponse\x12<\n" + "\tSubscribe\x12\x16.chat.SubscribeRequest\x1a\x15.chat.DeliveryMessage0\x01\x12B\n" + - "\vAcknowledge\x12\x18.chat.AcknowledgeRequest\x1a\x19.chat.AcknowledgeResponseB)Z'gitea.fe.kaien.ru/kaien/l-ai-chat-protob\x06proto3" + "\vAcknowledge\x12\x18.chat.AcknowledgeRequest\x1a\x19.chat.AcknowledgeResponse\x12@\n" + + "\x0eGetParticipant\x12\x1b.chat.GetParticipantRequest\x1a\x11.chat.Participant\x12]\n" + + "\x14BatchGetParticipants\x12!.chat.BatchGetParticipantsRequest\x1a\".chat.BatchGetParticipantsResponse\x12`\n" + + "\x15ListTopicParticipants\x12\".chat.ListTopicParticipantsRequest\x1a#.chat.ListTopicParticipantsResponse\x12D\n" + + "\x10GetMyParticipant\x12\x1d.chat.GetMyParticipantRequest\x1a\x11.chat.Participant\x12J\n" + + "\x13RegisterSelfProfile\x12 .chat.RegisterSelfProfileRequest\x1a\x11.chat.Participant\x12c\n" + + "\x16SyncMyTopicMemberships\x12#.chat.SyncMyTopicMembershipsRequest\x1a$.chat.SyncMyTopicMembershipsResponse\x12D\n" + + "\x10UploadSelfAvatar\x12\x1d.chat.UploadSelfAvatarRequest\x1a\x11.chat.Participant\x12D\n" + + "\x10DeleteSelfAvatar\x12\x1d.chat.DeleteSelfAvatarRequest\x1a\x11.chat.ParticipantB)Z'gitea.fe.kaien.ru/kaien/l-ai-chat-protob\x06proto3" var ( file_chat_proto_rawDescOnce sync.Once @@ -1358,65 +2116,103 @@ func file_chat_proto_rawDescGZIP() []byte { } var file_chat_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_chat_proto_msgTypes = make([]protoimpl.MessageInfo, 17) +var file_chat_proto_msgTypes = make([]protoimpl.MessageInfo, 31) var file_chat_proto_goTypes = []any{ - (AuthorType)(0), // 0: chat.AuthorType - (MessageSource)(0), // 1: chat.MessageSource - (*Topic)(nil), // 2: chat.Topic - (*Message)(nil), // 3: chat.Message - (*ListTopicsRequest)(nil), // 4: chat.ListTopicsRequest - (*ListTopicsResponse)(nil), // 5: chat.ListTopicsResponse - (*GetTopicRequest)(nil), // 6: chat.GetTopicRequest - (*CreateTopicRequest)(nil), // 7: chat.CreateTopicRequest - (*UpdateTopicRequest)(nil), // 8: chat.UpdateTopicRequest - (*PostMessageRequest)(nil), // 9: chat.PostMessageRequest - (*AgentPostMessageRequest)(nil), // 10: chat.AgentPostMessageRequest - (*PostMessageResponse)(nil), // 11: chat.PostMessageResponse - (*GetMessageRequest)(nil), // 12: chat.GetMessageRequest - (*HistoryRequest)(nil), // 13: chat.HistoryRequest - (*HistoryResponse)(nil), // 14: chat.HistoryResponse - (*SubscribeRequest)(nil), // 15: chat.SubscribeRequest - (*DeliveryMessage)(nil), // 16: chat.DeliveryMessage - (*AcknowledgeRequest)(nil), // 17: chat.AcknowledgeRequest - (*AcknowledgeResponse)(nil), // 18: chat.AcknowledgeResponse - (*timestamppb.Timestamp)(nil), // 19: google.protobuf.Timestamp + (AuthorType)(0), // 0: chat.AuthorType + (MessageSource)(0), // 1: chat.MessageSource + (*Topic)(nil), // 2: chat.Topic + (*Message)(nil), // 3: chat.Message + (*ListTopicsRequest)(nil), // 4: chat.ListTopicsRequest + (*ListTopicsResponse)(nil), // 5: chat.ListTopicsResponse + (*GetTopicRequest)(nil), // 6: chat.GetTopicRequest + (*CreateTopicRequest)(nil), // 7: chat.CreateTopicRequest + (*UpdateTopicRequest)(nil), // 8: chat.UpdateTopicRequest + (*PostMessageRequest)(nil), // 9: chat.PostMessageRequest + (*AgentPostMessageRequest)(nil), // 10: chat.AgentPostMessageRequest + (*PostMessageResponse)(nil), // 11: chat.PostMessageResponse + (*GetMessageRequest)(nil), // 12: chat.GetMessageRequest + (*HistoryRequest)(nil), // 13: chat.HistoryRequest + (*HistoryResponse)(nil), // 14: chat.HistoryResponse + (*SubscribeRequest)(nil), // 15: chat.SubscribeRequest + (*DeliveryMessage)(nil), // 16: chat.DeliveryMessage + (*AcknowledgeRequest)(nil), // 17: chat.AcknowledgeRequest + (*AcknowledgeResponse)(nil), // 18: chat.AcknowledgeResponse + (*Participant)(nil), // 19: chat.Participant + (*GetParticipantRequest)(nil), // 20: chat.GetParticipantRequest + (*BatchGetParticipantsRequest)(nil), // 21: chat.BatchGetParticipantsRequest + (*BatchGetParticipantsResponse)(nil), // 22: chat.BatchGetParticipantsResponse + (*ListTopicParticipantsRequest)(nil), // 23: chat.ListTopicParticipantsRequest + (*ListTopicParticipantsResponse)(nil), // 24: chat.ListTopicParticipantsResponse + (*GetMyParticipantRequest)(nil), // 25: chat.GetMyParticipantRequest + (*RegisterSelfProfileRequest)(nil), // 26: chat.RegisterSelfProfileRequest + (*SyncMyTopicMembershipsRequest)(nil), // 27: chat.SyncMyTopicMembershipsRequest + (*SyncMyTopicMembershipsResponse)(nil), // 28: chat.SyncMyTopicMembershipsResponse + (*UploadSelfAvatarRequest)(nil), // 29: chat.UploadSelfAvatarRequest + (*DeleteSelfAvatarRequest)(nil), // 30: chat.DeleteSelfAvatarRequest + nil, // 31: chat.Participant.AliasesEntry + nil, // 32: chat.RegisterSelfProfileRequest.AliasesEntry + (*timestamppb.Timestamp)(nil), // 33: google.protobuf.Timestamp } var file_chat_proto_depIdxs = []int32{ - 19, // 0: chat.Topic.created_at:type_name -> google.protobuf.Timestamp + 33, // 0: chat.Topic.created_at:type_name -> google.protobuf.Timestamp 0, // 1: chat.Message.author_type:type_name -> chat.AuthorType 1, // 2: chat.Message.source:type_name -> chat.MessageSource - 19, // 3: chat.Message.created_at:type_name -> google.protobuf.Timestamp + 33, // 3: chat.Message.created_at:type_name -> google.protobuf.Timestamp 2, // 4: chat.ListTopicsResponse.topics:type_name -> chat.Topic 0, // 5: chat.PostMessageRequest.author_type:type_name -> chat.AuthorType 1, // 6: chat.PostMessageRequest.source:type_name -> chat.MessageSource 3, // 7: chat.PostMessageResponse.message:type_name -> chat.Message 3, // 8: chat.HistoryResponse.messages:type_name -> chat.Message 3, // 9: chat.DeliveryMessage.message:type_name -> chat.Message - 4, // 10: chat.ChatService.ListTopics:input_type -> chat.ListTopicsRequest - 6, // 11: chat.ChatService.GetTopic:input_type -> chat.GetTopicRequest - 7, // 12: chat.ChatService.CreateTopic:input_type -> chat.CreateTopicRequest - 8, // 13: chat.ChatService.UpdateTopic:input_type -> chat.UpdateTopicRequest - 9, // 14: chat.ChatService.PostMessage:input_type -> chat.PostMessageRequest - 10, // 15: chat.ChatService.AgentPostMessage:input_type -> chat.AgentPostMessageRequest - 12, // 16: chat.ChatService.GetMessage:input_type -> chat.GetMessageRequest - 13, // 17: chat.ChatService.History:input_type -> chat.HistoryRequest - 15, // 18: chat.ChatService.Subscribe:input_type -> chat.SubscribeRequest - 17, // 19: chat.ChatService.Acknowledge:input_type -> chat.AcknowledgeRequest - 5, // 20: chat.ChatService.ListTopics:output_type -> chat.ListTopicsResponse - 2, // 21: chat.ChatService.GetTopic:output_type -> chat.Topic - 2, // 22: chat.ChatService.CreateTopic:output_type -> chat.Topic - 2, // 23: chat.ChatService.UpdateTopic:output_type -> chat.Topic - 11, // 24: chat.ChatService.PostMessage:output_type -> chat.PostMessageResponse - 11, // 25: chat.ChatService.AgentPostMessage:output_type -> chat.PostMessageResponse - 3, // 26: chat.ChatService.GetMessage:output_type -> chat.Message - 14, // 27: chat.ChatService.History:output_type -> chat.HistoryResponse - 16, // 28: chat.ChatService.Subscribe:output_type -> chat.DeliveryMessage - 18, // 29: chat.ChatService.Acknowledge:output_type -> chat.AcknowledgeResponse - 20, // [20:30] is the sub-list for method output_type - 10, // [10:20] is the sub-list for method input_type - 10, // [10:10] is the sub-list for extension type_name - 10, // [10:10] is the sub-list for extension extendee - 0, // [0:10] is the sub-list for field type_name + 0, // 10: chat.Participant.author_type:type_name -> chat.AuthorType + 31, // 11: chat.Participant.aliases:type_name -> chat.Participant.AliasesEntry + 33, // 12: chat.Participant.last_seen_at:type_name -> google.protobuf.Timestamp + 33, // 13: chat.Participant.created_at:type_name -> google.protobuf.Timestamp + 33, // 14: chat.Participant.updated_at:type_name -> google.protobuf.Timestamp + 19, // 15: chat.BatchGetParticipantsResponse.participants:type_name -> chat.Participant + 19, // 16: chat.ListTopicParticipantsResponse.participants:type_name -> chat.Participant + 32, // 17: chat.RegisterSelfProfileRequest.aliases:type_name -> chat.RegisterSelfProfileRequest.AliasesEntry + 4, // 18: chat.ChatService.ListTopics:input_type -> chat.ListTopicsRequest + 6, // 19: chat.ChatService.GetTopic:input_type -> chat.GetTopicRequest + 7, // 20: chat.ChatService.CreateTopic:input_type -> chat.CreateTopicRequest + 8, // 21: chat.ChatService.UpdateTopic:input_type -> chat.UpdateTopicRequest + 9, // 22: chat.ChatService.PostMessage:input_type -> chat.PostMessageRequest + 10, // 23: chat.ChatService.AgentPostMessage:input_type -> chat.AgentPostMessageRequest + 12, // 24: chat.ChatService.GetMessage:input_type -> chat.GetMessageRequest + 13, // 25: chat.ChatService.History:input_type -> chat.HistoryRequest + 15, // 26: chat.ChatService.Subscribe:input_type -> chat.SubscribeRequest + 17, // 27: chat.ChatService.Acknowledge:input_type -> chat.AcknowledgeRequest + 20, // 28: chat.ChatService.GetParticipant:input_type -> chat.GetParticipantRequest + 21, // 29: chat.ChatService.BatchGetParticipants:input_type -> chat.BatchGetParticipantsRequest + 23, // 30: chat.ChatService.ListTopicParticipants:input_type -> chat.ListTopicParticipantsRequest + 25, // 31: chat.ChatService.GetMyParticipant:input_type -> chat.GetMyParticipantRequest + 26, // 32: chat.ChatService.RegisterSelfProfile:input_type -> chat.RegisterSelfProfileRequest + 27, // 33: chat.ChatService.SyncMyTopicMemberships:input_type -> chat.SyncMyTopicMembershipsRequest + 29, // 34: chat.ChatService.UploadSelfAvatar:input_type -> chat.UploadSelfAvatarRequest + 30, // 35: chat.ChatService.DeleteSelfAvatar:input_type -> chat.DeleteSelfAvatarRequest + 5, // 36: chat.ChatService.ListTopics:output_type -> chat.ListTopicsResponse + 2, // 37: chat.ChatService.GetTopic:output_type -> chat.Topic + 2, // 38: chat.ChatService.CreateTopic:output_type -> chat.Topic + 2, // 39: chat.ChatService.UpdateTopic:output_type -> chat.Topic + 11, // 40: chat.ChatService.PostMessage:output_type -> chat.PostMessageResponse + 11, // 41: chat.ChatService.AgentPostMessage:output_type -> chat.PostMessageResponse + 3, // 42: chat.ChatService.GetMessage:output_type -> chat.Message + 14, // 43: chat.ChatService.History:output_type -> chat.HistoryResponse + 16, // 44: chat.ChatService.Subscribe:output_type -> chat.DeliveryMessage + 18, // 45: chat.ChatService.Acknowledge:output_type -> chat.AcknowledgeResponse + 19, // 46: chat.ChatService.GetParticipant:output_type -> chat.Participant + 22, // 47: chat.ChatService.BatchGetParticipants:output_type -> chat.BatchGetParticipantsResponse + 24, // 48: chat.ChatService.ListTopicParticipants:output_type -> chat.ListTopicParticipantsResponse + 19, // 49: chat.ChatService.GetMyParticipant:output_type -> chat.Participant + 19, // 50: chat.ChatService.RegisterSelfProfile:output_type -> chat.Participant + 28, // 51: chat.ChatService.SyncMyTopicMemberships:output_type -> chat.SyncMyTopicMembershipsResponse + 19, // 52: chat.ChatService.UploadSelfAvatar:output_type -> chat.Participant + 19, // 53: chat.ChatService.DeleteSelfAvatar:output_type -> chat.Participant + 36, // [36:54] is the sub-list for method output_type + 18, // [18:36] is the sub-list for method input_type + 18, // [18:18] is the sub-list for extension type_name + 18, // [18:18] is the sub-list for extension extendee + 0, // [0:18] is the sub-list for field type_name } func init() { file_chat_proto_init() } @@ -1437,7 +2233,7 @@ func file_chat_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_chat_proto_rawDesc), len(file_chat_proto_rawDesc)), NumEnums: 2, - NumMessages: 17, + NumMessages: 31, NumExtensions: 0, NumServices: 1, }, diff --git a/chat.proto b/chat.proto index 73c5722..5c0b51a 100644 --- a/chat.proto +++ b/chat.proto @@ -69,6 +69,50 @@ service ChatService { // it never moves backwards. Cursor changes ONLY here; delivery itself // never persists cursor state. rpc Acknowledge(AcknowledgeRequest) returns (AcknowledgeResponse); + + // ---- Participant registry (Stage 7.1) ---- + // + // Canonical identity is author_id + author_type. Presentation fields + // (display_name, aliases, role, bio, status_text, avatar, presence) are + // mutable and resolved at read time ("current-name" semantics). Messages + // never snapshot display names. + + // GetParticipant returns one participant by author_id. Missing metadata + // yields a fail-soft stub (author_id + author_type only), never NotFound + // for known historical authors that were backfilled as stubs. + rpc GetParticipant(GetParticipantRequest) returns (Participant); + + // BatchGetParticipants returns participants for the given author_ids + // (deduplicated; server-bounded). Unknown IDs may be omitted or returned + // as fail-soft stubs — clients must tolerate both. + rpc BatchGetParticipants(BatchGetParticipantsRequest) returns (BatchGetParticipantsResponse); + + // ListTopicParticipants returns the durable topic membership roster + // (presentation/social state, NOT an ACL). Membership ≠ permission. + rpc ListTopicParticipants(ListTopicParticipantsRequest) returns (ListTopicParticipantsResponse); + + // GetMyParticipant returns the authenticated caller's own participant + // (agent from service token, or human via HTTP equivalent). + rpc GetMyParticipant(GetMyParticipantRequest) returns (Participant); + + // RegisterSelfProfile upserts the authenticated agent's public profile. + // Caller-supplied identity fields (author_id, author_type, agent_id) are + // ignored: the server derives them from the service token → logical + // agent ID → AgentUUID. Returns the authoritative Participant. + rpc RegisterSelfProfile(RegisterSelfProfileRequest) returns (Participant); + + // SyncMyTopicMemberships replaces the authenticated agent's durable + // topic memberships with the full desired set (add missing, remove + // stale). Caller cannot choose another agent's author_id. + rpc SyncMyTopicMemberships(SyncMyTopicMembershipsRequest) returns (SyncMyTopicMembershipsResponse); + + // UploadSelfAvatar replaces the authenticated agent's avatar bytes + // (PNG/JPEG/WebP only; size/dimension limits enforced server-side). + // Returns updated Participant metadata including avatar_hash. + rpc UploadSelfAvatar(UploadSelfAvatarRequest) returns (Participant); + + // DeleteSelfAvatar clears the authenticated agent's avatar. + rpc DeleteSelfAvatar(DeleteSelfAvatarRequest) returns (Participant); } // AuthorType is the kind of identity a message author has. HUMAN covers @@ -232,3 +276,82 @@ message AcknowledgeRequest { message AcknowledgeResponse { int64 last_ack_sequence = 1; // the cursor as stored (never lower than before) } + +// ---- Participants ---- + +// Participant is the public presentation of a chat identity. +// Internal platform IDs (telegram_id, discord_id, email, OAuth subject, +// global_user_id) are NEVER exposed here. +message Participant { + string author_id = 1; // canonical UUID + AuthorType author_type = 2; + string display_name = 3; // may be empty for stubs + // Locale key → localized alias (e.g. "ru" → "Мира"). Not unique. + map aliases = 4; + string role = 5; // e.g. "Coordinator"; may be empty + string bio = 6; + string status_text = 7; // participant-owned; distinct from presence + string avatar_hash = 8; // empty when no avatar; used for ETag / skip-upload + bool online = 9; // server-owned global ONLINE/OFFLINE + google.protobuf.Timestamp last_seen_at = 10; + int64 profile_version = 11; // monotonic; bumps on profile/avatar changes + int64 presence_version = 12; // monotonic; bumps on ONLINE/OFFLINE transitions + // Logical agent id (e.g. "coordinator") — set only for AUTHOR_TYPE_AGENT. + string agent_id = 13; + google.protobuf.Timestamp created_at = 14; + google.protobuf.Timestamp updated_at = 15; +} + +message GetParticipantRequest { + string author_id = 1; +} + +message BatchGetParticipantsRequest { + repeated string author_ids = 1; // server caps size (e.g. 100) +} + +message BatchGetParticipantsResponse { + repeated Participant participants = 1; +} + +message ListTopicParticipantsRequest { + string topic_id = 1; +} + +message ListTopicParticipantsResponse { + repeated Participant participants = 1; +} + +message GetMyParticipantRequest {} + +// RegisterSelfProfileRequest carries only mutable public profile fields. +// author_id / author_type / agent_id are derived server-side from the +// authenticated principal and MUST NOT be trusted from the request. +message RegisterSelfProfileRequest { + string display_name = 1; + map aliases = 2; + string role = 3; + string bio = 4; + string status_text = 5; +} + +message SyncMyTopicMembershipsRequest { + // Full desired set of topic UUIDs for the authenticated agent. + repeated string topic_ids = 1; +} + +message SyncMyTopicMembershipsResponse { + repeated string topic_ids = 1; // authoritative memberships after sync + int32 added = 2; + int32 removed = 3; +} + +message UploadSelfAvatarRequest { + // Raw image bytes (PNG/JPEG/WebP). Content-Type is validated from magic + // bytes + decode, not trusted from metadata alone. + bytes content = 1; + // Optional declared content type; must match detected type when set. + string content_type = 2; +} + +message DeleteSelfAvatarRequest {} diff --git a/chat_grpc.pb.go b/chat_grpc.pb.go index 94c795a..970f41f 100644 --- a/chat_grpc.pb.go +++ b/chat_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc (unknown) +// - protoc v7.35.1 // source: chat.proto package l_ai_chat_proto @@ -19,16 +19,24 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - ChatService_ListTopics_FullMethodName = "/chat.ChatService/ListTopics" - ChatService_GetTopic_FullMethodName = "/chat.ChatService/GetTopic" - ChatService_CreateTopic_FullMethodName = "/chat.ChatService/CreateTopic" - ChatService_UpdateTopic_FullMethodName = "/chat.ChatService/UpdateTopic" - ChatService_PostMessage_FullMethodName = "/chat.ChatService/PostMessage" - ChatService_AgentPostMessage_FullMethodName = "/chat.ChatService/AgentPostMessage" - ChatService_GetMessage_FullMethodName = "/chat.ChatService/GetMessage" - ChatService_History_FullMethodName = "/chat.ChatService/History" - ChatService_Subscribe_FullMethodName = "/chat.ChatService/Subscribe" - ChatService_Acknowledge_FullMethodName = "/chat.ChatService/Acknowledge" + ChatService_ListTopics_FullMethodName = "/chat.ChatService/ListTopics" + ChatService_GetTopic_FullMethodName = "/chat.ChatService/GetTopic" + ChatService_CreateTopic_FullMethodName = "/chat.ChatService/CreateTopic" + ChatService_UpdateTopic_FullMethodName = "/chat.ChatService/UpdateTopic" + ChatService_PostMessage_FullMethodName = "/chat.ChatService/PostMessage" + ChatService_AgentPostMessage_FullMethodName = "/chat.ChatService/AgentPostMessage" + ChatService_GetMessage_FullMethodName = "/chat.ChatService/GetMessage" + ChatService_History_FullMethodName = "/chat.ChatService/History" + ChatService_Subscribe_FullMethodName = "/chat.ChatService/Subscribe" + ChatService_Acknowledge_FullMethodName = "/chat.ChatService/Acknowledge" + ChatService_GetParticipant_FullMethodName = "/chat.ChatService/GetParticipant" + ChatService_BatchGetParticipants_FullMethodName = "/chat.ChatService/BatchGetParticipants" + ChatService_ListTopicParticipants_FullMethodName = "/chat.ChatService/ListTopicParticipants" + ChatService_GetMyParticipant_FullMethodName = "/chat.ChatService/GetMyParticipant" + ChatService_RegisterSelfProfile_FullMethodName = "/chat.ChatService/RegisterSelfProfile" + ChatService_SyncMyTopicMemberships_FullMethodName = "/chat.ChatService/SyncMyTopicMemberships" + ChatService_UploadSelfAvatar_FullMethodName = "/chat.ChatService/UploadSelfAvatar" + ChatService_DeleteSelfAvatar_FullMethodName = "/chat.ChatService/DeleteSelfAvatar" ) // ChatServiceClient is the client API for ChatService service. @@ -89,6 +97,35 @@ type ChatServiceClient interface { // it never moves backwards. Cursor changes ONLY here; delivery itself // never persists cursor state. Acknowledge(ctx context.Context, in *AcknowledgeRequest, opts ...grpc.CallOption) (*AcknowledgeResponse, error) + // GetParticipant returns one participant by author_id. Missing metadata + // yields a fail-soft stub (author_id + author_type only), never NotFound + // for known historical authors that were backfilled as stubs. + GetParticipant(ctx context.Context, in *GetParticipantRequest, opts ...grpc.CallOption) (*Participant, error) + // BatchGetParticipants returns participants for the given author_ids + // (deduplicated; server-bounded). Unknown IDs may be omitted or returned + // as fail-soft stubs — clients must tolerate both. + BatchGetParticipants(ctx context.Context, in *BatchGetParticipantsRequest, opts ...grpc.CallOption) (*BatchGetParticipantsResponse, error) + // ListTopicParticipants returns the durable topic membership roster + // (presentation/social state, NOT an ACL). Membership ≠ permission. + ListTopicParticipants(ctx context.Context, in *ListTopicParticipantsRequest, opts ...grpc.CallOption) (*ListTopicParticipantsResponse, error) + // GetMyParticipant returns the authenticated caller's own participant + // (agent from service token, or human via HTTP equivalent). + GetMyParticipant(ctx context.Context, in *GetMyParticipantRequest, opts ...grpc.CallOption) (*Participant, error) + // RegisterSelfProfile upserts the authenticated agent's public profile. + // Caller-supplied identity fields (author_id, author_type, agent_id) are + // ignored: the server derives them from the service token → logical + // agent ID → AgentUUID. Returns the authoritative Participant. + RegisterSelfProfile(ctx context.Context, in *RegisterSelfProfileRequest, opts ...grpc.CallOption) (*Participant, error) + // SyncMyTopicMemberships replaces the authenticated agent's durable + // topic memberships with the full desired set (add missing, remove + // stale). Caller cannot choose another agent's author_id. + SyncMyTopicMemberships(ctx context.Context, in *SyncMyTopicMembershipsRequest, opts ...grpc.CallOption) (*SyncMyTopicMembershipsResponse, error) + // UploadSelfAvatar replaces the authenticated agent's avatar bytes + // (PNG/JPEG/WebP only; size/dimension limits enforced server-side). + // Returns updated Participant metadata including avatar_hash. + UploadSelfAvatar(ctx context.Context, in *UploadSelfAvatarRequest, opts ...grpc.CallOption) (*Participant, error) + // DeleteSelfAvatar clears the authenticated agent's avatar. + DeleteSelfAvatar(ctx context.Context, in *DeleteSelfAvatarRequest, opts ...grpc.CallOption) (*Participant, error) } type chatServiceClient struct { @@ -208,6 +245,86 @@ func (c *chatServiceClient) Acknowledge(ctx context.Context, in *AcknowledgeRequ return out, nil } +func (c *chatServiceClient) GetParticipant(ctx context.Context, in *GetParticipantRequest, opts ...grpc.CallOption) (*Participant, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Participant) + err := c.cc.Invoke(ctx, ChatService_GetParticipant_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *chatServiceClient) BatchGetParticipants(ctx context.Context, in *BatchGetParticipantsRequest, opts ...grpc.CallOption) (*BatchGetParticipantsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(BatchGetParticipantsResponse) + err := c.cc.Invoke(ctx, ChatService_BatchGetParticipants_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *chatServiceClient) ListTopicParticipants(ctx context.Context, in *ListTopicParticipantsRequest, opts ...grpc.CallOption) (*ListTopicParticipantsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListTopicParticipantsResponse) + err := c.cc.Invoke(ctx, ChatService_ListTopicParticipants_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *chatServiceClient) GetMyParticipant(ctx context.Context, in *GetMyParticipantRequest, opts ...grpc.CallOption) (*Participant, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Participant) + err := c.cc.Invoke(ctx, ChatService_GetMyParticipant_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *chatServiceClient) RegisterSelfProfile(ctx context.Context, in *RegisterSelfProfileRequest, opts ...grpc.CallOption) (*Participant, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Participant) + err := c.cc.Invoke(ctx, ChatService_RegisterSelfProfile_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *chatServiceClient) SyncMyTopicMemberships(ctx context.Context, in *SyncMyTopicMembershipsRequest, opts ...grpc.CallOption) (*SyncMyTopicMembershipsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SyncMyTopicMembershipsResponse) + err := c.cc.Invoke(ctx, ChatService_SyncMyTopicMemberships_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *chatServiceClient) UploadSelfAvatar(ctx context.Context, in *UploadSelfAvatarRequest, opts ...grpc.CallOption) (*Participant, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Participant) + err := c.cc.Invoke(ctx, ChatService_UploadSelfAvatar_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *chatServiceClient) DeleteSelfAvatar(ctx context.Context, in *DeleteSelfAvatarRequest, opts ...grpc.CallOption) (*Participant, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Participant) + err := c.cc.Invoke(ctx, ChatService_DeleteSelfAvatar_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // ChatServiceServer is the server API for ChatService service. // All implementations must embed UnimplementedChatServiceServer // for forward compatibility. @@ -266,6 +383,35 @@ type ChatServiceServer interface { // it never moves backwards. Cursor changes ONLY here; delivery itself // never persists cursor state. Acknowledge(context.Context, *AcknowledgeRequest) (*AcknowledgeResponse, error) + // GetParticipant returns one participant by author_id. Missing metadata + // yields a fail-soft stub (author_id + author_type only), never NotFound + // for known historical authors that were backfilled as stubs. + GetParticipant(context.Context, *GetParticipantRequest) (*Participant, error) + // BatchGetParticipants returns participants for the given author_ids + // (deduplicated; server-bounded). Unknown IDs may be omitted or returned + // as fail-soft stubs — clients must tolerate both. + BatchGetParticipants(context.Context, *BatchGetParticipantsRequest) (*BatchGetParticipantsResponse, error) + // ListTopicParticipants returns the durable topic membership roster + // (presentation/social state, NOT an ACL). Membership ≠ permission. + ListTopicParticipants(context.Context, *ListTopicParticipantsRequest) (*ListTopicParticipantsResponse, error) + // GetMyParticipant returns the authenticated caller's own participant + // (agent from service token, or human via HTTP equivalent). + GetMyParticipant(context.Context, *GetMyParticipantRequest) (*Participant, error) + // RegisterSelfProfile upserts the authenticated agent's public profile. + // Caller-supplied identity fields (author_id, author_type, agent_id) are + // ignored: the server derives them from the service token → logical + // agent ID → AgentUUID. Returns the authoritative Participant. + RegisterSelfProfile(context.Context, *RegisterSelfProfileRequest) (*Participant, error) + // SyncMyTopicMemberships replaces the authenticated agent's durable + // topic memberships with the full desired set (add missing, remove + // stale). Caller cannot choose another agent's author_id. + SyncMyTopicMemberships(context.Context, *SyncMyTopicMembershipsRequest) (*SyncMyTopicMembershipsResponse, error) + // UploadSelfAvatar replaces the authenticated agent's avatar bytes + // (PNG/JPEG/WebP only; size/dimension limits enforced server-side). + // Returns updated Participant metadata including avatar_hash. + UploadSelfAvatar(context.Context, *UploadSelfAvatarRequest) (*Participant, error) + // DeleteSelfAvatar clears the authenticated agent's avatar. + DeleteSelfAvatar(context.Context, *DeleteSelfAvatarRequest) (*Participant, error) mustEmbedUnimplementedChatServiceServer() } @@ -306,6 +452,30 @@ func (UnimplementedChatServiceServer) Subscribe(*SubscribeRequest, grpc.ServerSt func (UnimplementedChatServiceServer) Acknowledge(context.Context, *AcknowledgeRequest) (*AcknowledgeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Acknowledge not implemented") } +func (UnimplementedChatServiceServer) GetParticipant(context.Context, *GetParticipantRequest) (*Participant, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetParticipant not implemented") +} +func (UnimplementedChatServiceServer) BatchGetParticipants(context.Context, *BatchGetParticipantsRequest) (*BatchGetParticipantsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BatchGetParticipants not implemented") +} +func (UnimplementedChatServiceServer) ListTopicParticipants(context.Context, *ListTopicParticipantsRequest) (*ListTopicParticipantsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListTopicParticipants not implemented") +} +func (UnimplementedChatServiceServer) GetMyParticipant(context.Context, *GetMyParticipantRequest) (*Participant, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetMyParticipant not implemented") +} +func (UnimplementedChatServiceServer) RegisterSelfProfile(context.Context, *RegisterSelfProfileRequest) (*Participant, error) { + return nil, status.Errorf(codes.Unimplemented, "method RegisterSelfProfile not implemented") +} +func (UnimplementedChatServiceServer) SyncMyTopicMemberships(context.Context, *SyncMyTopicMembershipsRequest) (*SyncMyTopicMembershipsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SyncMyTopicMemberships not implemented") +} +func (UnimplementedChatServiceServer) UploadSelfAvatar(context.Context, *UploadSelfAvatarRequest) (*Participant, error) { + return nil, status.Errorf(codes.Unimplemented, "method UploadSelfAvatar not implemented") +} +func (UnimplementedChatServiceServer) DeleteSelfAvatar(context.Context, *DeleteSelfAvatarRequest) (*Participant, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteSelfAvatar not implemented") +} func (UnimplementedChatServiceServer) mustEmbedUnimplementedChatServiceServer() {} func (UnimplementedChatServiceServer) testEmbeddedByValue() {} @@ -500,6 +670,150 @@ func _ChatService_Acknowledge_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _ChatService_GetParticipant_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetParticipantRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChatServiceServer).GetParticipant(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ChatService_GetParticipant_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChatServiceServer).GetParticipant(ctx, req.(*GetParticipantRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ChatService_BatchGetParticipants_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BatchGetParticipantsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChatServiceServer).BatchGetParticipants(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ChatService_BatchGetParticipants_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChatServiceServer).BatchGetParticipants(ctx, req.(*BatchGetParticipantsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ChatService_ListTopicParticipants_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListTopicParticipantsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChatServiceServer).ListTopicParticipants(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ChatService_ListTopicParticipants_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChatServiceServer).ListTopicParticipants(ctx, req.(*ListTopicParticipantsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ChatService_GetMyParticipant_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetMyParticipantRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChatServiceServer).GetMyParticipant(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ChatService_GetMyParticipant_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChatServiceServer).GetMyParticipant(ctx, req.(*GetMyParticipantRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ChatService_RegisterSelfProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RegisterSelfProfileRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChatServiceServer).RegisterSelfProfile(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ChatService_RegisterSelfProfile_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChatServiceServer).RegisterSelfProfile(ctx, req.(*RegisterSelfProfileRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ChatService_SyncMyTopicMemberships_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SyncMyTopicMembershipsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChatServiceServer).SyncMyTopicMemberships(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ChatService_SyncMyTopicMemberships_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChatServiceServer).SyncMyTopicMemberships(ctx, req.(*SyncMyTopicMembershipsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ChatService_UploadSelfAvatar_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UploadSelfAvatarRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChatServiceServer).UploadSelfAvatar(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ChatService_UploadSelfAvatar_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChatServiceServer).UploadSelfAvatar(ctx, req.(*UploadSelfAvatarRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ChatService_DeleteSelfAvatar_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteSelfAvatarRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChatServiceServer).DeleteSelfAvatar(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ChatService_DeleteSelfAvatar_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChatServiceServer).DeleteSelfAvatar(ctx, req.(*DeleteSelfAvatarRequest)) + } + return interceptor(ctx, in, info, handler) +} + // ChatService_ServiceDesc is the grpc.ServiceDesc for ChatService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -543,6 +857,38 @@ var ChatService_ServiceDesc = grpc.ServiceDesc{ MethodName: "Acknowledge", Handler: _ChatService_Acknowledge_Handler, }, + { + MethodName: "GetParticipant", + Handler: _ChatService_GetParticipant_Handler, + }, + { + MethodName: "BatchGetParticipants", + Handler: _ChatService_BatchGetParticipants_Handler, + }, + { + MethodName: "ListTopicParticipants", + Handler: _ChatService_ListTopicParticipants_Handler, + }, + { + MethodName: "GetMyParticipant", + Handler: _ChatService_GetMyParticipant_Handler, + }, + { + MethodName: "RegisterSelfProfile", + Handler: _ChatService_RegisterSelfProfile_Handler, + }, + { + MethodName: "SyncMyTopicMemberships", + Handler: _ChatService_SyncMyTopicMemberships_Handler, + }, + { + MethodName: "UploadSelfAvatar", + Handler: _ChatService_UploadSelfAvatar_Handler, + }, + { + MethodName: "DeleteSelfAvatar", + Handler: _ChatService_DeleteSelfAvatar_Handler, + }, }, Streams: []grpc.StreamDesc{ {