From 387140f0234823e42a4bd469cdca644aae85ab84 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Thu, 11 Jun 2026 13:03:20 +0200 Subject: [PATCH 1/2] Introduce a participant RPC protobuf registry for well-known LiveKit RPCs. --- .changeset/room-rpc-sip.md | 6 + livekit/roomrpc/siprpc/roomrpc_sip_v1.pb.go | 295 ++++++++++++++++++ magefile.go | 33 +- protobufs/roomrpc/README.md | 25 ++ protobufs/roomrpc/siprpc/roomrpc_sip_v1.proto | 48 +++ 5 files changed, 406 insertions(+), 1 deletion(-) create mode 100644 .changeset/room-rpc-sip.md create mode 100644 livekit/roomrpc/siprpc/roomrpc_sip_v1.pb.go create mode 100644 protobufs/roomrpc/README.md create mode 100644 protobufs/roomrpc/siprpc/roomrpc_sip_v1.proto diff --git a/.changeset/room-rpc-sip.md b/.changeset/room-rpc-sip.md new file mode 100644 index 000000000..bd9c2f5fe --- /dev/null +++ b/.changeset/room-rpc-sip.md @@ -0,0 +1,6 @@ +--- +"github.com/livekit/protocol": minor +"@livekit/protocol": minor +--- + +Introduce a participant RPC protobuf registry for well-known LiveKit RPCs. diff --git a/livekit/roomrpc/siprpc/roomrpc_sip_v1.pb.go b/livekit/roomrpc/siprpc/roomrpc_sip_v1.pb.go new file mode 100644 index 000000000..5d17bb4e9 --- /dev/null +++ b/livekit/roomrpc/siprpc/roomrpc_sip_v1.pb.go @@ -0,0 +1,295 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v7.35.0 +// source: roomrpc/siprpc/roomrpc_sip_v1.proto + +package siprpc + +import ( + _ "github.com/livekit/protocol/livekit/logger" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type GetRemoteHeadersV1Request struct { + state protoimpl.MessageState `protogen:"open.v1"` + Include []string `protobuf:"bytes,1,rep,name=include,proto3" json:"include,omitempty"` // list of headers to include + Exclude []string `protobuf:"bytes,2,rep,name=exclude,proto3" json:"exclude,omitempty"` // list of headers to exclude + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetRemoteHeadersV1Request) Reset() { + *x = GetRemoteHeadersV1Request{} + mi := &file_roomrpc_siprpc_roomrpc_sip_v1_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRemoteHeadersV1Request) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRemoteHeadersV1Request) ProtoMessage() {} + +func (x *GetRemoteHeadersV1Request) ProtoReflect() protoreflect.Message { + mi := &file_roomrpc_siprpc_roomrpc_sip_v1_proto_msgTypes[0] + 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 GetRemoteHeadersV1Request.ProtoReflect.Descriptor instead. +func (*GetRemoteHeadersV1Request) Descriptor() ([]byte, []int) { + return file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDescGZIP(), []int{0} +} + +func (x *GetRemoteHeadersV1Request) GetInclude() []string { + if x != nil { + return x.Include + } + return nil +} + +func (x *GetRemoteHeadersV1Request) GetExclude() []string { + if x != nil { + return x.Exclude + } + return nil +} + +type GetRemoteHeadersV1Response struct { + state protoimpl.MessageState `protogen:"open.v1"` + Headers map[string]string `protobuf:"bytes,1,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetRemoteHeadersV1Response) Reset() { + *x = GetRemoteHeadersV1Response{} + mi := &file_roomrpc_siprpc_roomrpc_sip_v1_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRemoteHeadersV1Response) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRemoteHeadersV1Response) ProtoMessage() {} + +func (x *GetRemoteHeadersV1Response) ProtoReflect() protoreflect.Message { + mi := &file_roomrpc_siprpc_roomrpc_sip_v1_proto_msgTypes[1] + 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 GetRemoteHeadersV1Response.ProtoReflect.Descriptor instead. +func (*GetRemoteHeadersV1Response) Descriptor() ([]byte, []int) { + return file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDescGZIP(), []int{1} +} + +func (x *GetRemoteHeadersV1Response) GetHeaders() map[string]string { + if x != nil { + return x.Headers + } + return nil +} + +type EndCallV1Request struct { + state protoimpl.MessageState `protogen:"open.v1"` + Headers map[string]string `protobuf:"bytes,1,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EndCallV1Request) Reset() { + *x = EndCallV1Request{} + mi := &file_roomrpc_siprpc_roomrpc_sip_v1_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EndCallV1Request) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EndCallV1Request) ProtoMessage() {} + +func (x *EndCallV1Request) ProtoReflect() protoreflect.Message { + mi := &file_roomrpc_siprpc_roomrpc_sip_v1_proto_msgTypes[2] + 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 EndCallV1Request.ProtoReflect.Descriptor instead. +func (*EndCallV1Request) Descriptor() ([]byte, []int) { + return file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDescGZIP(), []int{2} +} + +func (x *EndCallV1Request) GetHeaders() map[string]string { + if x != nil { + return x.Headers + } + return nil +} + +type EndCallV1Response struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EndCallV1Response) Reset() { + *x = EndCallV1Response{} + mi := &file_roomrpc_siprpc_roomrpc_sip_v1_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EndCallV1Response) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EndCallV1Response) ProtoMessage() {} + +func (x *EndCallV1Response) ProtoReflect() protoreflect.Message { + mi := &file_roomrpc_siprpc_roomrpc_sip_v1_proto_msgTypes[3] + 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 EndCallV1Response.ProtoReflect.Descriptor instead. +func (*EndCallV1Response) Descriptor() ([]byte, []int) { + return file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDescGZIP(), []int{3} +} + +var File_roomrpc_siprpc_roomrpc_sip_v1_proto protoreflect.FileDescriptor + +const file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDesc = "" + + "\n" + + "#roomrpc/siprpc/roomrpc_sip_v1.proto\x12\alivekit\x1a\x14logger/options.proto\"O\n" + + "\x19GetRemoteHeadersV1Request\x12\x18\n" + + "\ainclude\x18\x01 \x03(\tR\ainclude\x12\x18\n" + + "\aexclude\x18\x02 \x03(\tR\aexclude\"\xca\x01\n" + + "\x1aGetRemoteHeadersV1Response\x12p\n" + + "\aheaders\x18\x01 \x03(\v20.livekit.GetRemoteHeadersV1Response.HeadersEntryB$\xb2P\x1e\xc0P\x01R\aheaders\x1a:\n" + + "\fHeadersEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb6\x01\n" + + "\x10EndCallV1Request\x12f\n" + + "\aheaders\x18\x01 \x03(\v2&.livekit.EndCallV1Request.HeadersEntryB$\xb2P\x1e\xc0P\x01R\aheaders\x1a:\n" + + "\fHeadersEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x13\n" + + "\x11EndCallV1Response2\xb1\x01\n" + + "\x10SIPParticipantV1\x12[\n" + + "\x10GetRemoteHeaders\x12\".livekit.GetRemoteHeadersV1Request\x1a#.livekit.GetRemoteHeadersV1Response\x12@\n" + + "\aEndCall\x12\x19.livekit.EndCallV1Request\x1a\x1a.livekit.EndCallV1ResponseBnZ2github.com/livekit/protocol/livekit/roomrpc/siprpc\xaa\x02\x19LiveKit.Proto.RoomRPC.SIP\xea\x02\x1bLiveKit::Proto::RoomRPC:SIPb\x06proto3" + +var ( + file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDescOnce sync.Once + file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDescData []byte +) + +func file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDescGZIP() []byte { + file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDescOnce.Do(func() { + file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDesc), len(file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDesc))) + }) + return file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDescData +} + +var file_roomrpc_siprpc_roomrpc_sip_v1_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_roomrpc_siprpc_roomrpc_sip_v1_proto_goTypes = []any{ + (*GetRemoteHeadersV1Request)(nil), // 0: livekit.GetRemoteHeadersV1Request + (*GetRemoteHeadersV1Response)(nil), // 1: livekit.GetRemoteHeadersV1Response + (*EndCallV1Request)(nil), // 2: livekit.EndCallV1Request + (*EndCallV1Response)(nil), // 3: livekit.EndCallV1Response + nil, // 4: livekit.GetRemoteHeadersV1Response.HeadersEntry + nil, // 5: livekit.EndCallV1Request.HeadersEntry +} +var file_roomrpc_siprpc_roomrpc_sip_v1_proto_depIdxs = []int32{ + 4, // 0: livekit.GetRemoteHeadersV1Response.headers:type_name -> livekit.GetRemoteHeadersV1Response.HeadersEntry + 5, // 1: livekit.EndCallV1Request.headers:type_name -> livekit.EndCallV1Request.HeadersEntry + 0, // 2: livekit.SIPParticipantV1.GetRemoteHeaders:input_type -> livekit.GetRemoteHeadersV1Request + 2, // 3: livekit.SIPParticipantV1.EndCall:input_type -> livekit.EndCallV1Request + 1, // 4: livekit.SIPParticipantV1.GetRemoteHeaders:output_type -> livekit.GetRemoteHeadersV1Response + 3, // 5: livekit.SIPParticipantV1.EndCall:output_type -> livekit.EndCallV1Response + 4, // [4:6] is the sub-list for method output_type + 2, // [2:4] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_roomrpc_siprpc_roomrpc_sip_v1_proto_init() } +func file_roomrpc_siprpc_roomrpc_sip_v1_proto_init() { + if File_roomrpc_siprpc_roomrpc_sip_v1_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDesc), len(file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDesc)), + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_roomrpc_siprpc_roomrpc_sip_v1_proto_goTypes, + DependencyIndexes: file_roomrpc_siprpc_roomrpc_sip_v1_proto_depIdxs, + MessageInfos: file_roomrpc_siprpc_roomrpc_sip_v1_proto_msgTypes, + }.Build() + File_roomrpc_siprpc_roomrpc_sip_v1_proto = out.File + file_roomrpc_siprpc_roomrpc_sip_v1_proto_goTypes = nil + file_roomrpc_siprpc_roomrpc_sip_v1_proto_depIdxs = nil +} diff --git a/magefile.go b/magefile.go index 3fdeda1ea..087bbf623 100644 --- a/magefile.go +++ b/magefile.go @@ -94,8 +94,16 @@ func Proto() error { "rpc/sip.proto", } + // mapped proto directory: + // ./protobufs/roomrpc/rpc + // and generated Go package: + // ./livekit/roomrpc/rpc + roomrpcTypeNames := []string{ + "sip", + } + fmt.Println("generating protobuf") - target := "livekit" + const target = "./livekit" if err := os.MkdirAll(target, 0755); err != nil { return err } @@ -162,6 +170,29 @@ func Proto() error { } } + fmt.Println("generating protobuf (livekit/roomrpc)") + for _, protoName := range roomrpcTypeNames { + pkgName := "roomrpc/" + protoName + "rpc" + protoFiles, err := os.ReadDir(filepath.Join("./protobufs", pkgName)) + if err != nil { + return err + } + args := []string{ + "--go_out", target, + "--go_opt=paths=source_relative", + "--plugin=go=" + protocGoPath, + "-I=./protobufs", + } + for _, protoFile := range protoFiles { + args = append(args, filepath.Join(pkgName, protoFile.Name())) + } + cmd := exec.Command(protoc, args...) + connectStd(cmd) + if err := cmd.Run(); err != nil { + return err + } + } + fmt.Println("generating grpc protobuf") args = append([]string{ "--go_out", ".", diff --git a/protobufs/roomrpc/README.md b/protobufs/roomrpc/README.md new file mode 100644 index 000000000..9ffe7816f --- /dev/null +++ b/protobufs/roomrpc/README.md @@ -0,0 +1,25 @@ +# LiveKit participant RPCs registry (roomrpc) + +These protos describe a list of well-known [LiveKit participant RPCs](https://docs.livekit.io/transport/data/rpc/) +and corresponding request/response messages. + +## Directory structure + +Each participant type or API should have its own directory under `roomrpc` with the name `rpc` +to avoid Go package name collisions and isolate types for each API. + +## Versioning + +Proto files should follow the name convention: `roomrpc__v.proto`. Version is a single sequential number, not a semver. +For example: `roomrpc_sip_v1.proto`. + +Corresponding `V` should be added as a suffix to the service definition and the request/response types +(method name should omit the version, as the versioned service name is part of the method path). + +```protobuf +service MyServiceV1 { + rpc DoSomething(DoSomethingV1Request) returns (DoSomethingV1Response); +} +message DoSomethingV1Request {} +message DoSomethingV1Response {} +``` \ No newline at end of file diff --git a/protobufs/roomrpc/siprpc/roomrpc_sip_v1.proto b/protobufs/roomrpc/siprpc/roomrpc_sip_v1.proto new file mode 100644 index 000000000..607c50609 --- /dev/null +++ b/protobufs/roomrpc/siprpc/roomrpc_sip_v1.proto @@ -0,0 +1,48 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package livekit; +option go_package = "github.com/livekit/protocol/livekit/roomrpc/siprpc"; +option csharp_namespace = "LiveKit.Proto.RoomRPC.SIP"; +option ruby_package = "LiveKit::Proto::RoomRPC::SIP"; + +import "logger/options.proto"; + +service SIPParticipantV1 { + // RPC: lk.sip.GetRemoteHeaders + rpc GetRemoteHeaders(GetRemoteHeadersV1Request) returns (GetRemoteHeadersV1Response); + // RPC: lk.sip.EndCall + rpc EndCall(EndCallV1Request) returns (EndCallV1Response); +} + +message GetRemoteHeadersV1Request { + repeated string include = 1; // list of headers to include + repeated string exclude = 2; // list of headers to exclude +} +message GetRemoteHeadersV1Response { + map headers = 1 [ + (logger.sensitivity) = SENSITIVITY_PII, + (logger.redact_format) = "" + ]; +} + +message EndCallV1Request { + map headers = 1 [ + (logger.sensitivity) = SENSITIVITY_PII, + (logger.redact_format) = "" + ]; +} +message EndCallV1Response {} From e52cd2aa99711ea15deca48114691d15225ed83c Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 13:56:57 +0000 Subject: [PATCH 2/2] generated protobuf --- livekit/roomrpc/siprpc/roomrpc_sip_v1.pb.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/livekit/roomrpc/siprpc/roomrpc_sip_v1.pb.go b/livekit/roomrpc/siprpc/roomrpc_sip_v1.pb.go index 5d17bb4e9..81c848c26 100644 --- a/livekit/roomrpc/siprpc/roomrpc_sip_v1.pb.go +++ b/livekit/roomrpc/siprpc/roomrpc_sip_v1.pb.go @@ -15,7 +15,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v7.35.0 +// protoc v7.35.1 // source: roomrpc/siprpc/roomrpc_sip_v1.proto package siprpc @@ -233,7 +233,7 @@ const file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDesc = "" + "\x11EndCallV1Response2\xb1\x01\n" + "\x10SIPParticipantV1\x12[\n" + "\x10GetRemoteHeaders\x12\".livekit.GetRemoteHeadersV1Request\x1a#.livekit.GetRemoteHeadersV1Response\x12@\n" + - "\aEndCall\x12\x19.livekit.EndCallV1Request\x1a\x1a.livekit.EndCallV1ResponseBnZ2github.com/livekit/protocol/livekit/roomrpc/siprpc\xaa\x02\x19LiveKit.Proto.RoomRPC.SIP\xea\x02\x1bLiveKit::Proto::RoomRPC:SIPb\x06proto3" + "\aEndCall\x12\x19.livekit.EndCallV1Request\x1a\x1a.livekit.EndCallV1ResponseBoZ2github.com/livekit/protocol/livekit/roomrpc/siprpc\xaa\x02\x19LiveKit.Proto.RoomRPC.SIP\xea\x02\x1cLiveKit::Proto::RoomRPC::SIPb\x06proto3" var ( file_roomrpc_siprpc_roomrpc_sip_v1_proto_rawDescOnce sync.Once