From 641bae4c29ebe223f2554b6d1e19d4a75fefca8f Mon Sep 17 00:00:00 2001 From: Paul Wells Date: Mon, 15 Sep 2025 21:31:36 -0700 Subject: [PATCH] add recording store request header (#1197) * add recording store request header * generated protobuf * wip * tidy * generated protobuf --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- auth/grants.go | 45 ++++++++++++++---- livekit/livekit_metrics.pb.go | 84 ++++++++++++++++++++++++++++----- protobufs/livekit_metrics.proto | 7 ++- utils/guid/id.go | 7 +++ utils/guid/id_test.go | 9 ++++ 5 files changed, 130 insertions(+), 22 deletions(-) diff --git a/auth/grants.go b/auth/grants.go index b1b4cd0..8567f09 100644 --- a/auth/grants.go +++ b/auth/grants.go @@ -162,13 +162,14 @@ func checkOutputForCredentials(output any) error { } type ClaimGrants struct { - Identity string `json:"identity,omitempty"` - Name string `json:"name,omitempty"` - Kind string `json:"kind,omitempty"` - Video *VideoGrant `json:"video,omitempty"` - SIP *SIPGrant `json:"sip,omitempty"` - Agent *AgentGrant `json:"agent,omitempty"` - Inference *InferenceGrant `json:"inference,omitempty"` + Identity string `json:"identity,omitempty"` + Name string `json:"name,omitempty"` + Kind string `json:"kind,omitempty"` + Video *VideoGrant `json:"video,omitempty"` + SIP *SIPGrant `json:"sip,omitempty"` + Agent *AgentGrant `json:"agent,omitempty"` + Inference *InferenceGrant `json:"inference,omitempty"` + Observability *ObservabilityGrant `json:"observability,omitempty"` // Room configuration to use if this participant initiates the room RoomConfig *RoomConfiguration `json:"roomConfig,omitempty"` // Cloud-only, config preset to use @@ -206,6 +207,7 @@ func (c *ClaimGrants) Clone() *ClaimGrants { clone.SIP = c.SIP.Clone() clone.Agent = c.Agent.Clone() clone.Inference = c.Inference.Clone() + clone.Observability = c.Observability.Clone() clone.Attributes = maps.Clone(c.Attributes) clone.RoomConfig = c.RoomConfig.Clone() @@ -223,6 +225,7 @@ func (c *ClaimGrants) MarshalLogObject(e zapcore.ObjectEncoder) error { e.AddObject("SIP", c.SIP) e.AddObject("Agent", c.Agent) e.AddObject("Inference", c.Inference) + e.AddObject("Observability", c.Observability) e.AddObject("RoomConfig", logger.Proto((*livekit.RoomConfiguration)(c.RoomConfig))) e.AddString("RoomPreset", c.RoomPreset) return nil @@ -560,7 +563,7 @@ func (s *AgentGrant) MarshalLogObject(e zapcore.ObjectEncoder) error { // ------------------------------------------------------------------ type InferenceGrant struct { - // Admin grants to all inference features (LLM, STT, TTS) + // Perform grants to all inference features (LLM, STT, TTS) Perform bool `json:"perform,omitempty"` } @@ -585,6 +588,32 @@ func (s *InferenceGrant) MarshalLogObject(e zapcore.ObjectEncoder) error { // ------------------------------------------------------------------ +type ObservabilityGrant struct { + // Write grants to publish observability data + Write bool `json:"write,omitempty"` +} + +func (s *ObservabilityGrant) Clone() *ObservabilityGrant { + if s == nil { + return nil + } + + clone := *s + + return &clone +} + +func (s *ObservabilityGrant) MarshalLogObject(e zapcore.ObjectEncoder) error { + if s == nil { + return nil + } + + e.AddBool("Write", s.Write) + return nil +} + +// ------------------------------------------------------------------ + func sourceToString(source livekit.TrackSource) string { return strings.ToLower(source.String()) } diff --git a/livekit/livekit_metrics.pb.go b/livekit/livekit_metrics.pb.go index 98f9cc1..2bc367d 100644 --- a/livekit/livekit_metrics.pb.go +++ b/livekit/livekit_metrics.pb.go @@ -454,6 +454,58 @@ func (x *EventMetric) GetRid() uint32 { return 0 } +type MetricsRecordingHeader struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoomId string `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + EnableUserDataTraining *bool `protobuf:"varint,2,opt,name=enable_user_data_training,json=enableUserDataTraining,proto3,oneof" json:"enable_user_data_training,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MetricsRecordingHeader) Reset() { + *x = MetricsRecordingHeader{} + mi := &file_livekit_metrics_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MetricsRecordingHeader) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MetricsRecordingHeader) ProtoMessage() {} + +func (x *MetricsRecordingHeader) ProtoReflect() protoreflect.Message { + mi := &file_livekit_metrics_proto_msgTypes[4] + 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 MetricsRecordingHeader.ProtoReflect.Descriptor instead. +func (*MetricsRecordingHeader) Descriptor() ([]byte, []int) { + return file_livekit_metrics_proto_rawDescGZIP(), []int{4} +} + +func (x *MetricsRecordingHeader) GetRoomId() string { + if x != nil { + return x.RoomId + } + return "" +} + +func (x *MetricsRecordingHeader) GetEnableUserDataTraining() bool { + if x != nil && x.EnableUserDataTraining != nil { + return *x.EnableUserDataTraining + } + return false +} + var File_livekit_metrics_proto protoreflect.FileDescriptor const file_livekit_metrics_proto_rawDesc = "" + @@ -487,7 +539,11 @@ const file_livekit_metrics_proto_rawDesc = "" + "\bmetadata\x18\b \x01(\tR\bmetadata\x12\x10\n" + "\x03rid\x18\t \x01(\rR\x03ridB\x13\n" + "\x11_end_timestamp_msB\x1b\n" + - "\x19_normalized_end_timestamp*\x81\a\n" + + "\x19_normalized_end_timestamp\"\x8f\x01\n" + + "\x16MetricsRecordingHeader\x12\x17\n" + + "\aroom_id\x18\x01 \x01(\tR\x06roomId\x12>\n" + + "\x19enable_user_data_training\x18\x02 \x01(\bH\x00R\x16enableUserDataTraining\x88\x01\x01B\x1c\n" + + "\x1a_enable_user_data_training*\x81\a\n" + "\vMetricLabel\x12\x13\n" + "\x0fAGENTS_LLM_TTFT\x10\x00\x12\x13\n" + "\x0fAGENTS_STT_TTFT\x10\x01\x12\x13\n" + @@ -525,23 +581,24 @@ func file_livekit_metrics_proto_rawDescGZIP() []byte { } var file_livekit_metrics_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_livekit_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_livekit_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_livekit_metrics_proto_goTypes = []any{ - (MetricLabel)(0), // 0: livekit.MetricLabel - (*MetricsBatch)(nil), // 1: livekit.MetricsBatch - (*TimeSeriesMetric)(nil), // 2: livekit.TimeSeriesMetric - (*MetricSample)(nil), // 3: livekit.MetricSample - (*EventMetric)(nil), // 4: livekit.EventMetric - (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp + (MetricLabel)(0), // 0: livekit.MetricLabel + (*MetricsBatch)(nil), // 1: livekit.MetricsBatch + (*TimeSeriesMetric)(nil), // 2: livekit.TimeSeriesMetric + (*MetricSample)(nil), // 3: livekit.MetricSample + (*EventMetric)(nil), // 4: livekit.EventMetric + (*MetricsRecordingHeader)(nil), // 5: livekit.MetricsRecordingHeader + (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp } var file_livekit_metrics_proto_depIdxs = []int32{ - 5, // 0: livekit.MetricsBatch.normalized_timestamp:type_name -> google.protobuf.Timestamp + 6, // 0: livekit.MetricsBatch.normalized_timestamp:type_name -> google.protobuf.Timestamp 2, // 1: livekit.MetricsBatch.time_series:type_name -> livekit.TimeSeriesMetric 4, // 2: livekit.MetricsBatch.events:type_name -> livekit.EventMetric 3, // 3: livekit.TimeSeriesMetric.samples:type_name -> livekit.MetricSample - 5, // 4: livekit.MetricSample.normalized_timestamp:type_name -> google.protobuf.Timestamp - 5, // 5: livekit.EventMetric.normalized_start_timestamp:type_name -> google.protobuf.Timestamp - 5, // 6: livekit.EventMetric.normalized_end_timestamp:type_name -> google.protobuf.Timestamp + 6, // 4: livekit.MetricSample.normalized_timestamp:type_name -> google.protobuf.Timestamp + 6, // 5: livekit.EventMetric.normalized_start_timestamp:type_name -> google.protobuf.Timestamp + 6, // 6: livekit.EventMetric.normalized_end_timestamp:type_name -> google.protobuf.Timestamp 7, // [7:7] is the sub-list for method output_type 7, // [7:7] is the sub-list for method input_type 7, // [7:7] is the sub-list for extension type_name @@ -555,13 +612,14 @@ func file_livekit_metrics_proto_init() { return } file_livekit_metrics_proto_msgTypes[3].OneofWrappers = []any{} + file_livekit_metrics_proto_msgTypes[4].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_livekit_metrics_proto_rawDesc), len(file_livekit_metrics_proto_rawDesc)), NumEnums: 1, - NumMessages: 4, + NumMessages: 5, NumExtensions: 0, NumServices: 0, }, diff --git a/protobufs/livekit_metrics.proto b/protobufs/livekit_metrics.proto index 6ac4975..e6896a4 100644 --- a/protobufs/livekit_metrics.proto +++ b/protobufs/livekit_metrics.proto @@ -53,7 +53,7 @@ message MetricsBatch { // This is useful for storing participant identities, track names, etc. // There is also a predefined list of labels that can be used to reference common metrics. // They have reserved indices from 0 to (METRIC_LABEL_PREDEFINED_MAX_VALUE - 1). - // Indexes pointing at str_data should start from METRIC_LABEL_PREDEFINED_MAX_VALUE, + // Indexes pointing at str_data should start from METRIC_LABEL_PREDEFINED_MAX_VALUE, // such that str_data[0] == index of METRIC_LABEL_PREDEFINED_MAX_VALUE. repeated string str_data = 3; repeated TimeSeriesMetric time_series = 4; @@ -87,3 +87,8 @@ message EventMetric { string metadata = 8; uint32 rid = 9; // index into 'str_data' } + +message MetricsRecordingHeader { + string room_id = 1; + optional bool enable_user_data_training = 2; +} diff --git a/utils/guid/id.go b/utils/guid/id.go index 046cc32..4c6c078 100644 --- a/utils/guid/id.go +++ b/utils/guid/id.go @@ -21,6 +21,7 @@ import ( "fmt" mrand "math/rand/v2" "os" + "regexp" "sync" "unsafe" @@ -199,3 +200,9 @@ func Unmarshal[T livekit.Guid](b livekit.GuidBlock) T { } return T(unsafe.String(unsafe.SliceData(id), len(id))) } + +var validIDPattern = regexp.MustCompile(`^([a-zA-Z0-9]{1,16}_){1,2}[a-zA-Z0-9]{0,12}$`) + +func IsValidID[T ~string](id T) bool { + return validIDPattern.MatchString(string(id)) +} diff --git a/utils/guid/id_test.go b/utils/guid/id_test.go index c322e3f..e80b932 100644 --- a/utils/guid/id_test.go +++ b/utils/guid/id_test.go @@ -55,3 +55,12 @@ func BenchmarkNew(b *testing.B) { _ = guid }) } + +func TestIsValidID(t *testing.T) { + require.True(t, IsValidID("A_SFo4igEG5Dg5")) + require.True(t, IsValidID("NM_OJOHANNESBURG1A_K6SMQw2ZCZyB")) + require.False(t, IsValidID("A_A_A_SFo4igEG5Dg5")) + require.False(t, IsValidID("_A_SFo4igEG5Dg5")) + require.False(t, IsValidID("_SFo4igEG5Dg5")) + require.False(t, IsValidID("SFo4igEG5Dg5")) +}