diff --git a/Makefile b/Makefile
index 7885775e8a4b394adf66a1fbd6830eab495528ec..9da3c2b3fabf48a3bb8659a6c39bf9f04e4e5e14 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@ SHELL = bash
 
 PROTODIR=perxis-proto/proto
 DSTDIR=./proto
-ALLPROTO?=$(shell find $(PROTODIR) -name '*.proto' )
+ALLPROTO?=$(shell find -L $(PROTODIR) -name '*.proto' )
 # Убираем status.proto нужен только для front
 PROTOFILES=	$(filter-out $(PROTODIR)/status/status.proto, $(ALLPROTO))
 PROTOGOFILES=$(PROTOFILES:.proto=.pb.go)
@@ -19,7 +19,10 @@ SERVICERECOVERING=$(shell find $(PKGDIR) -name "recovering_middleware.go" -type
 
 # Генерация grpc-клиентов для go
 proto: protoc-check protoc-gen-go-check $(PROTOGOFILES)
-	@echo "Generated all protobuf Go files"
+	echo "Generated all protobuf Go files"
+	echo $(ALLPROTO)
+	echo $(PROTODIR)
+	find $(PROTODIR) -name '*.proto'
 
 %.pb.go: %.proto
 	@protoc -I=$(PROTODIR) --experimental_allow_proto3_optional --go_out=$(DSTDIR) --go-grpc_out=$(DSTDIR) --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative "$<"
@@ -40,8 +43,8 @@ endif
 protoc-gen-go-check:
 ifeq (,$(wildcard $(GOPATH)/bin/protoc-gen-go))
 	$(error "Protocol Buffers Go plugin not found. \
-	Run \"go get -u google.golang.org/protobuf/cmd/protoc-gen-go google.golang.org/grpc/cmd/protoc-gen-go-grpc\" \
-	or visit \"https://github.com/golang/protobuf/tree/v1.3.2#installation\" for more.\n")
+	Run \"go install google.golang.org/protobuf/cmd/protoc-gen-go@latest google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest\" \
+	or visit \"https://grpc.io/docs/languages/go/quickstart/\" for more.\n")
 endif
 
 # Генерация логгирования (access & error) для всех сервисов. Предполагается наличие файлов `logging_middleware.go/error_middleware.go`
diff --git a/pkg/operation/operation.go b/pkg/operation/operation.go
new file mode 100644
index 0000000000000000000000000000000000000000..11e17ca1b6e05254b58fba732d96ea392bb5a34d
--- /dev/null
+++ b/pkg/operation/operation.go
@@ -0,0 +1,117 @@
+package operation
+
+import (
+	"context"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+
+	"git.perx.ru/perxis/perxis-go/proto/common"
+	"google.golang.org/grpc"
+	"google.golang.org/protobuf/proto"
+)
+
+const DefaultPollInterval = time.Second
+
+type Client = common.OperationServiceClient
+type Proto = common.Operation
+
+type Operation struct {
+	proto    *Proto
+	client   Client
+	newTimer func(time.Duration) (func() <-chan time.Time, func() bool)
+}
+
+func NewOperation(client Client, proto *Proto) *Operation {
+	return &Operation{
+		proto:  proto,
+		client: client,
+	}
+}
+
+func NewRemoteOperation(client Client, proto *Proto) *Operation {
+	return &Operation{
+		proto:  proto,
+		client: client,
+	}
+}
+
+func (o *Operation) Id() string {
+	return o.proto.GetError()
+}
+
+func (o *Operation) Description() string {
+	return o.proto.GetDescription()
+}
+
+func (o *Operation) CreatedAt() time.Time {
+	c := o.proto.GetCreatedAt()
+	if c != nil {
+		return c.AsTime()
+	}
+	return time.Time{}
+}
+
+func (o *Operation) IsDone() bool {
+	return o.proto.GetDone()
+}
+
+func (o *Operation) Response() (proto.Message, error) {
+	r := o.proto.GetResponse()
+	if r == nil {
+		return nil, nil
+	}
+	return r.UnmarshalNew()
+}
+
+func (o *Operation) Error() error {
+	if errStr := o.proto.GetError(); errStr != "" {
+		return errors.New(errStr)
+	}
+	return nil
+}
+
+func (o *Operation) Cancel() error {
+	req := &common.CancelOperationRequest{OperationId: o.Id()}
+	_, err := o.client.Cancel(context.Background(), req)
+	return err
+}
+
+func (o *Operation) Poll(ctx context.Context, opts ...grpc.CallOption) error {
+	req := &common.GetOperationRequest{OperationId: o.Id()}
+	op, err := o.client.Get(ctx, req, opts...)
+	if err != nil {
+		return err
+	}
+	o.proto = op
+	return nil
+}
+
+func (o *Operation) Wait(ctx context.Context, opts ...grpc.CallOption) error {
+	return o.waitInterval(ctx, DefaultPollInterval, opts...)
+}
+
+func (o *Operation) waitInterval(ctx context.Context, pollInterval time.Duration, opts ...grpc.CallOption) error {
+	for !o.IsDone() {
+		err := o.Poll(ctx, opts...)
+
+		if err != nil {
+			return errors.Wrap(err, "operation poll fail")
+		}
+
+		if o.IsDone() {
+			break
+		}
+
+		interval := pollInterval
+		wait, stop := o.newTimer(interval)
+		select {
+		case <-wait():
+		case <-ctx.Done():
+			stop()
+			return errors.Wrap(ctx.Err(), "operation wait timeout")
+		}
+	}
+
+	return errors.Wrap(o.GetError(), "operation failed")
+}
diff --git a/pkg/operation/service.go b/pkg/operation/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..1b89c3289fe4726d69bd0b9f2c05744d91d9db86
--- /dev/null
+++ b/pkg/operation/service.go
@@ -0,0 +1,70 @@
+package operation
+
+import (
+	"sync"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+)
+
+type Service interface {
+	Get(id string) (*Operation, error)
+	Cancel(id string) (*Operation, error)
+}
+
+type OperationService struct {
+	ops       map[string]*Operation
+	mu        sync.RWMutex
+	retention time.Duration
+	cleanup	time.Duration
+}
+
+func NewService() *OperationService {
+	return &OperationService{
+		ops: make(map[string]*Operation),
+		mu:  sync.RWMutex{},
+	},
+}
+
+func (s *OperationService) Cleanup() {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	for id, op := range s.ops {
+		createdAt := op.CreatedAt()
+		if createdAt.IsZero() || time.Since(createdAt) > s.retention {
+			delete(s.ops, id)
+		}
+	}
+}
+
+func (s *OperationService) Set(op *Operation) error {
+	if op == nil || op.Id() == "" {
+		return errors.New("invalid operation")
+	}
+
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	s.ops[op.Id()] = op
+	return nil
+}
+
+func (s *OperationService) Get(id string) (*Operation, error) {
+	s.mu.RLock()
+	defer s.mu.RUnlock()
+	op, ok := s.ops[id]
+	if !ok {
+		return nil, errors.New("operation not found")
+	}
+	return op, nil
+}
+
+func (s *OperationService) Cancel(id string) (*Operation, error) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	op, ok := s.ops[id]
+	if !ok {
+		return nil, errors.New("operation not found")
+	}
+	op.Done = true
+	return op, nil
+}
diff --git a/proto/common/operation.pb.go b/proto/common/operation.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..b4d7d242694c1002dac7794cbdf7ce78edf3224c
--- /dev/null
+++ b/proto/common/operation.pb.go
@@ -0,0 +1,283 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.31.0
+// 	protoc        v4.24.3
+// source: common/operation.proto
+
+package common
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	anypb "google.golang.org/protobuf/types/known/anypb"
+	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
+	reflect "reflect"
+	sync "sync"
+)
+
+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 Operation struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// ID операции
+	Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+	// Описание операции
+	Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
+	// Время создания операции
+	CreatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
+	// Создатель операции
+	CreatedBy string `protobuf:"bytes,4,opt,name=created_by,json=createdBy,proto3" json:"created_by,omitempty"`
+	// Время последнего изменения операции
+	ModifiedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=modified_at,json=modifiedAt,proto3" json:"modified_at,omitempty"`
+	// Операция завершена
+	Done bool `protobuf:"varint,7,opt,name=done,proto3" json:"done,omitempty"`
+	// Метаданные операции
+	Metadata *anypb.Any `protobuf:"bytes,8,opt,name=metadata,proto3" json:"metadata,omitempty"`
+	// Результат выполнения операции
+	//
+	// Types that are assignable to Result:
+	//
+	//	*Operation_Response
+	//	*Operation_Error
+	Result isOperation_Result `protobuf_oneof:"result"`
+}
+
+func (x *Operation) Reset() {
+	*x = Operation{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_common_operation_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Operation) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Operation) ProtoMessage() {}
+
+func (x *Operation) ProtoReflect() protoreflect.Message {
+	mi := &file_common_operation_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Operation.ProtoReflect.Descriptor instead.
+func (*Operation) Descriptor() ([]byte, []int) {
+	return file_common_operation_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Operation) GetId() string {
+	if x != nil {
+		return x.Id
+	}
+	return ""
+}
+
+func (x *Operation) GetDescription() string {
+	if x != nil {
+		return x.Description
+	}
+	return ""
+}
+
+func (x *Operation) GetCreatedAt() *timestamppb.Timestamp {
+	if x != nil {
+		return x.CreatedAt
+	}
+	return nil
+}
+
+func (x *Operation) GetCreatedBy() string {
+	if x != nil {
+		return x.CreatedBy
+	}
+	return ""
+}
+
+func (x *Operation) GetModifiedAt() *timestamppb.Timestamp {
+	if x != nil {
+		return x.ModifiedAt
+	}
+	return nil
+}
+
+func (x *Operation) GetDone() bool {
+	if x != nil {
+		return x.Done
+	}
+	return false
+}
+
+func (x *Operation) GetMetadata() *anypb.Any {
+	if x != nil {
+		return x.Metadata
+	}
+	return nil
+}
+
+func (m *Operation) GetResult() isOperation_Result {
+	if m != nil {
+		return m.Result
+	}
+	return nil
+}
+
+func (x *Operation) GetResponse() *anypb.Any {
+	if x, ok := x.GetResult().(*Operation_Response); ok {
+		return x.Response
+	}
+	return nil
+}
+
+func (x *Operation) GetError() string {
+	if x, ok := x.GetResult().(*Operation_Error); ok {
+		return x.Error
+	}
+	return ""
+}
+
+type isOperation_Result interface {
+	isOperation_Result()
+}
+
+type Operation_Response struct {
+	// Результат выполнения операции в случае успеха
+	Response *anypb.Any `protobuf:"bytes,9,opt,name=response,proto3,oneof"`
+}
+
+type Operation_Error struct {
+	// Результат выполнения операции в случае ошибки
+	Error string `protobuf:"bytes,10,opt,name=error,proto3,oneof"`
+}
+
+func (*Operation_Response) isOperation_Result() {}
+
+func (*Operation_Error) isOperation_Result() {}
+
+var File_common_operation_proto protoreflect.FileDescriptor
+
+var file_common_operation_proto_rawDesc = []byte{
+	0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
+	0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
+	0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
+	0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf0, 0x02, 0x0a,
+	0x09, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65,
+	0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x0a,
+	0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
+	0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72,
+	0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74,
+	0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65,
+	0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x3b, 0x0a, 0x0b, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69,
+	0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
+	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
+	0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65,
+	0x64, 0x41, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28,
+	0x08, 0x52, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x12, 0x30, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64,
+	0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
+	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52,
+	0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x32, 0x0a, 0x08, 0x72, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f,
+	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e,
+	0x79, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a,
+	0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05,
+	0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x08, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x42,
+	0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x2e, 0x70, 0x65, 0x72, 0x78, 0x2e, 0x72, 0x75, 0x2f, 0x70,
+	0x65, 0x72, 0x78, 0x69, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x78, 0x69, 0x73, 0x2d, 0x67, 0x6f, 0x2f,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x3b, 0x63, 0x6f, 0x6d,
+	0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_common_operation_proto_rawDescOnce sync.Once
+	file_common_operation_proto_rawDescData = file_common_operation_proto_rawDesc
+)
+
+func file_common_operation_proto_rawDescGZIP() []byte {
+	file_common_operation_proto_rawDescOnce.Do(func() {
+		file_common_operation_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_operation_proto_rawDescData)
+	})
+	return file_common_operation_proto_rawDescData
+}
+
+var file_common_operation_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_common_operation_proto_goTypes = []interface{}{
+	(*Operation)(nil),             // 0: common.Operation
+	(*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp
+	(*anypb.Any)(nil),             // 2: google.protobuf.Any
+}
+var file_common_operation_proto_depIdxs = []int32{
+	1, // 0: common.Operation.created_at:type_name -> google.protobuf.Timestamp
+	1, // 1: common.Operation.modified_at:type_name -> google.protobuf.Timestamp
+	2, // 2: common.Operation.metadata:type_name -> google.protobuf.Any
+	2, // 3: common.Operation.response:type_name -> google.protobuf.Any
+	4, // [4:4] is the sub-list for method output_type
+	4, // [4:4] is the sub-list for method input_type
+	4, // [4:4] is the sub-list for extension type_name
+	4, // [4:4] is the sub-list for extension extendee
+	0, // [0:4] is the sub-list for field type_name
+}
+
+func init() { file_common_operation_proto_init() }
+func file_common_operation_proto_init() {
+	if File_common_operation_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_common_operation_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Operation); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	file_common_operation_proto_msgTypes[0].OneofWrappers = []interface{}{
+		(*Operation_Response)(nil),
+		(*Operation_Error)(nil),
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_common_operation_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_common_operation_proto_goTypes,
+		DependencyIndexes: file_common_operation_proto_depIdxs,
+		MessageInfos:      file_common_operation_proto_msgTypes,
+	}.Build()
+	File_common_operation_proto = out.File
+	file_common_operation_proto_rawDesc = nil
+	file_common_operation_proto_goTypes = nil
+	file_common_operation_proto_depIdxs = nil
+}
diff --git a/proto/common/operation_service.pb.go b/proto/common/operation_service.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..93876246a4f69845688755a3a88bfde6330cbcd7
--- /dev/null
+++ b/proto/common/operation_service.pb.go
@@ -0,0 +1,230 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.31.0
+// 	protoc        v4.24.3
+// source: common/operation_service.proto
+
+package common
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+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 GetOperationRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	OperationId string `protobuf:"bytes,1,opt,name=operation_id,json=operationId,proto3" json:"operation_id,omitempty"`
+}
+
+func (x *GetOperationRequest) Reset() {
+	*x = GetOperationRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_common_operation_service_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GetOperationRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetOperationRequest) ProtoMessage() {}
+
+func (x *GetOperationRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_common_operation_service_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetOperationRequest.ProtoReflect.Descriptor instead.
+func (*GetOperationRequest) Descriptor() ([]byte, []int) {
+	return file_common_operation_service_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *GetOperationRequest) GetOperationId() string {
+	if x != nil {
+		return x.OperationId
+	}
+	return ""
+}
+
+type CancelOperationRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	OperationId string `protobuf:"bytes,1,opt,name=operation_id,json=operationId,proto3" json:"operation_id,omitempty"`
+}
+
+func (x *CancelOperationRequest) Reset() {
+	*x = CancelOperationRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_common_operation_service_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CancelOperationRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CancelOperationRequest) ProtoMessage() {}
+
+func (x *CancelOperationRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_common_operation_service_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CancelOperationRequest.ProtoReflect.Descriptor instead.
+func (*CancelOperationRequest) Descriptor() ([]byte, []int) {
+	return file_common_operation_service_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *CancelOperationRequest) GetOperationId() string {
+	if x != nil {
+		return x.OperationId
+	}
+	return ""
+}
+
+var File_common_operation_service_proto protoreflect.FileDescriptor
+
+var file_common_operation_service_proto_rawDesc = []byte{
+	0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x12, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x1a, 0x16, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
+	0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x1a, 0x17, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x3e, 0x0a, 0x13, 0x47, 0x65, 0x74,
+	0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x12, 0x27, 0x0a, 0x0c, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x04, 0xe8, 0xc7, 0x31, 0x01, 0x52, 0x0b, 0x6f, 0x70,
+	0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x41, 0x0a, 0x16, 0x43, 0x61, 0x6e,
+	0x63, 0x65, 0x6c, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0c, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x04, 0xe8, 0xc7, 0x31, 0x01, 0x52,
+	0x0b, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x32, 0x8a, 0x01, 0x0a,
+	0x10, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
+	0x65, 0x12, 0x37, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x1b, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
+	0x6e, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4f,
+	0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x06, 0x43, 0x61,
+	0x6e, 0x63, 0x65, 0x6c, 0x12, 0x1e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x61,
+	0x6e, 0x63, 0x65, 0x6c, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4f, 0x70,
+	0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74,
+	0x2e, 0x70, 0x65, 0x72, 0x78, 0x2e, 0x72, 0x75, 0x2f, 0x70, 0x65, 0x72, 0x78, 0x69, 0x73, 0x2f,
+	0x70, 0x65, 0x72, 0x78, 0x69, 0x73, 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
+	0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_common_operation_service_proto_rawDescOnce sync.Once
+	file_common_operation_service_proto_rawDescData = file_common_operation_service_proto_rawDesc
+)
+
+func file_common_operation_service_proto_rawDescGZIP() []byte {
+	file_common_operation_service_proto_rawDescOnce.Do(func() {
+		file_common_operation_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_operation_service_proto_rawDescData)
+	})
+	return file_common_operation_service_proto_rawDescData
+}
+
+var file_common_operation_service_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_common_operation_service_proto_goTypes = []interface{}{
+	(*GetOperationRequest)(nil),    // 0: common.GetOperationRequest
+	(*CancelOperationRequest)(nil), // 1: common.CancelOperationRequest
+	(*Operation)(nil),              // 2: common.Operation
+}
+var file_common_operation_service_proto_depIdxs = []int32{
+	0, // 0: common.OperationService.Get:input_type -> common.GetOperationRequest
+	1, // 1: common.OperationService.Cancel:input_type -> common.CancelOperationRequest
+	2, // 2: common.OperationService.Get:output_type -> common.Operation
+	2, // 3: common.OperationService.Cancel:output_type -> common.Operation
+	2, // [2:4] is the sub-list for method output_type
+	0, // [0:2] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_common_operation_service_proto_init() }
+func file_common_operation_service_proto_init() {
+	if File_common_operation_service_proto != nil {
+		return
+	}
+	file_common_operation_proto_init()
+	file_common_validation_proto_init()
+	if !protoimpl.UnsafeEnabled {
+		file_common_operation_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GetOperationRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_common_operation_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CancelOperationRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_common_operation_service_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_common_operation_service_proto_goTypes,
+		DependencyIndexes: file_common_operation_service_proto_depIdxs,
+		MessageInfos:      file_common_operation_service_proto_msgTypes,
+	}.Build()
+	File_common_operation_service_proto = out.File
+	file_common_operation_service_proto_rawDesc = nil
+	file_common_operation_service_proto_goTypes = nil
+	file_common_operation_service_proto_depIdxs = nil
+}
diff --git a/proto/common/operation_service_grpc.pb.go b/proto/common/operation_service_grpc.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..8d3ad97659ded9295d4f88cb16fc4da931ea2672
--- /dev/null
+++ b/proto/common/operation_service_grpc.pb.go
@@ -0,0 +1,150 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.3.0
+// - protoc             v4.24.3
+// source: common/operation_service.proto
+
+package common
+
+import (
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+const (
+	OperationService_Get_FullMethodName    = "/common.OperationService/Get"
+	OperationService_Cancel_FullMethodName = "/common.OperationService/Cancel"
+)
+
+// OperationServiceClient is the client API for OperationService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type OperationServiceClient interface {
+	// Возвращает статус операции и ее результат если она завершена
+	Get(ctx context.Context, in *GetOperationRequest, opts ...grpc.CallOption) (*Operation, error)
+	// Отменяет выполнение операции если это поддерживается сервисом
+	Cancel(ctx context.Context, in *CancelOperationRequest, opts ...grpc.CallOption) (*Operation, error)
+}
+
+type operationServiceClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewOperationServiceClient(cc grpc.ClientConnInterface) OperationServiceClient {
+	return &operationServiceClient{cc}
+}
+
+func (c *operationServiceClient) Get(ctx context.Context, in *GetOperationRequest, opts ...grpc.CallOption) (*Operation, error) {
+	out := new(Operation)
+	err := c.cc.Invoke(ctx, OperationService_Get_FullMethodName, in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *operationServiceClient) Cancel(ctx context.Context, in *CancelOperationRequest, opts ...grpc.CallOption) (*Operation, error) {
+	out := new(Operation)
+	err := c.cc.Invoke(ctx, OperationService_Cancel_FullMethodName, in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// OperationServiceServer is the server API for OperationService service.
+// All implementations must embed UnimplementedOperationServiceServer
+// for forward compatibility
+type OperationServiceServer interface {
+	// Возвращает статус операции и ее результат если она завершена
+	Get(context.Context, *GetOperationRequest) (*Operation, error)
+	// Отменяет выполнение операции если это поддерживается сервисом
+	Cancel(context.Context, *CancelOperationRequest) (*Operation, error)
+	mustEmbedUnimplementedOperationServiceServer()
+}
+
+// UnimplementedOperationServiceServer must be embedded to have forward compatible implementations.
+type UnimplementedOperationServiceServer struct {
+}
+
+func (UnimplementedOperationServiceServer) Get(context.Context, *GetOperationRequest) (*Operation, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Get not implemented")
+}
+func (UnimplementedOperationServiceServer) Cancel(context.Context, *CancelOperationRequest) (*Operation, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Cancel not implemented")
+}
+func (UnimplementedOperationServiceServer) mustEmbedUnimplementedOperationServiceServer() {}
+
+// UnsafeOperationServiceServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to OperationServiceServer will
+// result in compilation errors.
+type UnsafeOperationServiceServer interface {
+	mustEmbedUnimplementedOperationServiceServer()
+}
+
+func RegisterOperationServiceServer(s grpc.ServiceRegistrar, srv OperationServiceServer) {
+	s.RegisterService(&OperationService_ServiceDesc, srv)
+}
+
+func _OperationService_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(GetOperationRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(OperationServiceServer).Get(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: OperationService_Get_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(OperationServiceServer).Get(ctx, req.(*GetOperationRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _OperationService_Cancel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(CancelOperationRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(OperationServiceServer).Cancel(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: OperationService_Cancel_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(OperationServiceServer).Cancel(ctx, req.(*CancelOperationRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+// OperationService_ServiceDesc is the grpc.ServiceDesc for OperationService service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var OperationService_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "common.OperationService",
+	HandlerType: (*OperationServiceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "Get",
+			Handler:    _OperationService_Get_Handler,
+		},
+		{
+			MethodName: "Cancel",
+			Handler:    _OperationService_Cancel_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "common/operation_service.proto",
+}
diff --git a/proto/common/validation.pb.go b/proto/common/validation.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..cca43ab540da1592a3bb4c21a91ba2be6f1d22bb
--- /dev/null
+++ b/proto/common/validation.pb.go
@@ -0,0 +1,315 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.31.0
+// 	protoc        v4.24.3
+// source: common/validation.proto
+
+package common
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	descriptorpb "google.golang.org/protobuf/types/descriptorpb"
+	reflect "reflect"
+	sync "sync"
+)
+
+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 MapKeySpec struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Value   string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
+	Pattern string `protobuf:"bytes,2,opt,name=pattern,proto3" json:"pattern,omitempty"`
+	Length  string `protobuf:"bytes,3,opt,name=length,proto3" json:"length,omitempty"`
+}
+
+func (x *MapKeySpec) Reset() {
+	*x = MapKeySpec{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_common_validation_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *MapKeySpec) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MapKeySpec) ProtoMessage() {}
+
+func (x *MapKeySpec) ProtoReflect() protoreflect.Message {
+	mi := &file_common_validation_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use MapKeySpec.ProtoReflect.Descriptor instead.
+func (*MapKeySpec) Descriptor() ([]byte, []int) {
+	return file_common_validation_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *MapKeySpec) GetValue() string {
+	if x != nil {
+		return x.Value
+	}
+	return ""
+}
+
+func (x *MapKeySpec) GetPattern() string {
+	if x != nil {
+		return x.Pattern
+	}
+	return ""
+}
+
+func (x *MapKeySpec) GetLength() string {
+	if x != nil {
+		return x.Length
+	}
+	return ""
+}
+
+var file_common_validation_proto_extTypes = []protoimpl.ExtensionInfo{
+	{
+		ExtendedType:  (*descriptorpb.OneofOptions)(nil),
+		ExtensionType: (*bool)(nil),
+		Field:         101400,
+		Name:          "common.exactly_one",
+		Tag:           "varint,101400,opt,name=exactly_one",
+		Filename:      "common/validation.proto",
+	},
+	{
+		ExtendedType:  (*descriptorpb.FieldOptions)(nil),
+		ExtensionType: (*bool)(nil),
+		Field:         101501,
+		Name:          "common.required",
+		Tag:           "varint,101501,opt,name=required",
+		Filename:      "common/validation.proto",
+	},
+	{
+		ExtendedType:  (*descriptorpb.FieldOptions)(nil),
+		ExtensionType: (*string)(nil),
+		Field:         101502,
+		Name:          "common.pattern",
+		Tag:           "bytes,101502,opt,name=pattern",
+		Filename:      "common/validation.proto",
+	},
+	{
+		ExtendedType:  (*descriptorpb.FieldOptions)(nil),
+		ExtensionType: (*string)(nil),
+		Field:         101503,
+		Name:          "common.value",
+		Tag:           "bytes,101503,opt,name=value",
+		Filename:      "common/validation.proto",
+	},
+	{
+		ExtendedType:  (*descriptorpb.FieldOptions)(nil),
+		ExtensionType: (*string)(nil),
+		Field:         101504,
+		Name:          "common.size",
+		Tag:           "bytes,101504,opt,name=size",
+		Filename:      "common/validation.proto",
+	},
+	{
+		ExtendedType:  (*descriptorpb.FieldOptions)(nil),
+		ExtensionType: (*string)(nil),
+		Field:         101505,
+		Name:          "common.length",
+		Tag:           "bytes,101505,opt,name=length",
+		Filename:      "common/validation.proto",
+	},
+	{
+		ExtendedType:  (*descriptorpb.FieldOptions)(nil),
+		ExtensionType: (*bool)(nil),
+		Field:         101506,
+		Name:          "common.unique",
+		Tag:           "varint,101506,opt,name=unique",
+		Filename:      "common/validation.proto",
+	},
+	{
+		ExtendedType:  (*descriptorpb.FieldOptions)(nil),
+		ExtensionType: (*MapKeySpec)(nil),
+		Field:         101510,
+		Name:          "common.map_key",
+		Tag:           "bytes,101510,opt,name=map_key",
+		Filename:      "common/validation.proto",
+	},
+	{
+		ExtendedType:  (*descriptorpb.FieldOptions)(nil),
+		ExtensionType: (*string)(nil),
+		Field:         101511,
+		Name:          "common.bytes",
+		Tag:           "bytes,101511,opt,name=bytes",
+		Filename:      "common/validation.proto",
+	},
+}
+
+// Extension fields to descriptorpb.OneofOptions.
+var (
+	// optional bool exactly_one = 101400;
+	E_ExactlyOne = &file_common_validation_proto_extTypes[0]
+)
+
+// Extension fields to descriptorpb.FieldOptions.
+var (
+	// optional bool required = 101501;
+	E_Required = &file_common_validation_proto_extTypes[1]
+	// optional string pattern = 101502;
+	E_Pattern = &file_common_validation_proto_extTypes[2]
+	// optional string value = 101503;
+	E_Value = &file_common_validation_proto_extTypes[3]
+	// optional string size = 101504;
+	E_Size = &file_common_validation_proto_extTypes[4]
+	// optional string length = 101505;
+	E_Length = &file_common_validation_proto_extTypes[5]
+	// optional bool unique = 101506;
+	E_Unique = &file_common_validation_proto_extTypes[6]
+	// optional common.MapKeySpec map_key = 101510;
+	E_MapKey = &file_common_validation_proto_extTypes[7]
+	// optional string bytes = 101511;
+	E_Bytes = &file_common_validation_proto_extTypes[8]
+)
+
+var File_common_validation_proto protoreflect.FileDescriptor
+
+var file_common_validation_proto_rawDesc = []byte{
+	0x0a, 0x17, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
+	0x6e, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
+	0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x22, 0x54, 0x0a, 0x0a, 0x4d, 0x61, 0x70, 0x4b, 0x65, 0x79, 0x53, 0x70, 0x65,
+	0x63, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65,
+	0x72, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72,
+	0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3a, 0x40, 0x0a, 0x0b, 0x65, 0x78, 0x61,
+	0x63, 0x74, 0x6c, 0x79, 0x5f, 0x6f, 0x6e, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
+	0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4f, 0x6e, 0x65, 0x6f, 0x66,
+	0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x98, 0x98, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52,
+	0x0a, 0x65, 0x78, 0x61, 0x63, 0x74, 0x6c, 0x79, 0x4f, 0x6e, 0x65, 0x3a, 0x3b, 0x0a, 0x08, 0x72,
+	0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f,
+	0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xfd, 0x98, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08,
+	0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x3a, 0x39, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74,
+	0x65, 0x72, 0x6e, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f,
+	0x6e, 0x73, 0x18, 0xfe, 0x98, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74,
+	0x65, 0x72, 0x6e, 0x3a, 0x35, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, 0x2e, 0x67,
+	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46,
+	0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xff, 0x98, 0x06, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x33, 0x0a, 0x04, 0x73, 0x69,
+	0x7a, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
+	0x73, 0x18, 0x80, 0x99, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x3a,
+	0x37, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
+	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c,
+	0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x81, 0x99, 0x06, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3a, 0x37, 0x0a, 0x06, 0x75, 0x6e, 0x69, 0x71,
+	0x75, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
+	0x73, 0x18, 0x82, 0x99, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x75, 0x6e, 0x69, 0x71, 0x75,
+	0x65, 0x3a, 0x4c, 0x0a, 0x07, 0x6d, 0x61, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x1d, 0x2e, 0x67,
+	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46,
+	0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x86, 0x99, 0x06, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x70,
+	0x4b, 0x65, 0x79, 0x53, 0x70, 0x65, 0x63, 0x52, 0x06, 0x6d, 0x61, 0x70, 0x4b, 0x65, 0x79, 0x3a,
+	0x35, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
+	0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64,
+	0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x87, 0x99, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x2e, 0x70, 0x65,
+	0x72, 0x78, 0x2e, 0x72, 0x75, 0x2f, 0x70, 0x65, 0x72, 0x78, 0x69, 0x73, 0x2f, 0x70, 0x65, 0x72,
+	0x78, 0x69, 0x73, 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6d,
+	0x6d, 0x6f, 0x6e, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x33,
+}
+
+var (
+	file_common_validation_proto_rawDescOnce sync.Once
+	file_common_validation_proto_rawDescData = file_common_validation_proto_rawDesc
+)
+
+func file_common_validation_proto_rawDescGZIP() []byte {
+	file_common_validation_proto_rawDescOnce.Do(func() {
+		file_common_validation_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_validation_proto_rawDescData)
+	})
+	return file_common_validation_proto_rawDescData
+}
+
+var file_common_validation_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_common_validation_proto_goTypes = []interface{}{
+	(*MapKeySpec)(nil),                // 0: common.MapKeySpec
+	(*descriptorpb.OneofOptions)(nil), // 1: google.protobuf.OneofOptions
+	(*descriptorpb.FieldOptions)(nil), // 2: google.protobuf.FieldOptions
+}
+var file_common_validation_proto_depIdxs = []int32{
+	1,  // 0: common.exactly_one:extendee -> google.protobuf.OneofOptions
+	2,  // 1: common.required:extendee -> google.protobuf.FieldOptions
+	2,  // 2: common.pattern:extendee -> google.protobuf.FieldOptions
+	2,  // 3: common.value:extendee -> google.protobuf.FieldOptions
+	2,  // 4: common.size:extendee -> google.protobuf.FieldOptions
+	2,  // 5: common.length:extendee -> google.protobuf.FieldOptions
+	2,  // 6: common.unique:extendee -> google.protobuf.FieldOptions
+	2,  // 7: common.map_key:extendee -> google.protobuf.FieldOptions
+	2,  // 8: common.bytes:extendee -> google.protobuf.FieldOptions
+	0,  // 9: common.map_key:type_name -> common.MapKeySpec
+	10, // [10:10] is the sub-list for method output_type
+	10, // [10:10] is the sub-list for method input_type
+	9,  // [9:10] is the sub-list for extension type_name
+	0,  // [0:9] is the sub-list for extension extendee
+	0,  // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_common_validation_proto_init() }
+func file_common_validation_proto_init() {
+	if File_common_validation_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_common_validation_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*MapKeySpec); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_common_validation_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   1,
+			NumExtensions: 9,
+			NumServices:   0,
+		},
+		GoTypes:           file_common_validation_proto_goTypes,
+		DependencyIndexes: file_common_validation_proto_depIdxs,
+		MessageInfos:      file_common_validation_proto_msgTypes,
+		ExtensionInfos:    file_common_validation_proto_extTypes,
+	}.Build()
+	File_common_validation_proto = out.File
+	file_common_validation_proto_rawDesc = nil
+	file_common_validation_proto_goTypes = nil
+	file_common_validation_proto_depIdxs = nil
+}
diff --git a/proto/extensions/extension_service.pb.go b/proto/extensions/extension_service.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..f4d0b11103c6cafa6097cb2cd7074b88e6467aed
--- /dev/null
+++ b/proto/extensions/extension_service.pb.go
@@ -0,0 +1,775 @@
+//*
+//# Расширения (Extensions)
+//
+//Расширения представляют собой отдельные сервисы предоставляющие дополнительные возможности для пользователей. Сервис
+//может предоставлять несколько расширений одновременно.
+//
+//Для координации взаимодействия используется сервис менеджер расширений (Extension Manager). Для предоставления своих функций в систему сервис должен
+//зарегистрироваться на контроллере.
+//
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.31.0
+// 	protoc        v4.24.3
+// source: extensions/extension_service.proto
+
+package extensions
+
+import (
+	common "git.perx.ru/perxis/perxis-go/proto/common"
+	references "git.perx.ru/perxis/perxis-go/proto/references"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+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 ActionResponse_State int32
+
+const (
+	ActionResponse_DONE                ActionResponse_State = 0 // Запрос завершен
+	ActionResponse_ERROR               ActionResponse_State = 1 // Возникла ошибка
+	ActionResponse_PENDING             ActionResponse_State = 2 // Запрос ожидает выполнения
+	ActionResponse_IN_PROGRESS         ActionResponse_State = 3 // Запрос выполняется
+	ActionResponse_PARAMETERS_REQUIRED ActionResponse_State = 4 // Требуются дополнительные данные для выполнения запроса
+)
+
+// Enum value maps for ActionResponse_State.
+var (
+	ActionResponse_State_name = map[int32]string{
+		0: "DONE",
+		1: "ERROR",
+		2: "PENDING",
+		3: "IN_PROGRESS",
+		4: "PARAMETERS_REQUIRED",
+	}
+	ActionResponse_State_value = map[string]int32{
+		"DONE":                0,
+		"ERROR":               1,
+		"PENDING":             2,
+		"IN_PROGRESS":         3,
+		"PARAMETERS_REQUIRED": 4,
+	}
+)
+
+func (x ActionResponse_State) Enum() *ActionResponse_State {
+	p := new(ActionResponse_State)
+	*p = x
+	return p
+}
+
+func (x ActionResponse_State) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (ActionResponse_State) Descriptor() protoreflect.EnumDescriptor {
+	return file_extensions_extension_service_proto_enumTypes[0].Descriptor()
+}
+
+func (ActionResponse_State) Type() protoreflect.EnumType {
+	return &file_extensions_extension_service_proto_enumTypes[0]
+}
+
+func (x ActionResponse_State) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use ActionResponse_State.Descriptor instead.
+func (ActionResponse_State) EnumDescriptor() ([]byte, []int) {
+	return file_extensions_extension_service_proto_rawDescGZIP(), []int{4, 0}
+}
+
+type ActionResponse_Format int32
+
+const (
+	ActionResponse_PLAIN    ActionResponse_Format = 0
+	ActionResponse_HTML     ActionResponse_Format = 1
+	ActionResponse_MARKDOWN ActionResponse_Format = 2
+)
+
+// Enum value maps for ActionResponse_Format.
+var (
+	ActionResponse_Format_name = map[int32]string{
+		0: "PLAIN",
+		1: "HTML",
+		2: "MARKDOWN",
+	}
+	ActionResponse_Format_value = map[string]int32{
+		"PLAIN":    0,
+		"HTML":     1,
+		"MARKDOWN": 2,
+	}
+)
+
+func (x ActionResponse_Format) Enum() *ActionResponse_Format {
+	p := new(ActionResponse_Format)
+	*p = x
+	return p
+}
+
+func (x ActionResponse_Format) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (ActionResponse_Format) Descriptor() protoreflect.EnumDescriptor {
+	return file_extensions_extension_service_proto_enumTypes[1].Descriptor()
+}
+
+func (ActionResponse_Format) Type() protoreflect.EnumType {
+	return &file_extensions_extension_service_proto_enumTypes[1]
+}
+
+func (x ActionResponse_Format) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use ActionResponse_Format.Descriptor instead.
+func (ActionResponse_Format) EnumDescriptor() ([]byte, []int) {
+	return file_extensions_extension_service_proto_rawDescGZIP(), []int{4, 1}
+}
+
+// InstallRequest - запрос на установку расширений
+type InstallRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Extensions []string `protobuf:"bytes,10000,rep,name=extensions,proto3" json:"extensions,omitempty"`          // Список расширений для установки
+	SpaceId    string   `protobuf:"bytes,10010,opt,name=space_id,json=spaceId,proto3" json:"space_id,omitempty"` // Пространство для установки расширений
+	EnvId      string   `protobuf:"bytes,10020,opt,name=env_id,json=envId,proto3" json:"env_id,omitempty"`       // Идентификатор окружения для установки (по умолчанию master)
+	Force      bool     `protobuf:"varint,10100,opt,name=force,proto3" json:"force,omitempty"`                   // Устанавливать расширения вне зависимости от возможных ошибок
+	Update     bool     `protobuf:"varint,10200,opt,name=update,proto3" json:"update,omitempty"`                 // Установить обновления расширений
+}
+
+func (x *InstallRequest) Reset() {
+	*x = InstallRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_extensions_extension_service_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *InstallRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*InstallRequest) ProtoMessage() {}
+
+func (x *InstallRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_extensions_extension_service_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use InstallRequest.ProtoReflect.Descriptor instead.
+func (*InstallRequest) Descriptor() ([]byte, []int) {
+	return file_extensions_extension_service_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *InstallRequest) GetExtensions() []string {
+	if x != nil {
+		return x.Extensions
+	}
+	return nil
+}
+
+func (x *InstallRequest) GetSpaceId() string {
+	if x != nil {
+		return x.SpaceId
+	}
+	return ""
+}
+
+func (x *InstallRequest) GetEnvId() string {
+	if x != nil {
+		return x.EnvId
+	}
+	return ""
+}
+
+func (x *InstallRequest) GetForce() bool {
+	if x != nil {
+		return x.Force
+	}
+	return false
+}
+
+func (x *InstallRequest) GetUpdate() bool {
+	if x != nil {
+		return x.Update
+	}
+	return false
+}
+
+// ExtensionList - возвращает список состояний расширений в результате выполнения операции `common.Operation`
+// (СЃРј. `common.OperationService`)
+// В методах Install, Uninstall, Check возвращается список состояний расширений в результате выполнения операции
+type ExtensionList struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Results []*Extension `protobuf:"bytes,10000,rep,name=results,proto3" json:"results,omitempty"` // Список состояний расширений
+}
+
+func (x *ExtensionList) Reset() {
+	*x = ExtensionList{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_extensions_extension_service_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ExtensionList) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ExtensionList) ProtoMessage() {}
+
+func (x *ExtensionList) ProtoReflect() protoreflect.Message {
+	mi := &file_extensions_extension_service_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ExtensionList.ProtoReflect.Descriptor instead.
+func (*ExtensionList) Descriptor() ([]byte, []int) {
+	return file_extensions_extension_service_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *ExtensionList) GetResults() []*Extension {
+	if x != nil {
+		return x.Results
+	}
+	return nil
+}
+
+// UninstallRequest - запрос на удаление расширений
+type UninstallRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Extensions []string `protobuf:"bytes,10000,rep,name=extensions,proto3" json:"extensions,omitempty"`          // Список расширений для удаления
+	SpaceId    string   `protobuf:"bytes,10010,opt,name=space_id,json=spaceId,proto3" json:"space_id,omitempty"` // Пространство для удаления расширений
+	EnvId      string   `protobuf:"bytes,10020,opt,name=env_id,json=envId,proto3" json:"env_id,omitempty"`       // Идентификатор окружения для установки (по умолчанию master)
+	Remove     bool     `protobuf:"varint,10100,opt,name=remove,proto3" json:"remove,omitempty"`                 // Удалить изменения сделанные расширением в пространстве, если возможно
+	Force      bool     `protobuf:"varint,10200,opt,name=force,proto3" json:"force,omitempty"`                   // Удалять расширения вне зависимости от возможных ошибок, без учета зависимостей
+}
+
+func (x *UninstallRequest) Reset() {
+	*x = UninstallRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_extensions_extension_service_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *UninstallRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UninstallRequest) ProtoMessage() {}
+
+func (x *UninstallRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_extensions_extension_service_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use UninstallRequest.ProtoReflect.Descriptor instead.
+func (*UninstallRequest) Descriptor() ([]byte, []int) {
+	return file_extensions_extension_service_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *UninstallRequest) GetExtensions() []string {
+	if x != nil {
+		return x.Extensions
+	}
+	return nil
+}
+
+func (x *UninstallRequest) GetSpaceId() string {
+	if x != nil {
+		return x.SpaceId
+	}
+	return ""
+}
+
+func (x *UninstallRequest) GetEnvId() string {
+	if x != nil {
+		return x.EnvId
+	}
+	return ""
+}
+
+func (x *UninstallRequest) GetRemove() bool {
+	if x != nil {
+		return x.Remove
+	}
+	return false
+}
+
+func (x *UninstallRequest) GetForce() bool {
+	if x != nil {
+		return x.Force
+	}
+	return false
+}
+
+// CheckRequest - запрос на проверку статуса установки расширений
+type CheckRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Extensions []string `protobuf:"bytes,10000,rep,name=extensions,proto3" json:"extensions,omitempty"`          // Список расширений для удаления
+	SpaceId    string   `protobuf:"bytes,10010,opt,name=space_id,json=spaceId,proto3" json:"space_id,omitempty"` // Пространство для удаления расширений
+	EnvId      string   `protobuf:"bytes,10020,opt,name=env_id,json=envId,proto3" json:"env_id,omitempty"`       // Идентификатор окружения для установки (по умолчанию master)
+}
+
+func (x *CheckRequest) Reset() {
+	*x = CheckRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_extensions_extension_service_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CheckRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CheckRequest) ProtoMessage() {}
+
+func (x *CheckRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_extensions_extension_service_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CheckRequest.ProtoReflect.Descriptor instead.
+func (*CheckRequest) Descriptor() ([]byte, []int) {
+	return file_extensions_extension_service_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *CheckRequest) GetExtensions() []string {
+	if x != nil {
+		return x.Extensions
+	}
+	return nil
+}
+
+func (x *CheckRequest) GetSpaceId() string {
+	if x != nil {
+		return x.SpaceId
+	}
+	return ""
+}
+
+func (x *CheckRequest) GetEnvId() string {
+	if x != nil {
+		return x.EnvId
+	}
+	return ""
+}
+
+type ActionResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	State    ActionResponse_State    `protobuf:"varint,10000,opt,name=state,proto3,enum=extensions.ActionResponse_State" json:"state,omitempty"`                                                         // Состояние расширение
+	Target   Target                  `protobuf:"varint,10010,opt,name=target,proto3,enum=extensions.Target" json:"target,omitempty"`                                                                     // Как открывать результат выполнения действия в пользовательском интерфейсе (переопределяет значение в Action)
+	Format   ActionResponse_Format   `protobuf:"varint,10050,opt,name=format,proto3,enum=extensions.ActionResponse_Format" json:"format,omitempty"`                                                      // Формат полей msg  и error
+	Msg      string                  `protobuf:"bytes,10100,opt,name=msg,proto3" json:"msg,omitempty"`                                                                                                   // Сообщение о выполнении действия
+	Title    string                  `protobuf:"bytes,10110,opt,name=title,proto3" json:"title,omitempty"`                                                                                               // Текст для отображения в интерфейсе
+	Image    string                  `protobuf:"bytes,10140,opt,name=image,proto3" json:"image,omitempty"`                                                                                               // Изображение для отображения в интерфейсе (шапке окна)
+	Error    string                  `protobuf:"bytes,10200,opt,name=error,proto3" json:"error,omitempty"`                                                                                               // Сообщение в случае ошибки (дополнительно к msg)
+	Next     []*Action               `protobuf:"bytes,10300,rep,name=next,proto3" json:"next,omitempty"`                                                                                                 // Следующие возможные действия. Интерфейс отображает как варианты дальнейших действий пользователя
+	Metadata map[string]string       `protobuf:"bytes,10400,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // Метаданные запроса
+	Refs     []*references.Reference `protobuf:"bytes,10320,rep,name=refs,proto3" json:"refs,omitempty"`                                                                                                 // Ссылки на записи (назначение ссылок зависит от действия и расширения)
+}
+
+func (x *ActionResponse) Reset() {
+	*x = ActionResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_extensions_extension_service_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ActionResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ActionResponse) ProtoMessage() {}
+
+func (x *ActionResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_extensions_extension_service_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ActionResponse.ProtoReflect.Descriptor instead.
+func (*ActionResponse) Descriptor() ([]byte, []int) {
+	return file_extensions_extension_service_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *ActionResponse) GetState() ActionResponse_State {
+	if x != nil {
+		return x.State
+	}
+	return ActionResponse_DONE
+}
+
+func (x *ActionResponse) GetTarget() Target {
+	if x != nil {
+		return x.Target
+	}
+	return Target_DEFAULT
+}
+
+func (x *ActionResponse) GetFormat() ActionResponse_Format {
+	if x != nil {
+		return x.Format
+	}
+	return ActionResponse_PLAIN
+}
+
+func (x *ActionResponse) GetMsg() string {
+	if x != nil {
+		return x.Msg
+	}
+	return ""
+}
+
+func (x *ActionResponse) GetTitle() string {
+	if x != nil {
+		return x.Title
+	}
+	return ""
+}
+
+func (x *ActionResponse) GetImage() string {
+	if x != nil {
+		return x.Image
+	}
+	return ""
+}
+
+func (x *ActionResponse) GetError() string {
+	if x != nil {
+		return x.Error
+	}
+	return ""
+}
+
+func (x *ActionResponse) GetNext() []*Action {
+	if x != nil {
+		return x.Next
+	}
+	return nil
+}
+
+func (x *ActionResponse) GetMetadata() map[string]string {
+	if x != nil {
+		return x.Metadata
+	}
+	return nil
+}
+
+func (x *ActionResponse) GetRefs() []*references.Reference {
+	if x != nil {
+		return x.Refs
+	}
+	return nil
+}
+
+var File_extensions_extension_service_proto protoreflect.FileDescriptor
+
+var file_extensions_extension_service_proto_rawDesc = []byte{
+	0x0a, 0x22, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x65, 0x78, 0x74,
+	0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73,
+	0x1a, 0x1b, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x72, 0x65, 0x66,
+	0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x16, 0x63,
+	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
+	0x73, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x22, 0x95, 0x01, 0x0a, 0x0e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f,
+	0x6e, 0x73, 0x18, 0x90, 0x4e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e,
+	0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69,
+	0x64, 0x18, 0x9a, 0x4e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49,
+	0x64, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x76, 0x5f, 0x69, 0x64, 0x18, 0xa4, 0x4e, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x05, 0x65, 0x6e, 0x76, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x05, 0x66, 0x6f, 0x72,
+	0x63, 0x65, 0x18, 0xf4, 0x4e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65,
+	0x12, 0x17, 0x0a, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0xd8, 0x4f, 0x20, 0x01, 0x28,
+	0x08, 0x52, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, 0x41, 0x0a, 0x0d, 0x45, 0x78, 0x74,
+	0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x07, 0x72, 0x65,
+	0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x90, 0x4e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x65,
+	0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73,
+	0x69, 0x6f, 0x6e, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x97, 0x01, 0x0a,
+	0x10, 0x55, 0x6e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x12, 0x1f, 0x0a, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18,
+	0x90, 0x4e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f,
+	0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x9a,
+	0x4e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x16,
+	0x0a, 0x06, 0x65, 0x6e, 0x76, 0x5f, 0x69, 0x64, 0x18, 0xa4, 0x4e, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x05, 0x65, 0x6e, 0x76, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65,
+	0x18, 0xf4, 0x4e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x12,
+	0x15, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0xd8, 0x4f, 0x20, 0x01, 0x28, 0x08, 0x52,
+	0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x22, 0x63, 0x0a, 0x0c, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73,
+	0x69, 0x6f, 0x6e, 0x73, 0x18, 0x90, 0x4e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74,
+	0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x70, 0x61, 0x63, 0x65,
+	0x5f, 0x69, 0x64, 0x18, 0x9a, 0x4e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x61, 0x63,
+	0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x76, 0x5f, 0x69, 0x64, 0x18, 0xa4, 0x4e,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6e, 0x76, 0x49, 0x64, 0x22, 0xed, 0x04, 0x0a, 0x0e,
+	0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37,
+	0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x90, 0x4e, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20,
+	0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69,
+	0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65,
+	0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65,
+	0x74, 0x18, 0x9a, 0x4e, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e,
+	0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x06, 0x74, 0x61,
+	0x72, 0x67, 0x65, 0x74, 0x12, 0x3a, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0xc2,
+	0x4e, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f,
+	0x6e, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x2e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74,
+	0x12, 0x11, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0xf4, 0x4e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
+	0x6d, 0x73, 0x67, 0x12, 0x15, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0xfe, 0x4e, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x15, 0x0a, 0x05, 0x69, 0x6d,
+	0x61, 0x67, 0x65, 0x18, 0x9c, 0x4f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67,
+	0x65, 0x12, 0x15, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xd8, 0x4f, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x27, 0x0a, 0x04, 0x6e, 0x65, 0x78, 0x74,
+	0x18, 0xbc, 0x50, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73,
+	0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x6e, 0x65, 0x78,
+	0x74, 0x12, 0x45, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0xa0, 0x51,
+	0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
+	0x73, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08,
+	0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x32, 0x0a, 0x04, 0x72, 0x65, 0x66, 0x73,
+	0x18, 0xd0, 0x50, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
+	0x74, 0x2e, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x52, 0x65, 0x66,
+	0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x04, 0x72, 0x65, 0x66, 0x73, 0x1a, 0x3b, 0x0a, 0x0d,
+	0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
+	0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
+	0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
+	0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x53, 0x0a, 0x05, 0x53, 0x74, 0x61,
+	0x74, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05,
+	0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49,
+	0x4e, 0x47, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52,
+	0x45, 0x53, 0x53, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x45, 0x54,
+	0x45, 0x52, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x10, 0x04, 0x22, 0x2b,
+	0x0a, 0x06, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x4c, 0x41, 0x49,
+	0x4e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x4d, 0x4c, 0x10, 0x01, 0x12, 0x0c, 0x0a,
+	0x08, 0x4d, 0x41, 0x52, 0x4b, 0x44, 0x4f, 0x57, 0x4e, 0x10, 0x02, 0x32, 0x89, 0x02, 0x0a, 0x10,
+	0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
+	0x12, 0x3a, 0x0a, 0x07, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x12, 0x1a, 0x2e, 0x65, 0x78,
+	0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
+	0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x09,
+	0x55, 0x6e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x12, 0x1c, 0x2e, 0x65, 0x78, 0x74, 0x65,
+	0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
+	0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x05,
+	0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x18, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f,
+	0x6e, 0x73, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
+	0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19,
+	0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69,
+	0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x65, 0x78, 0x74, 0x65,
+	0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x2e, 0x70,
+	0x65, 0x72, 0x78, 0x2e, 0x72, 0x75, 0x2f, 0x70, 0x65, 0x72, 0x78, 0x69, 0x73, 0x2f, 0x70, 0x65,
+	0x72, 0x78, 0x69, 0x73, 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x78,
+	0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x3b, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69,
+	0x6f, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_extensions_extension_service_proto_rawDescOnce sync.Once
+	file_extensions_extension_service_proto_rawDescData = file_extensions_extension_service_proto_rawDesc
+)
+
+func file_extensions_extension_service_proto_rawDescGZIP() []byte {
+	file_extensions_extension_service_proto_rawDescOnce.Do(func() {
+		file_extensions_extension_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_extensions_extension_service_proto_rawDescData)
+	})
+	return file_extensions_extension_service_proto_rawDescData
+}
+
+var file_extensions_extension_service_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
+var file_extensions_extension_service_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
+var file_extensions_extension_service_proto_goTypes = []interface{}{
+	(ActionResponse_State)(0),    // 0: extensions.ActionResponse.State
+	(ActionResponse_Format)(0),   // 1: extensions.ActionResponse.Format
+	(*InstallRequest)(nil),       // 2: extensions.InstallRequest
+	(*ExtensionList)(nil),        // 3: extensions.ExtensionList
+	(*UninstallRequest)(nil),     // 4: extensions.UninstallRequest
+	(*CheckRequest)(nil),         // 5: extensions.CheckRequest
+	(*ActionResponse)(nil),       // 6: extensions.ActionResponse
+	nil,                          // 7: extensions.ActionResponse.MetadataEntry
+	(*Extension)(nil),            // 8: extensions.Extension
+	(Target)(0),                  // 9: extensions.Target
+	(*Action)(nil),               // 10: extensions.Action
+	(*references.Reference)(nil), // 11: content.references.Reference
+	(*ActionRequest)(nil),        // 12: extensions.ActionRequest
+	(*common.Operation)(nil),     // 13: common.Operation
+}
+var file_extensions_extension_service_proto_depIdxs = []int32{
+	8,  // 0: extensions.ExtensionList.results:type_name -> extensions.Extension
+	0,  // 1: extensions.ActionResponse.state:type_name -> extensions.ActionResponse.State
+	9,  // 2: extensions.ActionResponse.target:type_name -> extensions.Target
+	1,  // 3: extensions.ActionResponse.format:type_name -> extensions.ActionResponse.Format
+	10, // 4: extensions.ActionResponse.next:type_name -> extensions.Action
+	7,  // 5: extensions.ActionResponse.metadata:type_name -> extensions.ActionResponse.MetadataEntry
+	11, // 6: extensions.ActionResponse.refs:type_name -> content.references.Reference
+	2,  // 7: extensions.ExtensionService.Install:input_type -> extensions.InstallRequest
+	4,  // 8: extensions.ExtensionService.Uninstall:input_type -> extensions.UninstallRequest
+	5,  // 9: extensions.ExtensionService.Check:input_type -> extensions.CheckRequest
+	12, // 10: extensions.ExtensionService.Action:input_type -> extensions.ActionRequest
+	13, // 11: extensions.ExtensionService.Install:output_type -> common.Operation
+	13, // 12: extensions.ExtensionService.Uninstall:output_type -> common.Operation
+	13, // 13: extensions.ExtensionService.Check:output_type -> common.Operation
+	6,  // 14: extensions.ExtensionService.Action:output_type -> extensions.ActionResponse
+	11, // [11:15] is the sub-list for method output_type
+	7,  // [7:11] is the sub-list for method input_type
+	7,  // [7:7] is the sub-list for extension type_name
+	7,  // [7:7] is the sub-list for extension extendee
+	0,  // [0:7] is the sub-list for field type_name
+}
+
+func init() { file_extensions_extension_service_proto_init() }
+func file_extensions_extension_service_proto_init() {
+	if File_extensions_extension_service_proto != nil {
+		return
+	}
+	file_extensions_extension_proto_init()
+	if !protoimpl.UnsafeEnabled {
+		file_extensions_extension_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*InstallRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_extensions_extension_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ExtensionList); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_extensions_extension_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*UninstallRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_extensions_extension_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CheckRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_extensions_extension_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ActionResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_extensions_extension_service_proto_rawDesc,
+			NumEnums:      2,
+			NumMessages:   6,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_extensions_extension_service_proto_goTypes,
+		DependencyIndexes: file_extensions_extension_service_proto_depIdxs,
+		EnumInfos:         file_extensions_extension_service_proto_enumTypes,
+		MessageInfos:      file_extensions_extension_service_proto_msgTypes,
+	}.Build()
+	File_extensions_extension_service_proto = out.File
+	file_extensions_extension_service_proto_rawDesc = nil
+	file_extensions_extension_service_proto_goTypes = nil
+	file_extensions_extension_service_proto_depIdxs = nil
+}
diff --git a/proto/extensions/extension_service_grpc.pb.go b/proto/extensions/extension_service_grpc.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..3cd37b839f83963d40818ddb7f094894c56f2f12
--- /dev/null
+++ b/proto/extensions/extension_service_grpc.pb.go
@@ -0,0 +1,279 @@
+//*
+//# Расширения (Extensions)
+//
+//Расширения представляют собой отдельные сервисы предоставляющие дополнительные возможности для пользователей. Сервис
+//может предоставлять несколько расширений одновременно.
+//
+//Для координации взаимодействия используется сервис менеджер расширений (Extension Manager). Для предоставления своих функций в систему сервис должен
+//зарегистрироваться на контроллере.
+//
+
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.3.0
+// - protoc             v4.24.3
+// source: extensions/extension_service.proto
+
+package extensions
+
+import (
+	context "context"
+	common "git.perx.ru/perxis/perxis-go/proto/common"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+const (
+	ExtensionService_Install_FullMethodName   = "/extensions.ExtensionService/Install"
+	ExtensionService_Uninstall_FullMethodName = "/extensions.ExtensionService/Uninstall"
+	ExtensionService_Check_FullMethodName     = "/extensions.ExtensionService/Check"
+	ExtensionService_Action_FullMethodName    = "/extensions.ExtensionService/Action"
+)
+
+// ExtensionServiceClient is the client API for ExtensionService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type ExtensionServiceClient interface {
+	// Install выполняет установку указанных расширений. Если
+	// расширение уже установлены выполняет процесс переустановки. Возвращает longtime операцию
+	Install(ctx context.Context, in *InstallRequest, opts ...grpc.CallOption) (*common.Operation, error)
+	// Uninstall выполняет удаление указанных расширений.
+	Uninstall(ctx context.Context, in *UninstallRequest, opts ...grpc.CallOption) (*common.Operation, error)
+	// Check выполняет проверку установки для расширения. При этом расширение проверяет наличие необходимых данных в
+	// пространстве или наличие новой версии расширения и сообщает об этом. Никаких действий с данными пространства не
+	// производится.
+	Check(ctx context.Context, in *CheckRequest, opts ...grpc.CallOption) (*common.Operation, error)
+	// Пользовательские Действия (Actions)
+	//
+	// Пользовательские действия позволяют расширить функционал пользовательского интерфейса путем
+	// добавления в интерфейс элементов взаимодействуя с которыми пользователь может вызывать реакцию на
+	// сервере или переход в интерфейсе.
+	//
+	// Пользовательские действия добавляются при установке расширений в системную коллекции `System/Actions`.
+	// Коллекция создается автоматически менеджером расширений. При установке так же отображается меню `Действия`
+	// доступное для всех пользователей.
+	//
+	// Примеры пользовательских действий:
+	//   - "Собрать сайт" - добавляется Perxis.Web для сборки сайта, доступна пользователю через меню,
+	//     параметры space_id, env_id. При вызове выполняется запрос на сервер ProcessAction.
+	//   - "Посмотреть задачи" - добавляется Tasks для перехода на коллекцию задач. Отображается в меню,
+	//     параметры space_id, env_id.
+	//
+	// Приложения так же могут использовать действия для вызова обработки в других приложениях при
+	// необходимости.
+	Action(ctx context.Context, in *ActionRequest, opts ...grpc.CallOption) (*ActionResponse, error)
+}
+
+type extensionServiceClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewExtensionServiceClient(cc grpc.ClientConnInterface) ExtensionServiceClient {
+	return &extensionServiceClient{cc}
+}
+
+func (c *extensionServiceClient) Install(ctx context.Context, in *InstallRequest, opts ...grpc.CallOption) (*common.Operation, error) {
+	out := new(common.Operation)
+	err := c.cc.Invoke(ctx, ExtensionService_Install_FullMethodName, in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *extensionServiceClient) Uninstall(ctx context.Context, in *UninstallRequest, opts ...grpc.CallOption) (*common.Operation, error) {
+	out := new(common.Operation)
+	err := c.cc.Invoke(ctx, ExtensionService_Uninstall_FullMethodName, in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *extensionServiceClient) Check(ctx context.Context, in *CheckRequest, opts ...grpc.CallOption) (*common.Operation, error) {
+	out := new(common.Operation)
+	err := c.cc.Invoke(ctx, ExtensionService_Check_FullMethodName, in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *extensionServiceClient) Action(ctx context.Context, in *ActionRequest, opts ...grpc.CallOption) (*ActionResponse, error) {
+	out := new(ActionResponse)
+	err := c.cc.Invoke(ctx, ExtensionService_Action_FullMethodName, in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// ExtensionServiceServer is the server API for ExtensionService service.
+// All implementations must embed UnimplementedExtensionServiceServer
+// for forward compatibility
+type ExtensionServiceServer interface {
+	// Install выполняет установку указанных расширений. Если
+	// расширение уже установлены выполняет процесс переустановки. Возвращает longtime операцию
+	Install(context.Context, *InstallRequest) (*common.Operation, error)
+	// Uninstall выполняет удаление указанных расширений.
+	Uninstall(context.Context, *UninstallRequest) (*common.Operation, error)
+	// Check выполняет проверку установки для расширения. При этом расширение проверяет наличие необходимых данных в
+	// пространстве или наличие новой версии расширения и сообщает об этом. Никаких действий с данными пространства не
+	// производится.
+	Check(context.Context, *CheckRequest) (*common.Operation, error)
+	// Пользовательские Действия (Actions)
+	//
+	// Пользовательские действия позволяют расширить функционал пользовательского интерфейса путем
+	// добавления в интерфейс элементов взаимодействуя с которыми пользователь может вызывать реакцию на
+	// сервере или переход в интерфейсе.
+	//
+	// Пользовательские действия добавляются при установке расширений в системную коллекции `System/Actions`.
+	// Коллекция создается автоматически менеджером расширений. При установке так же отображается меню `Действия`
+	// доступное для всех пользователей.
+	//
+	// Примеры пользовательских действий:
+	//   - "Собрать сайт" - добавляется Perxis.Web для сборки сайта, доступна пользователю через меню,
+	//     параметры space_id, env_id. При вызове выполняется запрос на сервер ProcessAction.
+	//   - "Посмотреть задачи" - добавляется Tasks для перехода на коллекцию задач. Отображается в меню,
+	//     параметры space_id, env_id.
+	//
+	// Приложения так же могут использовать действия для вызова обработки в других приложениях при
+	// необходимости.
+	Action(context.Context, *ActionRequest) (*ActionResponse, error)
+	mustEmbedUnimplementedExtensionServiceServer()
+}
+
+// UnimplementedExtensionServiceServer must be embedded to have forward compatible implementations.
+type UnimplementedExtensionServiceServer struct {
+}
+
+func (UnimplementedExtensionServiceServer) Install(context.Context, *InstallRequest) (*common.Operation, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Install not implemented")
+}
+func (UnimplementedExtensionServiceServer) Uninstall(context.Context, *UninstallRequest) (*common.Operation, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Uninstall not implemented")
+}
+func (UnimplementedExtensionServiceServer) Check(context.Context, *CheckRequest) (*common.Operation, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Check not implemented")
+}
+func (UnimplementedExtensionServiceServer) Action(context.Context, *ActionRequest) (*ActionResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Action not implemented")
+}
+func (UnimplementedExtensionServiceServer) mustEmbedUnimplementedExtensionServiceServer() {}
+
+// UnsafeExtensionServiceServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to ExtensionServiceServer will
+// result in compilation errors.
+type UnsafeExtensionServiceServer interface {
+	mustEmbedUnimplementedExtensionServiceServer()
+}
+
+func RegisterExtensionServiceServer(s grpc.ServiceRegistrar, srv ExtensionServiceServer) {
+	s.RegisterService(&ExtensionService_ServiceDesc, srv)
+}
+
+func _ExtensionService_Install_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(InstallRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(ExtensionServiceServer).Install(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: ExtensionService_Install_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(ExtensionServiceServer).Install(ctx, req.(*InstallRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _ExtensionService_Uninstall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(UninstallRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(ExtensionServiceServer).Uninstall(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: ExtensionService_Uninstall_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(ExtensionServiceServer).Uninstall(ctx, req.(*UninstallRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _ExtensionService_Check_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(CheckRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(ExtensionServiceServer).Check(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: ExtensionService_Check_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(ExtensionServiceServer).Check(ctx, req.(*CheckRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _ExtensionService_Action_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(ActionRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(ExtensionServiceServer).Action(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: ExtensionService_Action_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(ExtensionServiceServer).Action(ctx, req.(*ActionRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+// ExtensionService_ServiceDesc is the grpc.ServiceDesc for ExtensionService service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var ExtensionService_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "extensions.ExtensionService",
+	HandlerType: (*ExtensionServiceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "Install",
+			Handler:    _ExtensionService_Install_Handler,
+		},
+		{
+			MethodName: "Uninstall",
+			Handler:    _ExtensionService_Uninstall_Handler,
+		},
+		{
+			MethodName: "Check",
+			Handler:    _ExtensionService_Check_Handler,
+		},
+		{
+			MethodName: "Action",
+			Handler:    _ExtensionService_Action_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "extensions/extension_service.proto",
+}