From 615585d5d1ae115e7dba08ebba5d3875743caad7 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Thu, 25 Sep 2025 13:32:16 +0200 Subject: [PATCH] feat: add `aibridgedserver` pkg (#19902) --- Makefile | 8 +- coderd/database/dump.sql | 3 +- ...72_aibridge_interception_metadata.down.sql | 1 + ...0372_aibridge_interception_metadata.up.sql | 1 + coderd/database/models.go | 9 +- coderd/database/queries.sql.go | 22 +- coderd/database/queries/aibridge.sql | 4 +- coderd/mcp/mcp.go | 3 + coderd/mcp/mcp_e2e_test.go | 22 +- .../x/aibridged}/proto/aibridged.pb.go | 577 +++++++------- .../x/aibridged}/proto/aibridged.proto | 0 .../x/aibridged}/proto/aibridged_drpc.pb.go | 56 +- .../x/aibridgedserver/aibridgedserver.go | 430 +++++++++++ .../aibridgedserver_internal_test.go | 88 +++ .../x/aibridgedserver/aibridgedserver_test.go | 710 ++++++++++++++++++ 15 files changed, 1587 insertions(+), 347 deletions(-) create mode 100644 coderd/database/migrations/000372_aibridge_interception_metadata.down.sql create mode 100644 coderd/database/migrations/000372_aibridge_interception_metadata.up.sql rename {aibridged => enterprise/x/aibridged}/proto/aibridged.pb.go (61%) rename {aibridged => enterprise/x/aibridged}/proto/aibridged.proto (100%) rename {aibridged => enterprise/x/aibridged}/proto/aibridged_drpc.pb.go (83%) create mode 100644 enterprise/x/aibridgedserver/aibridgedserver.go create mode 100644 enterprise/x/aibridgedserver/aibridgedserver_internal_test.go create mode 100644 enterprise/x/aibridgedserver/aibridgedserver_test.go diff --git a/Makefile b/Makefile index 66c65b5d76..526ddaf5b8 100644 --- a/Makefile +++ b/Makefile @@ -641,7 +641,7 @@ GEN_FILES := \ provisionersdk/proto/provisioner.pb.go \ provisionerd/proto/provisionerd.pb.go \ vpn/vpn.pb.go \ - aibridged/proto/aibridged.pb.go \ + enterprise/x/aibridged/proto/aibridged.pb.go \ $(DB_GEN_FILES) \ $(SITE_GEN_FILES) \ coderd/rbac/object_gen.go \ @@ -690,7 +690,7 @@ gen/mark-fresh: provisionersdk/proto/provisioner.pb.go \ provisionerd/proto/provisionerd.pb.go \ vpn/vpn.pb.go \ - aibridged/proto/aibridged.pb.go \ + enterprise/x/aibridged/proto/aibridged.pb.go \ coderd/database/dump.sql \ $(DB_GEN_FILES) \ site/src/api/typesGenerated.ts \ @@ -810,13 +810,13 @@ vpn/vpn.pb.go: vpn/vpn.proto --go_opt=paths=source_relative \ ./vpn/vpn.proto -aibridged/proto/aibridged.pb.go: aibridged/proto/aibridged.proto +enterprise/x/aibridged/proto/aibridged.pb.go: enterprise/x/aibridged/proto/aibridged.proto protoc \ --go_out=. \ --go_opt=paths=source_relative \ --go-drpc_out=. \ --go-drpc_opt=paths=source_relative \ - ./aibridged/proto/aibridged.proto + ./enterprise/x/aibridged/proto/aibridged.proto site/src/api/typesGenerated.ts: site/node_modules/.installed $(wildcard scripts/apitypings/*) $(shell find ./codersdk $(FIND_EXCLUSIONS) -type f -name '*.go') # -C sets the directory for the go run command diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index df1f581950..07b11cd511 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -990,7 +990,8 @@ CREATE TABLE aibridge_interceptions ( initiator_id uuid NOT NULL, provider text NOT NULL, model text NOT NULL, - started_at timestamp with time zone NOT NULL + started_at timestamp with time zone NOT NULL, + metadata jsonb ); COMMENT ON TABLE aibridge_interceptions IS 'Audit log of requests intercepted by AI Bridge'; diff --git a/coderd/database/migrations/000372_aibridge_interception_metadata.down.sql b/coderd/database/migrations/000372_aibridge_interception_metadata.down.sql new file mode 100644 index 0000000000..36b2a2ec61 --- /dev/null +++ b/coderd/database/migrations/000372_aibridge_interception_metadata.down.sql @@ -0,0 +1 @@ +ALTER TABLE aibridge_interceptions DROP COLUMN metadata; diff --git a/coderd/database/migrations/000372_aibridge_interception_metadata.up.sql b/coderd/database/migrations/000372_aibridge_interception_metadata.up.sql new file mode 100644 index 0000000000..db3b65c903 --- /dev/null +++ b/coderd/database/migrations/000372_aibridge_interception_metadata.up.sql @@ -0,0 +1 @@ +ALTER TABLE aibridge_interceptions ADD COLUMN metadata JSONB DEFAULT NULL; diff --git a/coderd/database/models.go b/coderd/database/models.go index d096b0e66b..d797132e15 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -3373,10 +3373,11 @@ func AllWorkspaceTransitionValues() []WorkspaceTransition { type AIBridgeInterception struct { ID uuid.UUID `db:"id" json:"id"` // Relates to a users record, but FK is elided for performance. - InitiatorID uuid.UUID `db:"initiator_id" json:"initiator_id"` - Provider string `db:"provider" json:"provider"` - Model string `db:"model" json:"model"` - StartedAt time.Time `db:"started_at" json:"started_at"` + InitiatorID uuid.UUID `db:"initiator_id" json:"initiator_id"` + Provider string `db:"provider" json:"provider"` + Model string `db:"model" json:"model"` + StartedAt time.Time `db:"started_at" json:"started_at"` + Metadata pqtype.NullRawMessage `db:"metadata" json:"metadata"` } // Audit log of tokens used by intercepted requests in AI Bridge diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 71715b7f18..f2e4e33c33 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -112,7 +112,7 @@ func (q *sqlQuerier) ActivityBumpWorkspace(ctx context.Context, arg ActivityBump } const getAIBridgeInterceptionByID = `-- name: GetAIBridgeInterceptionByID :one -SELECT id, initiator_id, provider, model, started_at FROM aibridge_interceptions WHERE id = $1::uuid +SELECT id, initiator_id, provider, model, started_at, metadata FROM aibridge_interceptions WHERE id = $1::uuid ` func (q *sqlQuerier) GetAIBridgeInterceptionByID(ctx context.Context, id uuid.UUID) (AIBridgeInterception, error) { @@ -124,22 +124,24 @@ func (q *sqlQuerier) GetAIBridgeInterceptionByID(ctx context.Context, id uuid.UU &i.Provider, &i.Model, &i.StartedAt, + &i.Metadata, ) return i, err } const insertAIBridgeInterception = `-- name: InsertAIBridgeInterception :one -INSERT INTO aibridge_interceptions (id, initiator_id, provider, model, started_at) -VALUES ($1::uuid, $2::uuid, $3, $4, $5) -RETURNING id, initiator_id, provider, model, started_at +INSERT INTO aibridge_interceptions (id, initiator_id, provider, model, metadata, started_at) +VALUES ($1::uuid, $2::uuid, $3, $4, COALESCE($5::jsonb, '{}'::jsonb), $6) +RETURNING id, initiator_id, provider, model, started_at, metadata ` type InsertAIBridgeInterceptionParams struct { - ID uuid.UUID `db:"id" json:"id"` - InitiatorID uuid.UUID `db:"initiator_id" json:"initiator_id"` - Provider string `db:"provider" json:"provider"` - Model string `db:"model" json:"model"` - StartedAt time.Time `db:"started_at" json:"started_at"` + ID uuid.UUID `db:"id" json:"id"` + InitiatorID uuid.UUID `db:"initiator_id" json:"initiator_id"` + Provider string `db:"provider" json:"provider"` + Model string `db:"model" json:"model"` + Metadata json.RawMessage `db:"metadata" json:"metadata"` + StartedAt time.Time `db:"started_at" json:"started_at"` } func (q *sqlQuerier) InsertAIBridgeInterception(ctx context.Context, arg InsertAIBridgeInterceptionParams) (AIBridgeInterception, error) { @@ -148,6 +150,7 @@ func (q *sqlQuerier) InsertAIBridgeInterception(ctx context.Context, arg InsertA arg.InitiatorID, arg.Provider, arg.Model, + arg.Metadata, arg.StartedAt, ) var i AIBridgeInterception @@ -157,6 +160,7 @@ func (q *sqlQuerier) InsertAIBridgeInterception(ctx context.Context, arg InsertA &i.Provider, &i.Model, &i.StartedAt, + &i.Metadata, ) return i, err } diff --git a/coderd/database/queries/aibridge.sql b/coderd/database/queries/aibridge.sql index 0416bebefe..863a5f3051 100644 --- a/coderd/database/queries/aibridge.sql +++ b/coderd/database/queries/aibridge.sql @@ -1,6 +1,6 @@ -- name: InsertAIBridgeInterception :one -INSERT INTO aibridge_interceptions (id, initiator_id, provider, model, started_at) -VALUES (@id::uuid, @initiator_id::uuid, @provider, @model, @started_at) +INSERT INTO aibridge_interceptions (id, initiator_id, provider, model, metadata, started_at) +VALUES (@id::uuid, @initiator_id::uuid, @provider, @model, COALESCE(@metadata::jsonb, '{}'::jsonb), @started_at) RETURNING *; -- name: InsertAIBridgeTokenUsage :exec diff --git a/coderd/mcp/mcp.go b/coderd/mcp/mcp.go index 3696beff50..ed73bf5485 100644 --- a/coderd/mcp/mcp.go +++ b/coderd/mcp/mcp.go @@ -24,6 +24,9 @@ const ( MCPServerName = "Coder" // MCPServerInstructions is the instructions text for the MCP server. MCPServerInstructions = "Coder MCP Server providing workspace and template management tools" + + // Used in tests and aibridge. + MCPEndpoint = "/api/experimental/mcp/http" ) // Server represents an MCP HTTP server instance diff --git a/coderd/mcp/mcp_e2e_test.go b/coderd/mcp/mcp_e2e_test.go index b831d150c2..2813757a50 100644 --- a/coderd/mcp/mcp_e2e_test.go +++ b/coderd/mcp/mcp_e2e_test.go @@ -34,7 +34,7 @@ func TestMCPHTTP_E2E_ClientIntegration(t *testing.T) { _ = coderdtest.CreateFirstUser(t, coderClient) // Create MCP client pointing to our endpoint - mcpURL := api.AccessURL.String() + "/api/experimental/mcp/http" + mcpURL := api.AccessURL.String() + mcpserver.MCPEndpoint // Configure client with authentication headers using RFC 6750 Bearer token mcpClient, err := mcpclient.NewStreamableHttpClient(mcpURL, @@ -133,7 +133,7 @@ func TestMCPHTTP_E2E_UnauthenticatedAccess(t *testing.T) { defer cancel() // Test direct HTTP request to verify 401 status code - mcpURL := api.AccessURL.String() + "/api/experimental/mcp/http" + mcpURL := api.AccessURL.String() + mcpserver.MCPEndpoint // Make a POST request without authentication (MCP over HTTP uses POST) //nolint:gosec // Test code using controlled localhost URL @@ -197,7 +197,7 @@ func TestMCPHTTP_E2E_ToolWithWorkspace(t *testing.T) { workspace := coderdtest.CreateWorkspace(t, coderClient, template.ID) // Create MCP client - mcpURL := api.AccessURL.String() + "/api/experimental/mcp/http" + mcpURL := api.AccessURL.String() + mcpserver.MCPEndpoint mcpClient, err := mcpclient.NewStreamableHttpClient(mcpURL, transport.WithHTTPHeaders(map[string]string{ "Authorization": "Bearer " + coderClient.SessionToken(), @@ -280,7 +280,7 @@ func TestMCPHTTP_E2E_ErrorHandling(t *testing.T) { _ = coderdtest.CreateFirstUser(t, coderClient) // Create MCP client - mcpURL := api.AccessURL.String() + "/api/experimental/mcp/http" + mcpURL := api.AccessURL.String() + mcpserver.MCPEndpoint mcpClient, err := mcpclient.NewStreamableHttpClient(mcpURL, transport.WithHTTPHeaders(map[string]string{ "Authorization": "Bearer " + coderClient.SessionToken(), @@ -339,7 +339,7 @@ func TestMCPHTTP_E2E_ConcurrentRequests(t *testing.T) { _ = coderdtest.CreateFirstUser(t, coderClient) // Create MCP client - mcpURL := api.AccessURL.String() + "/api/experimental/mcp/http" + mcpURL := api.AccessURL.String() + mcpserver.MCPEndpoint mcpClient, err := mcpclient.NewStreamableHttpClient(mcpURL, transport.WithHTTPHeaders(map[string]string{ "Authorization": "Bearer " + coderClient.SessionToken(), @@ -410,7 +410,7 @@ func TestMCPHTTP_E2E_RFC6750_UnauthenticatedRequest(t *testing.T) { // Make a request without any authentication headers req := &http.Request{ Method: "POST", - URL: mustParseURL(t, api.AccessURL.String()+"/api/experimental/mcp/http"), + URL: mustParseURL(t, api.AccessURL.String()+mcpserver.MCPEndpoint), Header: make(http.Header), } @@ -493,7 +493,7 @@ func TestMCPHTTP_E2E_OAuth2_EndToEnd(t *testing.T) { // In a real OAuth2 flow, this would be an OAuth2 access token sessionToken := coderClient.SessionToken() - mcpURL := api.AccessURL.String() + "/api/experimental/mcp/http" + mcpURL := api.AccessURL.String() + mcpserver.MCPEndpoint mcpClient, err := mcpclient.NewStreamableHttpClient(mcpURL, transport.WithHTTPHeaders(map[string]string{ "Authorization": "Bearer " + sessionToken, @@ -640,7 +640,7 @@ func TestMCPHTTP_E2E_OAuth2_EndToEnd(t *testing.T) { t.Logf("Successfully obtained refresh token: %s...", refreshToken[:10]) // Step 3: Use access token to authenticate with MCP endpoint - mcpURL := api.AccessURL.String() + "/api/experimental/mcp/http" + mcpURL := api.AccessURL.String() + mcpserver.MCPEndpoint mcpClient, err := mcpclient.NewStreamableHttpClient(mcpURL, transport.WithHTTPHeaders(map[string]string{ "Authorization": "Bearer " + accessToken, @@ -776,7 +776,7 @@ func TestMCPHTTP_E2E_OAuth2_EndToEnd(t *testing.T) { t.Parallel() req := &http.Request{ Method: "POST", - URL: mustParseURL(t, api.AccessURL.String()+"/api/experimental/mcp/http"), + URL: mustParseURL(t, api.AccessURL.String()+mcpserver.MCPEndpoint), Header: map[string][]string{ "Authorization": {"Bearer invalid_token_value"}, "Content-Type": {"application/json"}, @@ -805,7 +805,7 @@ func TestMCPHTTP_E2E_OAuth2_EndToEnd(t *testing.T) { t.Run("DynamicClientRegistrationWithMCPFlow", func(t *testing.T) { t.Parallel() // Step 1: Attempt unauthenticated MCP access - mcpURL := api.AccessURL.String() + "/api/experimental/mcp/http" + mcpURL := api.AccessURL.String() + mcpserver.MCPEndpoint req := &http.Request{ Method: "POST", URL: mustParseURL(t, mcpURL), @@ -1232,7 +1232,7 @@ func TestMCPHTTP_E2E_ChatGPTEndpoint(t *testing.T) { template := coderdtest.CreateTemplate(t, coderClient, user.OrganizationID, version.ID) // Create MCP client pointing to the ChatGPT endpoint - mcpURL := api.AccessURL.String() + "/api/experimental/mcp/http?toolset=chatgpt" + mcpURL := api.AccessURL.String() + mcpserver.MCPEndpoint + "?toolset=chatgpt" // Configure client with authentication headers using RFC 6750 Bearer token mcpClient, err := mcpclient.NewStreamableHttpClient(mcpURL, diff --git a/aibridged/proto/aibridged.pb.go b/enterprise/x/aibridged/proto/aibridged.pb.go similarity index 61% rename from aibridged/proto/aibridged.pb.go rename to enterprise/x/aibridged/proto/aibridged.pb.go index cef558f8aa..e88d0d1a9a 100644 --- a/aibridged/proto/aibridged.pb.go +++ b/enterprise/x/aibridged/proto/aibridged.pb.go @@ -2,7 +2,7 @@ // versions: // protoc-gen-go v1.30.0 // protoc v4.23.4 -// source: aibridged/proto/aibridged.proto +// source: enterprise/x/aibridged/proto/aibridged.proto package proto @@ -38,7 +38,7 @@ type RecordInterceptionRequest struct { func (x *RecordInterceptionRequest) Reset() { *x = RecordInterceptionRequest{} if protoimpl.UnsafeEnabled { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[0] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -51,7 +51,7 @@ func (x *RecordInterceptionRequest) String() string { func (*RecordInterceptionRequest) ProtoMessage() {} func (x *RecordInterceptionRequest) ProtoReflect() protoreflect.Message { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[0] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -64,7 +64,7 @@ func (x *RecordInterceptionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordInterceptionRequest.ProtoReflect.Descriptor instead. func (*RecordInterceptionRequest) Descriptor() ([]byte, []int) { - return file_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{0} + return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{0} } func (x *RecordInterceptionRequest) GetId() string { @@ -118,7 +118,7 @@ type RecordInterceptionResponse struct { func (x *RecordInterceptionResponse) Reset() { *x = RecordInterceptionResponse{} if protoimpl.UnsafeEnabled { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[1] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -131,7 +131,7 @@ func (x *RecordInterceptionResponse) String() string { func (*RecordInterceptionResponse) ProtoMessage() {} func (x *RecordInterceptionResponse) ProtoReflect() protoreflect.Message { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[1] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -144,7 +144,7 @@ func (x *RecordInterceptionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordInterceptionResponse.ProtoReflect.Descriptor instead. func (*RecordInterceptionResponse) Descriptor() ([]byte, []int) { - return file_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{1} + return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{1} } type RecordTokenUsageRequest struct { @@ -163,7 +163,7 @@ type RecordTokenUsageRequest struct { func (x *RecordTokenUsageRequest) Reset() { *x = RecordTokenUsageRequest{} if protoimpl.UnsafeEnabled { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[2] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -176,7 +176,7 @@ func (x *RecordTokenUsageRequest) String() string { func (*RecordTokenUsageRequest) ProtoMessage() {} func (x *RecordTokenUsageRequest) ProtoReflect() protoreflect.Message { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[2] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -189,7 +189,7 @@ func (x *RecordTokenUsageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordTokenUsageRequest.ProtoReflect.Descriptor instead. func (*RecordTokenUsageRequest) Descriptor() ([]byte, []int) { - return file_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{2} + return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{2} } func (x *RecordTokenUsageRequest) GetInterceptionId() string { @@ -243,7 +243,7 @@ type RecordTokenUsageResponse struct { func (x *RecordTokenUsageResponse) Reset() { *x = RecordTokenUsageResponse{} if protoimpl.UnsafeEnabled { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[3] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -256,7 +256,7 @@ func (x *RecordTokenUsageResponse) String() string { func (*RecordTokenUsageResponse) ProtoMessage() {} func (x *RecordTokenUsageResponse) ProtoReflect() protoreflect.Message { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[3] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -269,7 +269,7 @@ func (x *RecordTokenUsageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordTokenUsageResponse.ProtoReflect.Descriptor instead. func (*RecordTokenUsageResponse) Descriptor() ([]byte, []int) { - return file_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{3} + return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{3} } type RecordPromptUsageRequest struct { @@ -287,7 +287,7 @@ type RecordPromptUsageRequest struct { func (x *RecordPromptUsageRequest) Reset() { *x = RecordPromptUsageRequest{} if protoimpl.UnsafeEnabled { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[4] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -300,7 +300,7 @@ func (x *RecordPromptUsageRequest) String() string { func (*RecordPromptUsageRequest) ProtoMessage() {} func (x *RecordPromptUsageRequest) ProtoReflect() protoreflect.Message { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[4] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -313,7 +313,7 @@ func (x *RecordPromptUsageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordPromptUsageRequest.ProtoReflect.Descriptor instead. func (*RecordPromptUsageRequest) Descriptor() ([]byte, []int) { - return file_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{4} + return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{4} } func (x *RecordPromptUsageRequest) GetInterceptionId() string { @@ -360,7 +360,7 @@ type RecordPromptUsageResponse struct { func (x *RecordPromptUsageResponse) Reset() { *x = RecordPromptUsageResponse{} if protoimpl.UnsafeEnabled { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[5] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -373,7 +373,7 @@ func (x *RecordPromptUsageResponse) String() string { func (*RecordPromptUsageResponse) ProtoMessage() {} func (x *RecordPromptUsageResponse) ProtoReflect() protoreflect.Message { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[5] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -386,7 +386,7 @@ func (x *RecordPromptUsageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordPromptUsageResponse.ProtoReflect.Descriptor instead. func (*RecordPromptUsageResponse) Descriptor() ([]byte, []int) { - return file_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{5} + return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{5} } type RecordToolUsageRequest struct { @@ -408,7 +408,7 @@ type RecordToolUsageRequest struct { func (x *RecordToolUsageRequest) Reset() { *x = RecordToolUsageRequest{} if protoimpl.UnsafeEnabled { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[6] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -421,7 +421,7 @@ func (x *RecordToolUsageRequest) String() string { func (*RecordToolUsageRequest) ProtoMessage() {} func (x *RecordToolUsageRequest) ProtoReflect() protoreflect.Message { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[6] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -434,7 +434,7 @@ func (x *RecordToolUsageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordToolUsageRequest.ProtoReflect.Descriptor instead. func (*RecordToolUsageRequest) Descriptor() ([]byte, []int) { - return file_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{6} + return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{6} } func (x *RecordToolUsageRequest) GetInterceptionId() string { @@ -509,7 +509,7 @@ type RecordToolUsageResponse struct { func (x *RecordToolUsageResponse) Reset() { *x = RecordToolUsageResponse{} if protoimpl.UnsafeEnabled { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[7] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -522,7 +522,7 @@ func (x *RecordToolUsageResponse) String() string { func (*RecordToolUsageResponse) ProtoMessage() {} func (x *RecordToolUsageResponse) ProtoReflect() protoreflect.Message { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[7] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -535,7 +535,7 @@ func (x *RecordToolUsageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordToolUsageResponse.ProtoReflect.Descriptor instead. func (*RecordToolUsageResponse) Descriptor() ([]byte, []int) { - return file_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{7} + return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{7} } type GetMCPServerConfigsRequest struct { @@ -549,7 +549,7 @@ type GetMCPServerConfigsRequest struct { func (x *GetMCPServerConfigsRequest) Reset() { *x = GetMCPServerConfigsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[8] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -562,7 +562,7 @@ func (x *GetMCPServerConfigsRequest) String() string { func (*GetMCPServerConfigsRequest) ProtoMessage() {} func (x *GetMCPServerConfigsRequest) ProtoReflect() protoreflect.Message { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[8] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -575,7 +575,7 @@ func (x *GetMCPServerConfigsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMCPServerConfigsRequest.ProtoReflect.Descriptor instead. func (*GetMCPServerConfigsRequest) Descriptor() ([]byte, []int) { - return file_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{8} + return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{8} } func (x *GetMCPServerConfigsRequest) GetUserId() string { @@ -597,7 +597,7 @@ type GetMCPServerConfigsResponse struct { func (x *GetMCPServerConfigsResponse) Reset() { *x = GetMCPServerConfigsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[9] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -610,7 +610,7 @@ func (x *GetMCPServerConfigsResponse) String() string { func (*GetMCPServerConfigsResponse) ProtoMessage() {} func (x *GetMCPServerConfigsResponse) ProtoReflect() protoreflect.Message { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[9] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -623,7 +623,7 @@ func (x *GetMCPServerConfigsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMCPServerConfigsResponse.ProtoReflect.Descriptor instead. func (*GetMCPServerConfigsResponse) Descriptor() ([]byte, []int) { - return file_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{9} + return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{9} } func (x *GetMCPServerConfigsResponse) GetCoderMcpConfig() *MCPServerConfig { @@ -654,7 +654,7 @@ type MCPServerConfig struct { func (x *MCPServerConfig) Reset() { *x = MCPServerConfig{} if protoimpl.UnsafeEnabled { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[10] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -667,7 +667,7 @@ func (x *MCPServerConfig) String() string { func (*MCPServerConfig) ProtoMessage() {} func (x *MCPServerConfig) ProtoReflect() protoreflect.Message { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[10] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -680,7 +680,7 @@ func (x *MCPServerConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use MCPServerConfig.ProtoReflect.Descriptor instead. func (*MCPServerConfig) Descriptor() ([]byte, []int) { - return file_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{10} + return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{10} } func (x *MCPServerConfig) GetId() string { @@ -723,7 +723,7 @@ type GetMCPServerAccessTokensBatchRequest struct { func (x *GetMCPServerAccessTokensBatchRequest) Reset() { *x = GetMCPServerAccessTokensBatchRequest{} if protoimpl.UnsafeEnabled { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[11] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -736,7 +736,7 @@ func (x *GetMCPServerAccessTokensBatchRequest) String() string { func (*GetMCPServerAccessTokensBatchRequest) ProtoMessage() {} func (x *GetMCPServerAccessTokensBatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[11] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -749,7 +749,7 @@ func (x *GetMCPServerAccessTokensBatchRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use GetMCPServerAccessTokensBatchRequest.ProtoReflect.Descriptor instead. func (*GetMCPServerAccessTokensBatchRequest) Descriptor() ([]byte, []int) { - return file_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{11} + return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{11} } func (x *GetMCPServerAccessTokensBatchRequest) GetUserId() string { @@ -780,7 +780,7 @@ type GetMCPServerAccessTokensBatchResponse struct { func (x *GetMCPServerAccessTokensBatchResponse) Reset() { *x = GetMCPServerAccessTokensBatchResponse{} if protoimpl.UnsafeEnabled { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[12] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -793,7 +793,7 @@ func (x *GetMCPServerAccessTokensBatchResponse) String() string { func (*GetMCPServerAccessTokensBatchResponse) ProtoMessage() {} func (x *GetMCPServerAccessTokensBatchResponse) ProtoReflect() protoreflect.Message { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[12] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -806,7 +806,7 @@ func (x *GetMCPServerAccessTokensBatchResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use GetMCPServerAccessTokensBatchResponse.ProtoReflect.Descriptor instead. func (*GetMCPServerAccessTokensBatchResponse) Descriptor() ([]byte, []int) { - return file_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{12} + return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{12} } func (x *GetMCPServerAccessTokensBatchResponse) GetAccessTokens() map[string]string { @@ -834,7 +834,7 @@ type IsAuthorizedRequest struct { func (x *IsAuthorizedRequest) Reset() { *x = IsAuthorizedRequest{} if protoimpl.UnsafeEnabled { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[13] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -847,7 +847,7 @@ func (x *IsAuthorizedRequest) String() string { func (*IsAuthorizedRequest) ProtoMessage() {} func (x *IsAuthorizedRequest) ProtoReflect() protoreflect.Message { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[13] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -860,7 +860,7 @@ func (x *IsAuthorizedRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use IsAuthorizedRequest.ProtoReflect.Descriptor instead. func (*IsAuthorizedRequest) Descriptor() ([]byte, []int) { - return file_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{13} + return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{13} } func (x *IsAuthorizedRequest) GetKey() string { @@ -881,7 +881,7 @@ type IsAuthorizedResponse struct { func (x *IsAuthorizedResponse) Reset() { *x = IsAuthorizedResponse{} if protoimpl.UnsafeEnabled { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[14] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -894,7 +894,7 @@ func (x *IsAuthorizedResponse) String() string { func (*IsAuthorizedResponse) ProtoMessage() {} func (x *IsAuthorizedResponse) ProtoReflect() protoreflect.Message { - mi := &file_aibridged_proto_aibridged_proto_msgTypes[14] + mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -907,7 +907,7 @@ func (x *IsAuthorizedResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use IsAuthorizedResponse.ProtoReflect.Descriptor instead. func (*IsAuthorizedResponse) Descriptor() ([]byte, []int) { - return file_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{14} + return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{14} } func (x *IsAuthorizedResponse) GetOwnerId() string { @@ -917,55 +917,109 @@ func (x *IsAuthorizedResponse) GetOwnerId() string { return "" } -var File_aibridged_proto_aibridged_proto protoreflect.FileDescriptor +var File_enterprise_x_aibridged_proto_aibridged_proto protoreflect.FileDescriptor -var file_aibridged_proto_aibridged_proto_rawDesc = []byte{ - 0x0a, 0x1f, 0x61, 0x69, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2f, 0x61, 0x69, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x12, 0x05, 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, 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, 0x22, 0xda, 0x02, 0x0a, 0x19, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, - 0x74, 0x6f, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x4a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 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, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 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, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x41, 0x74, 0x1a, 0x51, - 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, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 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, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x1c, 0x0a, 0x1a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, - 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0xf9, 0x02, 0x0a, 0x17, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, - 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x69, - 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x23, - 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x73, 0x12, 0x48, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, - 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, +var file_enterprise_x_aibridged_proto_aibridged_proto_rawDesc = []byte{ + 0x0a, 0x2c, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x2f, 0x78, 0x2f, 0x61, + 0x69, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, + 0x69, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, + 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, + 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, 0x22, 0xda, 0x02, 0x0a, 0x19, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, + 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x14, + 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, + 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x4a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 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, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 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, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x41, 0x74, 0x1a, 0x51, 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, 0x2a, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 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, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x1c, + 0x0a, 0x1a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xf9, 0x02, 0x0a, + 0x17, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x49, + 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x70, 0x75, + 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x69, 0x6e, 0x70, 0x75, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x12, 0x48, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 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, 0x39, 0x0a, 0x0a, 0x63, 0x72, + 0x65, 0x61, 0x74, 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, 0x09, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x1a, 0x51, 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, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 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, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x1a, 0x0a, 0x18, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xcb, 0x02, 0x0a, 0x18, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, + 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6d, 0x73, + 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x73, 0x67, 0x49, + 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x70, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x12, 0x49, 0x0a, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, + 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 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, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x18, 0x05, 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, 0x1a, + 0x51, 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, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 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, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0x1b, 0x0a, 0x19, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, + 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0xed, 0x03, 0x0a, 0x16, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, + 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0a, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, + 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x12, + 0x0a, 0x04, 0x74, 0x6f, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x6f, + 0x6f, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x6a, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x6a, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x10, 0x69, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, + 0x52, 0x0f, 0x69, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x88, 0x01, 0x01, 0x12, 0x47, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 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, 0x39, 0x0a, - 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x09, 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, 0x1a, 0x51, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, @@ -973,184 +1027,131 @@ var file_aibridged_proto_aibridged_proto_rawDesc = []byte{ 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 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, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x1a, 0x0a, 0x18, 0x52, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xcb, 0x02, 0x0a, 0x18, 0x52, 0x65, 0x63, 0x6f, - 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x15, 0x0a, - 0x06, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, - 0x73, 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x12, 0x49, 0x0a, 0x08, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, - 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 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, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 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, 0x1a, 0x51, 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, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 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, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x1b, 0x0a, 0x19, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, - 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0xed, 0x03, 0x0a, 0x16, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, - 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, - 0x0f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x22, 0x0a, - 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x00, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x88, 0x01, - 0x01, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x6f, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x74, 0x6f, 0x6f, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, - 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, - 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x10, 0x69, 0x6e, 0x76, 0x6f, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x01, 0x52, 0x0f, 0x69, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x47, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 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, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x09, - 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, 0x1a, 0x51, 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, 0x2a, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 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, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0d, - 0x0a, 0x0b, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x42, 0x13, 0x0a, - 0x11, 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x22, 0x19, 0x0a, 0x17, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, - 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x0a, - 0x1a, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x22, 0xb2, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x10, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x6d, 0x63, - 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x4d, 0x63, 0x70, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x51, 0x0a, 0x19, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x63, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x16, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x4d, - 0x63, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x22, 0x85, 0x01, 0x0a, 0x0f, 0x4d, 0x43, - 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, - 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, - 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x6f, 0x6c, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x65, - 0x67, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x6f, 0x6f, 0x6c, 0x41, - 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x67, 0x65, 0x78, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x6f, 0x6f, - 0x6c, 0x5f, 0x64, 0x65, 0x6e, 0x79, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x74, 0x6f, 0x6f, 0x6c, 0x44, 0x65, 0x6e, 0x79, 0x52, 0x65, 0x67, 0x65, - 0x78, 0x22, 0x72, 0x0a, 0x24, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, - 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x31, 0x0a, 0x15, 0x6d, 0x63, 0x70, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x12, 0x6d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x49, 0x64, 0x73, 0x22, 0xda, 0x02, 0x0a, 0x25, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x69, + 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, + 0x19, 0x0a, 0x17, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, + 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x0a, 0x1a, 0x47, 0x65, + 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x22, 0xb2, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x40, 0x0a, 0x10, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x6d, 0x63, 0x70, 0x5f, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x0e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x4d, 0x63, 0x70, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x51, 0x0a, 0x19, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, + 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x63, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, + 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x16, + 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x63, 0x70, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x22, 0x85, 0x01, 0x0a, 0x0f, 0x4d, 0x43, 0x50, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, + 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x28, 0x0a, 0x10, + 0x74, 0x6f, 0x6f, 0x6c, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x6f, 0x6f, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, + 0x77, 0x52, 0x65, 0x67, 0x65, 0x78, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x6f, 0x6f, 0x6c, 0x5f, 0x64, + 0x65, 0x6e, 0x79, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x74, 0x6f, 0x6f, 0x6c, 0x44, 0x65, 0x6e, 0x79, 0x52, 0x65, 0x67, 0x65, 0x78, 0x22, 0x72, + 0x0a, 0x24, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x31, 0x0a, 0x15, 0x6d, 0x63, 0x70, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, + 0x6d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, + 0x64, 0x73, 0x22, 0xda, 0x02, 0x0a, 0x25, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, + 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x0d, + 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, + 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x73, 0x12, 0x50, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x38, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x63, 0x0a, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, - 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x50, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, - 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 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, 0x1a, 0x39, 0x0a, 0x0b, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x73, 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, 0x27, 0x0a, 0x13, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, - 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x31, 0x0a, 0x14, 0x49, - 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x32, 0xe4, - 0x02, 0x0a, 0x08, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x59, 0x0a, 0x12, 0x52, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, - 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, - 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, - 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x52, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, - 0x12, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, - 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, - 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, - 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xeb, 0x01, 0x0a, 0x0f, 0x4d, 0x43, 0x50, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x5c, 0x0a, 0x13, 0x47, 0x65, 0x74, - 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, - 0x12, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, - 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x4d, 0x43, - 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x2b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, + 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x73, 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, 0x1a, 0x39, 0x0a, 0x0b, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 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, + 0x27, 0x0a, 0x13, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x31, 0x0a, 0x14, 0x49, 0x73, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x32, 0xe4, 0x02, 0x0a, 0x08, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x59, 0x0a, 0x12, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, + 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, + 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x50, 0x0a, 0x0f, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, + 0x61, 0x67, 0x65, 0x12, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x32, 0xeb, 0x01, 0x0a, 0x0f, 0x4d, 0x43, 0x50, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x5c, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x21, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x2b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x32, 0x55, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, - 0x72, 0x12, 0x47, 0x0a, 0x0c, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, - 0x64, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, - 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x69, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, - 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, + 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x32, 0x55, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x12, 0x47, + 0x0a, 0x0c, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x1a, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x69, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x64, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( - file_aibridged_proto_aibridged_proto_rawDescOnce sync.Once - file_aibridged_proto_aibridged_proto_rawDescData = file_aibridged_proto_aibridged_proto_rawDesc + file_enterprise_x_aibridged_proto_aibridged_proto_rawDescOnce sync.Once + file_enterprise_x_aibridged_proto_aibridged_proto_rawDescData = file_enterprise_x_aibridged_proto_aibridged_proto_rawDesc ) -func file_aibridged_proto_aibridged_proto_rawDescGZIP() []byte { - file_aibridged_proto_aibridged_proto_rawDescOnce.Do(func() { - file_aibridged_proto_aibridged_proto_rawDescData = protoimpl.X.CompressGZIP(file_aibridged_proto_aibridged_proto_rawDescData) +func file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP() []byte { + file_enterprise_x_aibridged_proto_aibridged_proto_rawDescOnce.Do(func() { + file_enterprise_x_aibridged_proto_aibridged_proto_rawDescData = protoimpl.X.CompressGZIP(file_enterprise_x_aibridged_proto_aibridged_proto_rawDescData) }) - return file_aibridged_proto_aibridged_proto_rawDescData + return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescData } -var file_aibridged_proto_aibridged_proto_msgTypes = make([]protoimpl.MessageInfo, 21) -var file_aibridged_proto_aibridged_proto_goTypes = []interface{}{ +var file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes = make([]protoimpl.MessageInfo, 21) +var file_enterprise_x_aibridged_proto_aibridged_proto_goTypes = []interface{}{ (*RecordInterceptionRequest)(nil), // 0: proto.RecordInterceptionRequest (*RecordInterceptionResponse)(nil), // 1: proto.RecordInterceptionResponse (*RecordTokenUsageRequest)(nil), // 2: proto.RecordTokenUsageRequest @@ -1175,7 +1176,7 @@ var file_aibridged_proto_aibridged_proto_goTypes = []interface{}{ (*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp (*anypb.Any)(nil), // 22: google.protobuf.Any } -var file_aibridged_proto_aibridged_proto_depIdxs = []int32{ +var file_enterprise_x_aibridged_proto_aibridged_proto_depIdxs = []int32{ 15, // 0: proto.RecordInterceptionRequest.metadata:type_name -> proto.RecordInterceptionRequest.MetadataEntry 21, // 1: proto.RecordInterceptionRequest.started_at:type_name -> google.protobuf.Timestamp 16, // 2: proto.RecordTokenUsageRequest.metadata:type_name -> proto.RecordTokenUsageRequest.MetadataEntry @@ -1213,13 +1214,13 @@ var file_aibridged_proto_aibridged_proto_depIdxs = []int32{ 0, // [0:16] is the sub-list for field type_name } -func init() { file_aibridged_proto_aibridged_proto_init() } -func file_aibridged_proto_aibridged_proto_init() { - if File_aibridged_proto_aibridged_proto != nil { +func init() { file_enterprise_x_aibridged_proto_aibridged_proto_init() } +func file_enterprise_x_aibridged_proto_aibridged_proto_init() { + if File_enterprise_x_aibridged_proto_aibridged_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_aibridged_proto_aibridged_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordInterceptionRequest); i { case 0: return &v.state @@ -1231,7 +1232,7 @@ func file_aibridged_proto_aibridged_proto_init() { return nil } } - file_aibridged_proto_aibridged_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordInterceptionResponse); i { case 0: return &v.state @@ -1243,7 +1244,7 @@ func file_aibridged_proto_aibridged_proto_init() { return nil } } - file_aibridged_proto_aibridged_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordTokenUsageRequest); i { case 0: return &v.state @@ -1255,7 +1256,7 @@ func file_aibridged_proto_aibridged_proto_init() { return nil } } - file_aibridged_proto_aibridged_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordTokenUsageResponse); i { case 0: return &v.state @@ -1267,7 +1268,7 @@ func file_aibridged_proto_aibridged_proto_init() { return nil } } - file_aibridged_proto_aibridged_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordPromptUsageRequest); i { case 0: return &v.state @@ -1279,7 +1280,7 @@ func file_aibridged_proto_aibridged_proto_init() { return nil } } - file_aibridged_proto_aibridged_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordPromptUsageResponse); i { case 0: return &v.state @@ -1291,7 +1292,7 @@ func file_aibridged_proto_aibridged_proto_init() { return nil } } - file_aibridged_proto_aibridged_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordToolUsageRequest); i { case 0: return &v.state @@ -1303,7 +1304,7 @@ func file_aibridged_proto_aibridged_proto_init() { return nil } } - file_aibridged_proto_aibridged_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordToolUsageResponse); i { case 0: return &v.state @@ -1315,7 +1316,7 @@ func file_aibridged_proto_aibridged_proto_init() { return nil } } - file_aibridged_proto_aibridged_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetMCPServerConfigsRequest); i { case 0: return &v.state @@ -1327,7 +1328,7 @@ func file_aibridged_proto_aibridged_proto_init() { return nil } } - file_aibridged_proto_aibridged_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetMCPServerConfigsResponse); i { case 0: return &v.state @@ -1339,7 +1340,7 @@ func file_aibridged_proto_aibridged_proto_init() { return nil } } - file_aibridged_proto_aibridged_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MCPServerConfig); i { case 0: return &v.state @@ -1351,7 +1352,7 @@ func file_aibridged_proto_aibridged_proto_init() { return nil } } - file_aibridged_proto_aibridged_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetMCPServerAccessTokensBatchRequest); i { case 0: return &v.state @@ -1363,7 +1364,7 @@ func file_aibridged_proto_aibridged_proto_init() { return nil } } - file_aibridged_proto_aibridged_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetMCPServerAccessTokensBatchResponse); i { case 0: return &v.state @@ -1375,7 +1376,7 @@ func file_aibridged_proto_aibridged_proto_init() { return nil } } - file_aibridged_proto_aibridged_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*IsAuthorizedRequest); i { case 0: return &v.state @@ -1387,7 +1388,7 @@ func file_aibridged_proto_aibridged_proto_init() { return nil } } - file_aibridged_proto_aibridged_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*IsAuthorizedResponse); i { case 0: return &v.state @@ -1400,23 +1401,23 @@ func file_aibridged_proto_aibridged_proto_init() { } } } - file_aibridged_proto_aibridged_proto_msgTypes[6].OneofWrappers = []interface{}{} + file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[6].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_aibridged_proto_aibridged_proto_rawDesc, + RawDescriptor: file_enterprise_x_aibridged_proto_aibridged_proto_rawDesc, NumEnums: 0, NumMessages: 21, NumExtensions: 0, NumServices: 3, }, - GoTypes: file_aibridged_proto_aibridged_proto_goTypes, - DependencyIndexes: file_aibridged_proto_aibridged_proto_depIdxs, - MessageInfos: file_aibridged_proto_aibridged_proto_msgTypes, + GoTypes: file_enterprise_x_aibridged_proto_aibridged_proto_goTypes, + DependencyIndexes: file_enterprise_x_aibridged_proto_aibridged_proto_depIdxs, + MessageInfos: file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes, }.Build() - File_aibridged_proto_aibridged_proto = out.File - file_aibridged_proto_aibridged_proto_rawDesc = nil - file_aibridged_proto_aibridged_proto_goTypes = nil - file_aibridged_proto_aibridged_proto_depIdxs = nil + File_enterprise_x_aibridged_proto_aibridged_proto = out.File + file_enterprise_x_aibridged_proto_aibridged_proto_rawDesc = nil + file_enterprise_x_aibridged_proto_aibridged_proto_goTypes = nil + file_enterprise_x_aibridged_proto_aibridged_proto_depIdxs = nil } diff --git a/aibridged/proto/aibridged.proto b/enterprise/x/aibridged/proto/aibridged.proto similarity index 100% rename from aibridged/proto/aibridged.proto rename to enterprise/x/aibridged/proto/aibridged.proto diff --git a/aibridged/proto/aibridged_drpc.pb.go b/enterprise/x/aibridged/proto/aibridged_drpc.pb.go similarity index 83% rename from aibridged/proto/aibridged_drpc.pb.go rename to enterprise/x/aibridged/proto/aibridged_drpc.pb.go index 76ecf37e6b..37c2cc71ff 100644 --- a/aibridged/proto/aibridged_drpc.pb.go +++ b/enterprise/x/aibridged/proto/aibridged_drpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-drpc. DO NOT EDIT. // protoc-gen-go-drpc version: v0.0.34 -// source: aibridged/proto/aibridged.proto +// source: enterprise/x/aibridged/proto/aibridged.proto package proto @@ -13,25 +13,25 @@ import ( drpcerr "storj.io/drpc/drpcerr" ) -type drpcEncoding_File_aibridged_proto_aibridged_proto struct{} +type drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto struct{} -func (drpcEncoding_File_aibridged_proto_aibridged_proto) Marshal(msg drpc.Message) ([]byte, error) { +func (drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto) Marshal(msg drpc.Message) ([]byte, error) { return proto.Marshal(msg.(proto.Message)) } -func (drpcEncoding_File_aibridged_proto_aibridged_proto) MarshalAppend(buf []byte, msg drpc.Message) ([]byte, error) { +func (drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto) MarshalAppend(buf []byte, msg drpc.Message) ([]byte, error) { return proto.MarshalOptions{}.MarshalAppend(buf, msg.(proto.Message)) } -func (drpcEncoding_File_aibridged_proto_aibridged_proto) Unmarshal(buf []byte, msg drpc.Message) error { +func (drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto) Unmarshal(buf []byte, msg drpc.Message) error { return proto.Unmarshal(buf, msg.(proto.Message)) } -func (drpcEncoding_File_aibridged_proto_aibridged_proto) JSONMarshal(msg drpc.Message) ([]byte, error) { +func (drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto) JSONMarshal(msg drpc.Message) ([]byte, error) { return protojson.Marshal(msg.(proto.Message)) } -func (drpcEncoding_File_aibridged_proto_aibridged_proto) JSONUnmarshal(buf []byte, msg drpc.Message) error { +func (drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto) JSONUnmarshal(buf []byte, msg drpc.Message) error { return protojson.Unmarshal(buf, msg.(proto.Message)) } @@ -56,7 +56,7 @@ func (c *drpcRecorderClient) DRPCConn() drpc.Conn { return c.cc } func (c *drpcRecorderClient) RecordInterception(ctx context.Context, in *RecordInterceptionRequest) (*RecordInterceptionResponse, error) { out := new(RecordInterceptionResponse) - err := c.cc.Invoke(ctx, "/proto.Recorder/RecordInterception", drpcEncoding_File_aibridged_proto_aibridged_proto{}, in, out) + err := c.cc.Invoke(ctx, "/proto.Recorder/RecordInterception", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, in, out) if err != nil { return nil, err } @@ -65,7 +65,7 @@ func (c *drpcRecorderClient) RecordInterception(ctx context.Context, in *RecordI func (c *drpcRecorderClient) RecordTokenUsage(ctx context.Context, in *RecordTokenUsageRequest) (*RecordTokenUsageResponse, error) { out := new(RecordTokenUsageResponse) - err := c.cc.Invoke(ctx, "/proto.Recorder/RecordTokenUsage", drpcEncoding_File_aibridged_proto_aibridged_proto{}, in, out) + err := c.cc.Invoke(ctx, "/proto.Recorder/RecordTokenUsage", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, in, out) if err != nil { return nil, err } @@ -74,7 +74,7 @@ func (c *drpcRecorderClient) RecordTokenUsage(ctx context.Context, in *RecordTok func (c *drpcRecorderClient) RecordPromptUsage(ctx context.Context, in *RecordPromptUsageRequest) (*RecordPromptUsageResponse, error) { out := new(RecordPromptUsageResponse) - err := c.cc.Invoke(ctx, "/proto.Recorder/RecordPromptUsage", drpcEncoding_File_aibridged_proto_aibridged_proto{}, in, out) + err := c.cc.Invoke(ctx, "/proto.Recorder/RecordPromptUsage", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, in, out) if err != nil { return nil, err } @@ -83,7 +83,7 @@ func (c *drpcRecorderClient) RecordPromptUsage(ctx context.Context, in *RecordPr func (c *drpcRecorderClient) RecordToolUsage(ctx context.Context, in *RecordToolUsageRequest) (*RecordToolUsageResponse, error) { out := new(RecordToolUsageResponse) - err := c.cc.Invoke(ctx, "/proto.Recorder/RecordToolUsage", drpcEncoding_File_aibridged_proto_aibridged_proto{}, in, out) + err := c.cc.Invoke(ctx, "/proto.Recorder/RecordToolUsage", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, in, out) if err != nil { return nil, err } @@ -122,7 +122,7 @@ func (DRPCRecorderDescription) NumMethods() int { return 4 } func (DRPCRecorderDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) { switch n { case 0: - return "/proto.Recorder/RecordInterception", drpcEncoding_File_aibridged_proto_aibridged_proto{}, + return "/proto.Recorder/RecordInterception", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { return srv.(DRPCRecorderServer). RecordInterception( @@ -131,7 +131,7 @@ func (DRPCRecorderDescription) Method(n int) (string, drpc.Encoding, drpc.Receiv ) }, DRPCRecorderServer.RecordInterception, true case 1: - return "/proto.Recorder/RecordTokenUsage", drpcEncoding_File_aibridged_proto_aibridged_proto{}, + return "/proto.Recorder/RecordTokenUsage", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { return srv.(DRPCRecorderServer). RecordTokenUsage( @@ -140,7 +140,7 @@ func (DRPCRecorderDescription) Method(n int) (string, drpc.Encoding, drpc.Receiv ) }, DRPCRecorderServer.RecordTokenUsage, true case 2: - return "/proto.Recorder/RecordPromptUsage", drpcEncoding_File_aibridged_proto_aibridged_proto{}, + return "/proto.Recorder/RecordPromptUsage", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { return srv.(DRPCRecorderServer). RecordPromptUsage( @@ -149,7 +149,7 @@ func (DRPCRecorderDescription) Method(n int) (string, drpc.Encoding, drpc.Receiv ) }, DRPCRecorderServer.RecordPromptUsage, true case 3: - return "/proto.Recorder/RecordToolUsage", drpcEncoding_File_aibridged_proto_aibridged_proto{}, + return "/proto.Recorder/RecordToolUsage", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { return srv.(DRPCRecorderServer). RecordToolUsage( @@ -176,7 +176,7 @@ type drpcRecorder_RecordInterceptionStream struct { } func (x *drpcRecorder_RecordInterceptionStream) SendAndClose(m *RecordInterceptionResponse) error { - if err := x.MsgSend(m, drpcEncoding_File_aibridged_proto_aibridged_proto{}); err != nil { + if err := x.MsgSend(m, drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}); err != nil { return err } return x.CloseSend() @@ -192,7 +192,7 @@ type drpcRecorder_RecordTokenUsageStream struct { } func (x *drpcRecorder_RecordTokenUsageStream) SendAndClose(m *RecordTokenUsageResponse) error { - if err := x.MsgSend(m, drpcEncoding_File_aibridged_proto_aibridged_proto{}); err != nil { + if err := x.MsgSend(m, drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}); err != nil { return err } return x.CloseSend() @@ -208,7 +208,7 @@ type drpcRecorder_RecordPromptUsageStream struct { } func (x *drpcRecorder_RecordPromptUsageStream) SendAndClose(m *RecordPromptUsageResponse) error { - if err := x.MsgSend(m, drpcEncoding_File_aibridged_proto_aibridged_proto{}); err != nil { + if err := x.MsgSend(m, drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}); err != nil { return err } return x.CloseSend() @@ -224,7 +224,7 @@ type drpcRecorder_RecordToolUsageStream struct { } func (x *drpcRecorder_RecordToolUsageStream) SendAndClose(m *RecordToolUsageResponse) error { - if err := x.MsgSend(m, drpcEncoding_File_aibridged_proto_aibridged_proto{}); err != nil { + if err := x.MsgSend(m, drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}); err != nil { return err } return x.CloseSend() @@ -249,7 +249,7 @@ func (c *drpcMCPConfiguratorClient) DRPCConn() drpc.Conn { return c.cc } func (c *drpcMCPConfiguratorClient) GetMCPServerConfigs(ctx context.Context, in *GetMCPServerConfigsRequest) (*GetMCPServerConfigsResponse, error) { out := new(GetMCPServerConfigsResponse) - err := c.cc.Invoke(ctx, "/proto.MCPConfigurator/GetMCPServerConfigs", drpcEncoding_File_aibridged_proto_aibridged_proto{}, in, out) + err := c.cc.Invoke(ctx, "/proto.MCPConfigurator/GetMCPServerConfigs", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, in, out) if err != nil { return nil, err } @@ -258,7 +258,7 @@ func (c *drpcMCPConfiguratorClient) GetMCPServerConfigs(ctx context.Context, in func (c *drpcMCPConfiguratorClient) GetMCPServerAccessTokensBatch(ctx context.Context, in *GetMCPServerAccessTokensBatchRequest) (*GetMCPServerAccessTokensBatchResponse, error) { out := new(GetMCPServerAccessTokensBatchResponse) - err := c.cc.Invoke(ctx, "/proto.MCPConfigurator/GetMCPServerAccessTokensBatch", drpcEncoding_File_aibridged_proto_aibridged_proto{}, in, out) + err := c.cc.Invoke(ctx, "/proto.MCPConfigurator/GetMCPServerAccessTokensBatch", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, in, out) if err != nil { return nil, err } @@ -287,7 +287,7 @@ func (DRPCMCPConfiguratorDescription) NumMethods() int { return 2 } func (DRPCMCPConfiguratorDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) { switch n { case 0: - return "/proto.MCPConfigurator/GetMCPServerConfigs", drpcEncoding_File_aibridged_proto_aibridged_proto{}, + return "/proto.MCPConfigurator/GetMCPServerConfigs", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { return srv.(DRPCMCPConfiguratorServer). GetMCPServerConfigs( @@ -296,7 +296,7 @@ func (DRPCMCPConfiguratorDescription) Method(n int) (string, drpc.Encoding, drpc ) }, DRPCMCPConfiguratorServer.GetMCPServerConfigs, true case 1: - return "/proto.MCPConfigurator/GetMCPServerAccessTokensBatch", drpcEncoding_File_aibridged_proto_aibridged_proto{}, + return "/proto.MCPConfigurator/GetMCPServerAccessTokensBatch", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { return srv.(DRPCMCPConfiguratorServer). GetMCPServerAccessTokensBatch( @@ -323,7 +323,7 @@ type drpcMCPConfigurator_GetMCPServerConfigsStream struct { } func (x *drpcMCPConfigurator_GetMCPServerConfigsStream) SendAndClose(m *GetMCPServerConfigsResponse) error { - if err := x.MsgSend(m, drpcEncoding_File_aibridged_proto_aibridged_proto{}); err != nil { + if err := x.MsgSend(m, drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}); err != nil { return err } return x.CloseSend() @@ -339,7 +339,7 @@ type drpcMCPConfigurator_GetMCPServerAccessTokensBatchStream struct { } func (x *drpcMCPConfigurator_GetMCPServerAccessTokensBatchStream) SendAndClose(m *GetMCPServerAccessTokensBatchResponse) error { - if err := x.MsgSend(m, drpcEncoding_File_aibridged_proto_aibridged_proto{}); err != nil { + if err := x.MsgSend(m, drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}); err != nil { return err } return x.CloseSend() @@ -363,7 +363,7 @@ func (c *drpcAuthorizerClient) DRPCConn() drpc.Conn { return c.cc } func (c *drpcAuthorizerClient) IsAuthorized(ctx context.Context, in *IsAuthorizedRequest) (*IsAuthorizedResponse, error) { out := new(IsAuthorizedResponse) - err := c.cc.Invoke(ctx, "/proto.Authorizer/IsAuthorized", drpcEncoding_File_aibridged_proto_aibridged_proto{}, in, out) + err := c.cc.Invoke(ctx, "/proto.Authorizer/IsAuthorized", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, in, out) if err != nil { return nil, err } @@ -387,7 +387,7 @@ func (DRPCAuthorizerDescription) NumMethods() int { return 1 } func (DRPCAuthorizerDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) { switch n { case 0: - return "/proto.Authorizer/IsAuthorized", drpcEncoding_File_aibridged_proto_aibridged_proto{}, + return "/proto.Authorizer/IsAuthorized", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { return srv.(DRPCAuthorizerServer). IsAuthorized( @@ -414,7 +414,7 @@ type drpcAuthorizer_IsAuthorizedStream struct { } func (x *drpcAuthorizer_IsAuthorizedStream) SendAndClose(m *IsAuthorizedResponse) error { - if err := x.MsgSend(m, drpcEncoding_File_aibridged_proto_aibridged_proto{}); err != nil { + if err := x.MsgSend(m, drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}); err != nil { return err } return x.CloseSend() diff --git a/enterprise/x/aibridgedserver/aibridgedserver.go b/enterprise/x/aibridgedserver/aibridgedserver.go new file mode 100644 index 0000000000..51483c435f --- /dev/null +++ b/enterprise/x/aibridgedserver/aibridgedserver.go @@ -0,0 +1,430 @@ +package aibridgedserver + +import ( + "context" + "crypto/sha256" + "crypto/subtle" + "database/sql" + "encoding/json" + "net/url" + "slices" + "sync" + + "github.com/google/uuid" + "github.com/hashicorp/go-multierror" + "golang.org/x/xerrors" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/structpb" + + "cdr.dev/slog" + + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbauthz" + "github.com/coder/coder/v2/coderd/database/dbtime" + "github.com/coder/coder/v2/coderd/externalauth" + "github.com/coder/coder/v2/coderd/httpmw" + codermcp "github.com/coder/coder/v2/coderd/mcp" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/enterprise/x/aibridged/proto" +) + +var ( + ErrExpiredOrInvalidOAuthToken = xerrors.New("expired or invalid OAuth2 token") + ErrNoMCPConfigFound = xerrors.New("no MCP config found") + + // These errors are returned by IsAuthorized. Since they're just returned as + // a generic dRPC error, it's difficult to tell them apart without string + // matching. + // TODO: return these errors to the client in a more structured/comparable + // way. + ErrInvalidKey = xerrors.New("invalid key") + ErrUnknownKey = xerrors.New("unknown key") + ErrExpired = xerrors.New("expired") + ErrUnknownUser = xerrors.New("unknown user") + ErrDeletedUser = xerrors.New("deleted user") + ErrSystemUser = xerrors.New("system user") + + ErrNoExternalAuthLinkFound = xerrors.New("no external auth link found") +) + +var ( + _ proto.DRPCAuthorizerServer = &Server{} + _ proto.DRPCMCPConfiguratorServer = &Server{} + _ proto.DRPCRecorderServer = &Server{} +) + +type store interface { + // Recorder-related queries. + InsertAIBridgeInterception(ctx context.Context, arg database.InsertAIBridgeInterceptionParams) (database.AIBridgeInterception, error) + InsertAIBridgeTokenUsage(ctx context.Context, arg database.InsertAIBridgeTokenUsageParams) error + InsertAIBridgeUserPrompt(ctx context.Context, arg database.InsertAIBridgeUserPromptParams) error + InsertAIBridgeToolUsage(ctx context.Context, arg database.InsertAIBridgeToolUsageParams) error + + // MCPConfigurator-related queries. + GetExternalAuthLinksByUserID(ctx context.Context, userID uuid.UUID) ([]database.ExternalAuthLink, error) + + // Authorizer-related queries. + GetAPIKeyByID(ctx context.Context, id string) (database.APIKey, error) + GetUserByID(ctx context.Context, id uuid.UUID) (database.User, error) +} + +type Server struct { + // lifecycleCtx must be tied to the API server's lifecycle + // as when the API server shuts down, we want to cancel any + // long-running operations. + lifecycleCtx context.Context + store store + logger slog.Logger + externalAuthConfigs map[string]*externalauth.Config + + coderMCPConfig *proto.MCPServerConfig // may be nil if not available +} + +func NewServer(lifecycleCtx context.Context, store store, logger slog.Logger, accessURL string, externalAuthConfigs []*externalauth.Config, experiments codersdk.Experiments) (*Server, error) { + eac := make(map[string]*externalauth.Config, len(externalAuthConfigs)) + + for _, cfg := range externalAuthConfigs { + // Only External Auth configs which are configured with an MCP URL are relevant to aibridged. + if cfg.MCPURL == "" { + continue + } + eac[cfg.ID] = cfg + } + + coderMCPConfig, err := getCoderMCPServerConfig(experiments, accessURL) + if err != nil { + logger.Warn(lifecycleCtx, "failed to retrieve coder MCP server config, Coder MCP will not be available", slog.Error(err)) + } + + return &Server{ + lifecycleCtx: lifecycleCtx, + store: store, + logger: logger.Named("aibridgedserver"), + externalAuthConfigs: eac, + coderMCPConfig: coderMCPConfig, + }, nil +} + +func (s *Server) RecordInterception(ctx context.Context, in *proto.RecordInterceptionRequest) (*proto.RecordInterceptionResponse, error) { + //nolint:gocritic // AIBridged has specific authz rules. + ctx = dbauthz.AsAIBridged(ctx) + + intcID, err := uuid.Parse(in.GetId()) + if err != nil { + return nil, xerrors.Errorf("invalid interception ID %q: %w", in.GetId(), err) + } + initID, err := uuid.Parse(in.GetInitiatorId()) + if err != nil { + return nil, xerrors.Errorf("invalid initiator ID %q: %w", in.GetInitiatorId(), err) + } + + _, err = s.store.InsertAIBridgeInterception(ctx, database.InsertAIBridgeInterceptionParams{ + ID: intcID, + InitiatorID: initID, + Provider: in.Provider, + Model: in.Model, + Metadata: marshalMetadata(ctx, s.logger, in.GetMetadata()), + StartedAt: in.StartedAt.AsTime(), + }) + if err != nil { + return nil, xerrors.Errorf("start interception: %w", err) + } + + return &proto.RecordInterceptionResponse{}, nil +} + +func (s *Server) RecordTokenUsage(ctx context.Context, in *proto.RecordTokenUsageRequest) (*proto.RecordTokenUsageResponse, error) { + //nolint:gocritic // AIBridged has specific authz rules. + ctx = dbauthz.AsAIBridged(ctx) + + intcID, err := uuid.Parse(in.GetInterceptionId()) + if err != nil { + return nil, xerrors.Errorf("failed to parse interception_id %q: %w", in.GetInterceptionId(), err) + } + + err = s.store.InsertAIBridgeTokenUsage(ctx, database.InsertAIBridgeTokenUsageParams{ + ID: uuid.New(), + InterceptionID: intcID, + ProviderResponseID: in.GetMsgId(), + InputTokens: in.GetInputTokens(), + OutputTokens: in.GetOutputTokens(), + Metadata: marshalMetadata(ctx, s.logger, in.GetMetadata()), + CreatedAt: in.GetCreatedAt().AsTime(), + }) + if err != nil { + return nil, xerrors.Errorf("insert token usage: %w", err) + } + return &proto.RecordTokenUsageResponse{}, nil +} + +func (s *Server) RecordPromptUsage(ctx context.Context, in *proto.RecordPromptUsageRequest) (*proto.RecordPromptUsageResponse, error) { + //nolint:gocritic // AIBridged has specific authz rules. + ctx = dbauthz.AsAIBridged(ctx) + + intcID, err := uuid.Parse(in.GetInterceptionId()) + if err != nil { + return nil, xerrors.Errorf("failed to parse interception_id %q: %w", in.GetInterceptionId(), err) + } + + err = s.store.InsertAIBridgeUserPrompt(ctx, database.InsertAIBridgeUserPromptParams{ + ID: uuid.New(), + InterceptionID: intcID, + ProviderResponseID: in.GetMsgId(), + Prompt: in.GetPrompt(), + Metadata: marshalMetadata(ctx, s.logger, in.GetMetadata()), + CreatedAt: in.GetCreatedAt().AsTime(), + }) + if err != nil { + return nil, xerrors.Errorf("insert user prompt: %w", err) + } + return &proto.RecordPromptUsageResponse{}, nil +} + +func (s *Server) RecordToolUsage(ctx context.Context, in *proto.RecordToolUsageRequest) (*proto.RecordToolUsageResponse, error) { + //nolint:gocritic // AIBridged has specific authz rules. + ctx = dbauthz.AsAIBridged(ctx) + + intcID, err := uuid.Parse(in.GetInterceptionId()) + if err != nil { + return nil, xerrors.Errorf("failed to parse interception_id %q: %w", in.GetInterceptionId(), err) + } + + err = s.store.InsertAIBridgeToolUsage(ctx, database.InsertAIBridgeToolUsageParams{ + ID: uuid.New(), + InterceptionID: intcID, + ProviderResponseID: in.GetMsgId(), + ServerUrl: sql.NullString{String: in.GetServerUrl(), Valid: in.ServerUrl != nil}, + Tool: in.GetTool(), + Input: in.GetInput(), + Injected: in.GetInjected(), + InvocationError: sql.NullString{String: in.GetInvocationError(), Valid: in.InvocationError != nil}, + Metadata: marshalMetadata(ctx, s.logger, in.GetMetadata()), + CreatedAt: in.GetCreatedAt().AsTime(), + }) + if err != nil { + return nil, xerrors.Errorf("insert tool usage: %w", err) + } + return &proto.RecordToolUsageResponse{}, nil +} + +func (s *Server) GetMCPServerConfigs(_ context.Context, _ *proto.GetMCPServerConfigsRequest) (*proto.GetMCPServerConfigsResponse, error) { + cfgs := make([]*proto.MCPServerConfig, 0, len(s.externalAuthConfigs)) + for _, eac := range s.externalAuthConfigs { + var allowlist, denylist string + if eac.MCPToolAllowRegex != nil { + allowlist = eac.MCPToolAllowRegex.String() + } + if eac.MCPToolDenyRegex != nil { + denylist = eac.MCPToolDenyRegex.String() + } + + cfgs = append(cfgs, &proto.MCPServerConfig{ + Id: eac.ID, + Url: eac.MCPURL, + ToolAllowRegex: allowlist, + ToolDenyRegex: denylist, + }) + } + + return &proto.GetMCPServerConfigsResponse{ + CoderMcpConfig: s.coderMCPConfig, // it's fine if this is nil + ExternalAuthMcpConfigs: cfgs, + }, nil +} + +func (s *Server) GetMCPServerAccessTokensBatch(ctx context.Context, in *proto.GetMCPServerAccessTokensBatchRequest) (*proto.GetMCPServerAccessTokensBatchResponse, error) { + if len(in.GetMcpServerConfigIds()) == 0 { + return &proto.GetMCPServerAccessTokensBatchResponse{}, nil + } + + userID, err := uuid.Parse(in.GetUserId()) + if err != nil { + return nil, xerrors.Errorf("parse user_id: %w", err) + } + + //nolint:gocritic // AIBridged has specific authz rules. + ctx = dbauthz.AsAIBridged(ctx) + links, err := s.store.GetExternalAuthLinksByUserID(ctx, userID) + if err != nil { + return nil, xerrors.Errorf("fetch external auth links: %w", err) + } + + if len(links) == 0 { + return &proto.GetMCPServerAccessTokensBatchResponse{}, nil + } + + // Ensure unique to prevent unnecessary effort. + ids := in.GetMcpServerConfigIds() + slices.Sort(ids) + ids = slices.Compact(ids) + + var ( + wg sync.WaitGroup + errs error + + mu sync.Mutex + tokens = make(map[string]string, len(ids)) + tokenErrs = make(map[string]string) + ) + +externalAuthLoop: + for _, id := range ids { + eac, ok := s.externalAuthConfigs[id] + if !ok { + mu.Lock() + s.logger.Warn(ctx, "no MCP server config found by given ID", slog.F("id", id)) + tokenErrs[id] = ErrNoMCPConfigFound.Error() + mu.Unlock() + continue + } + + for _, link := range links { + if link.ProviderID != eac.ID { + continue + } + + // Validate all configured External Auth links concurrently. + wg.Add(1) + go func() { + defer wg.Done() + + // TODO: timeout. + valid, _, validateErr := eac.ValidateToken(ctx, link.OAuthToken()) + mu.Lock() + defer mu.Unlock() + if !valid { + // TODO: attempt refresh. + s.logger.Warn(ctx, "invalid/expired access token, cannot auto-configure MCP", slog.F("provider", link.ProviderID), slog.Error(validateErr)) + tokenErrs[id] = ErrExpiredOrInvalidOAuthToken.Error() + return + } + + if validateErr != nil { + errs = multierror.Append(errs, validateErr) + tokenErrs[id] = validateErr.Error() + } else { + tokens[id] = link.OAuthAccessToken + } + }() + + continue externalAuthLoop + } + + // No link found for this external auth config, so include a generic + // error. + mu.Lock() + tokenErrs[id] = ErrNoExternalAuthLinkFound.Error() + mu.Unlock() + } + + wg.Wait() + return &proto.GetMCPServerAccessTokensBatchResponse{ + AccessTokens: tokens, + Errors: tokenErrs, + }, errs +} + +// IsAuthorized validates a given Coder API key and returns the user ID to which it belongs (if valid). +// +// NOTE: this should really be using the code from [httpmw.ExtractAPIKey]. That function not only validates the key +// but handles many other cases like updating last used, expiry, etc. This code does not currently use it for +// a few reasons: +// +// 1. [httpmw.ExtractAPIKey] relies on keys being given in specific headers [httpmw.APITokenFromRequest] which AI +// bridge requests will not conform to. +// 2. The code mixes many different concerns, and handles HTTP responses too, which is undesirable here. +// 3. The core logic would need to be extracted, but that will surely be a complex & time-consuming distraction right now. +// 4. Once we have an Early Access release of AI Bridge, we need to return to this. +// +// TODO: replace with logic from [httpmw.ExtractAPIKey]. +func (s *Server) IsAuthorized(ctx context.Context, in *proto.IsAuthorizedRequest) (*proto.IsAuthorizedResponse, error) { + //nolint:gocritic // AIBridged has specific authz rules. + ctx = dbauthz.AsAIBridged(ctx) + + // Key matches expected format. + keyID, keySecret, err := httpmw.SplitAPIToken(in.GetKey()) + if err != nil { + return nil, ErrInvalidKey + } + + // Key exists. + key, err := s.store.GetAPIKeyByID(ctx, keyID) + if err != nil { + s.logger.Warn(ctx, "failed to retrieve API key by id", slog.F("key_id", keyID), slog.Error(err)) + return nil, ErrUnknownKey + } + + // Key has not expired. + now := dbtime.Now() + if key.ExpiresAt.Before(now) { + return nil, ErrExpired + } + + // Key secret matches. + hashedSecret := sha256.Sum256([]byte(keySecret)) + if subtle.ConstantTimeCompare(key.HashedSecret, hashedSecret[:]) != 1 { + return nil, ErrInvalidKey + } + + // User exists. + user, err := s.store.GetUserByID(ctx, key.UserID) + if err != nil { + s.logger.Warn(ctx, "failed to retrieve API key user", slog.F("key_id", keyID), slog.F("user_id", key.UserID), slog.Error(err)) + return nil, ErrUnknownUser + } + + // User is not deleted or a system user. + if user.Deleted { + return nil, ErrDeletedUser + } + if user.IsSystem { + return nil, ErrSystemUser + } + + return &proto.IsAuthorizedResponse{ + OwnerId: key.UserID.String(), + }, nil +} + +func getCoderMCPServerConfig(experiments codersdk.Experiments, accessURL string) (*proto.MCPServerConfig, error) { + // Both the MCP & OAuth2 experiments are currently required in order to use our + // internal MCP server. + if !experiments.Enabled(codersdk.ExperimentMCPServerHTTP) { + return nil, xerrors.Errorf("%q experiment not enabled", codersdk.ExperimentMCPServerHTTP) + } + if !experiments.Enabled(codersdk.ExperimentOAuth2) { + return nil, xerrors.Errorf("%q experiment not enabled", codersdk.ExperimentOAuth2) + } + + u, err := url.JoinPath(accessURL, codermcp.MCPEndpoint) + if err != nil { + return nil, xerrors.Errorf("build MCP URL with %q: %w", accessURL, err) + } + + return &proto.MCPServerConfig{ + Id: "coder", + Url: u, + }, nil +} + +// marshalMetadata attempts to marshal the given metadata map into a +// JSON-encoded byte slice. If the marshaling fails, the function logs a +// warning and returns nil. The supplied context is only used for logging. +func marshalMetadata(ctx context.Context, logger slog.Logger, in map[string]*anypb.Any) []byte { + mdMap := make(map[string]any, len(in)) + for k, v := range in { + if v == nil { + continue + } + var sv structpb.Value + if err := v.UnmarshalTo(&sv); err == nil { + mdMap[k] = sv.AsInterface() + } + } + out, err := json.Marshal(mdMap) + if err != nil { + logger.Warn(ctx, "failed to marshal aibridge metadata from proto to JSON", slog.F("metadata", in), slog.Error(err)) + return nil + } + return out +} diff --git a/enterprise/x/aibridgedserver/aibridgedserver_internal_test.go b/enterprise/x/aibridgedserver/aibridgedserver_internal_test.go new file mode 100644 index 0000000000..28b9463e8b --- /dev/null +++ b/enterprise/x/aibridgedserver/aibridgedserver_internal_test.go @@ -0,0 +1,88 @@ +package aibridgedserver + +import ( + "context" + "encoding/json" + "math" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/structpb" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/slogtest" +) + +func TestMarshalMetadata(t *testing.T) { + t.Parallel() + + t.Run("NilData", func(t *testing.T) { + t.Parallel() + logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug) + out := marshalMetadata(context.Background(), logger, nil) + require.JSONEq(t, "{}", string(out)) + }) + + t.Run("WithData", func(t *testing.T) { + t.Parallel() + logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug) + + list := structpb.NewListValue(&structpb.ListValue{Values: []*structpb.Value{ + structpb.NewStringValue("a"), + structpb.NewNumberValue(1), + structpb.NewBoolValue(false), + }}) + obj := structpb.NewStructValue(&structpb.Struct{Fields: map[string]*structpb.Value{ + "a": structpb.NewStringValue("b"), + "n": structpb.NewNumberValue(3), + }}) + + nonValue := mustMarshalAny(t, &structpb.Struct{Fields: map[string]*structpb.Value{ + "ignored": structpb.NewStringValue("yes"), + }}) + invalid := &anypb.Any{TypeUrl: "type.googleapis.com/google.protobuf.Value", Value: []byte{0xff, 0x00}} + + in := map[string]*anypb.Any{ + "null": mustMarshalAny(t, structpb.NewNullValue()), + // Scalars + "string": mustMarshalAny(t, structpb.NewStringValue("hello")), + "bool": mustMarshalAny(t, structpb.NewBoolValue(true)), + "number": mustMarshalAny(t, structpb.NewNumberValue(42)), + // Complex types + "list": mustMarshalAny(t, list), + "object": mustMarshalAny(t, obj), + // Extra valid entries + "ok": mustMarshalAny(t, structpb.NewStringValue("present")), + "nan": mustMarshalAny(t, structpb.NewNumberValue(math.NaN())), + // Entries that should be ignored + "invalid": invalid, + "non_value": nonValue, + } + + out := marshalMetadata(context.Background(), logger, in) + require.NotNil(t, out) + var got map[string]any + require.NoError(t, json.Unmarshal(out, &got)) + + expected := map[string]any{ + "string": "hello", + "bool": true, + "number": float64(42), + "null": nil, + "list": []any{"a", float64(1), false}, + "object": map[string]any{"a": "b", "n": float64(3)}, + "ok": "present", + "nan": "NaN", + } + require.Equal(t, expected, got) + }) +} + +func mustMarshalAny(t testing.TB, m proto.Message) *anypb.Any { + t.Helper() + a, err := anypb.New(m) + require.NoError(t, err) + return a +} diff --git a/enterprise/x/aibridgedserver/aibridgedserver_test.go b/enterprise/x/aibridgedserver/aibridgedserver_test.go new file mode 100644 index 0000000000..2e8e4c9c87 --- /dev/null +++ b/enterprise/x/aibridgedserver/aibridgedserver_test.go @@ -0,0 +1,710 @@ +package aibridgedserver_test + +import ( + "context" + "crypto/sha256" + "database/sql" + "encoding/json" + "fmt" + "net" + "net/url" + "testing" + "time" + + "github.com/google/uuid" + "github.com/sqlc-dev/pqtype" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + protobufproto "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbmock" + "github.com/coder/coder/v2/coderd/database/dbtime" + "github.com/coder/coder/v2/coderd/externalauth" + codermcp "github.com/coder/coder/v2/coderd/mcp" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/cryptorand" + "github.com/coder/coder/v2/enterprise/x/aibridged/proto" + "github.com/coder/coder/v2/enterprise/x/aibridgedserver" + "github.com/coder/coder/v2/testutil" +) + +var requiredExperiments = []codersdk.Experiment{ + codersdk.ExperimentMCPServerHTTP, codersdk.ExperimentOAuth2, +} + +// TestAuthorization validates the authorization logic. +// No other tests are explicitly defined in this package because aibridgedserver is +// tested via integration tests in the aibridged package (see aibridged/aibridged_integration_test.go). +func TestAuthorization(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + // Key will be set to the same key passed to mocksFn if unset. + key string + // mocksFn is called with a valid API key and user. If the test needs + // invalid values, it should just mutate them directly. + mocksFn func(db *dbmock.MockStore, apiKey database.APIKey, user database.User) + expectedErr error + }{ + { + name: "invalid key format", + key: "foo", + expectedErr: aibridgedserver.ErrInvalidKey, + }, + { + name: "unknown key", + expectedErr: aibridgedserver.ErrUnknownKey, + mocksFn: func(db *dbmock.MockStore, apiKey database.APIKey, user database.User) { + db.EXPECT().GetAPIKeyByID(gomock.Any(), apiKey.ID).Times(1).Return(database.APIKey{}, sql.ErrNoRows) + }, + }, + { + name: "expired", + expectedErr: aibridgedserver.ErrExpired, + mocksFn: func(db *dbmock.MockStore, apiKey database.APIKey, user database.User) { + apiKey.ExpiresAt = dbtime.Now().Add(-time.Hour) + db.EXPECT().GetAPIKeyByID(gomock.Any(), apiKey.ID).Times(1).Return(apiKey, nil) + }, + }, + { + name: "invalid key secret", + expectedErr: aibridgedserver.ErrInvalidKey, + mocksFn: func(db *dbmock.MockStore, apiKey database.APIKey, user database.User) { + apiKey.HashedSecret = []byte("differentsecret") + db.EXPECT().GetAPIKeyByID(gomock.Any(), apiKey.ID).Times(1).Return(apiKey, nil) + }, + }, + { + name: "unknown user", + expectedErr: aibridgedserver.ErrUnknownUser, + mocksFn: func(db *dbmock.MockStore, apiKey database.APIKey, user database.User) { + db.EXPECT().GetAPIKeyByID(gomock.Any(), apiKey.ID).Times(1).Return(apiKey, nil) + db.EXPECT().GetUserByID(gomock.Any(), user.ID).Times(1).Return(database.User{}, sql.ErrNoRows) + }, + }, + { + name: "deleted user", + expectedErr: aibridgedserver.ErrDeletedUser, + mocksFn: func(db *dbmock.MockStore, apiKey database.APIKey, user database.User) { + db.EXPECT().GetAPIKeyByID(gomock.Any(), apiKey.ID).Times(1).Return(apiKey, nil) + db.EXPECT().GetUserByID(gomock.Any(), user.ID).Times(1).Return(database.User{ID: user.ID, Deleted: true}, nil) + }, + }, + { + name: "system user", + expectedErr: aibridgedserver.ErrSystemUser, + mocksFn: func(db *dbmock.MockStore, apiKey database.APIKey, user database.User) { + db.EXPECT().GetAPIKeyByID(gomock.Any(), apiKey.ID).Times(1).Return(apiKey, nil) + db.EXPECT().GetUserByID(gomock.Any(), user.ID).Times(1).Return(database.User{ID: user.ID, IsSystem: true}, nil) + }, + }, + { + name: "valid", + mocksFn: func(db *dbmock.MockStore, apiKey database.APIKey, user database.User) { + db.EXPECT().GetAPIKeyByID(gomock.Any(), apiKey.ID).Times(1).Return(apiKey, nil) + db.EXPECT().GetUserByID(gomock.Any(), user.ID).Times(1).Return(user, nil) + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + db := dbmock.NewMockStore(ctrl) + logger := testutil.Logger(t) + + // Make a fake user and an API key for the mock calls. + now := dbtime.Now() + user := database.User{ + ID: uuid.New(), + Email: "test@coder.com", + Username: "test", + Name: "Test User", + CreatedAt: now, + UpdatedAt: now, + RBACRoles: []string{}, + LoginType: database.LoginTypePassword, + Status: database.UserStatusActive, + LastSeenAt: now, + } + + keyID, _ := cryptorand.String(10) + keySecret, _ := cryptorand.String(22) + token := fmt.Sprintf("%s-%s", keyID, keySecret) + keySecretHashed := sha256.Sum256([]byte(keySecret)) + apiKey := database.APIKey{ + ID: keyID, + LifetimeSeconds: 86400, // default in db + HashedSecret: keySecretHashed[:], + IPAddress: pqtype.Inet{ + IPNet: net.IPNet{ + IP: net.IPv4(127, 0, 0, 1), + Mask: net.IPv4Mask(255, 255, 255, 255), + }, + Valid: true, + }, + UserID: user.ID, + LastUsed: now, + ExpiresAt: now.Add(time.Hour), + CreatedAt: now, + UpdatedAt: now, + LoginType: database.LoginTypePassword, + Scopes: []database.APIKeyScope{database.APIKeyScopeAll}, + TokenName: "", + } + if tc.key == "" { + tc.key = token + } + + // Define any case-specific mocks. + if tc.mocksFn != nil { + tc.mocksFn(db, apiKey, user) + } + + srv, err := aibridgedserver.NewServer(t.Context(), db, logger, "/", nil, requiredExperiments) + require.NoError(t, err) + require.NotNil(t, srv) + + _, err = srv.IsAuthorized(t.Context(), &proto.IsAuthorizedRequest{Key: tc.key}) + if tc.expectedErr != nil { + require.Error(t, err) + require.ErrorIs(t, err, tc.expectedErr) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestGetMCPServerConfigs(t *testing.T) { + t.Parallel() + + externalAuthCfgs := []*externalauth.Config{ + { + ID: "1", + MCPURL: "1.com/mcp", + }, + { + ID: "2", // Will not be eligible for inclusion since MCPURL is not defined. + }, + } + + cases := []struct { + name string + experiments codersdk.Experiments + externalAuthConfigs []*externalauth.Config + expectCoderMCP bool + expectedExternalMCP bool + }{ + { + name: "experiments not enabled", + experiments: codersdk.Experiments{}, + }, + { + name: "MCP experiment enabled, not OAuth2", + experiments: codersdk.Experiments{codersdk.ExperimentMCPServerHTTP}, + }, + { + name: "OAuth2 experiment enabled, not MCP", + experiments: codersdk.Experiments{codersdk.ExperimentOAuth2}, + }, + { + name: "only internal MCP", + experiments: requiredExperiments, + expectCoderMCP: true, + }, + { + name: "only external MCP", + externalAuthConfigs: externalAuthCfgs, + expectedExternalMCP: true, + }, + { + name: "both internal & external MCP", + experiments: requiredExperiments, + externalAuthConfigs: externalAuthCfgs, + expectCoderMCP: true, + expectedExternalMCP: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + db := dbmock.NewMockStore(ctrl) + logger := testutil.Logger(t) + + accessURL := "https://my-cool-deployment.com" + srv, err := aibridgedserver.NewServer(t.Context(), db, logger, accessURL, tc.externalAuthConfigs, tc.experiments) + require.NoError(t, err) + require.NotNil(t, srv) + + resp, err := srv.GetMCPServerConfigs(t.Context(), &proto.GetMCPServerConfigsRequest{}) + require.NoError(t, err) + require.NotNil(t, resp) + + if tc.expectCoderMCP { + coderConfig := resp.CoderMcpConfig + require.NotNil(t, coderConfig) + require.Equal(t, "coder", coderConfig.GetId()) + expectedURL, err := url.JoinPath(accessURL, codermcp.MCPEndpoint) + require.NoError(t, err) + require.Equal(t, expectedURL, coderConfig.GetUrl()) + require.Empty(t, coderConfig.GetToolAllowRegex()) + require.Empty(t, coderConfig.GetToolDenyRegex()) + } else { + require.Empty(t, resp.GetCoderMcpConfig()) + } + + if tc.expectedExternalMCP { + require.Len(t, resp.GetExternalAuthMcpConfigs(), 1) + } else { + require.Empty(t, resp.GetExternalAuthMcpConfigs()) + } + }) + } +} + +func TestGetMCPServerAccessTokensBatch(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + db := dbmock.NewMockStore(ctrl) + logger := testutil.Logger(t) + + // Given: 2 external auth configured with MCP and 1 without. + srv, err := aibridgedserver.NewServer(t.Context(), db, logger, "/", []*externalauth.Config{ + { + ID: "1", + MCPURL: "1.com/mcp", + }, + { + ID: "2", + MCPURL: "2.com/mcp", + }, + { + ID: "3", + }, + }, requiredExperiments) + require.NoError(t, err) + require.NotNil(t, srv) + + // When: requesting all external auth links, return all. + db.EXPECT().GetExternalAuthLinksByUserID(gomock.Any(), gomock.Any()).MinTimes(1).DoAndReturn(func(ctx context.Context, userID uuid.UUID) ([]database.ExternalAuthLink, error) { + return []database.ExternalAuthLink{ + { + UserID: userID, + ProviderID: "1", + OAuthAccessToken: "1-token", + }, + { + UserID: userID, + ProviderID: "2", + OAuthAccessToken: "2-token", + OAuthExpiry: dbtime.Now().Add(-time.Minute), // This token is expired and should not be returned. + }, + { + UserID: userID, + ProviderID: "3", + OAuthAccessToken: "3-token", + }, + }, nil + }) + + // When: accessing the MCP server access tokens, only the 2 with MCP configured should be returned, and the 1 without should + // not fail the request but rather have an error returned specifically for that server. + resp, err := srv.GetMCPServerAccessTokensBatch(t.Context(), &proto.GetMCPServerAccessTokensBatchRequest{ + UserId: uuid.NewString(), + McpServerConfigIds: []string{"1", "1", "2", "3"}, // Duplicates must be tolerated. + }) + require.NoError(t, err) + + // Then: 2 MCP servers are eligible but only 1 will return a valid token as the other expired. + require.Len(t, resp.GetAccessTokens(), 1) + require.Equal(t, "1-token", resp.GetAccessTokens()["1"]) + require.Len(t, resp.GetErrors(), 2) + require.Contains(t, resp.GetErrors()["2"], aibridgedserver.ErrExpiredOrInvalidOAuthToken.Error()) + require.Contains(t, resp.GetErrors()["3"], aibridgedserver.ErrNoMCPConfigFound.Error()) +} + +func TestRecordInterception(t *testing.T) { + t.Parallel() + + var ( + metadataProto = map[string]*anypb.Any{ + "key": mustMarshalAny(t, &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "value"}}), + } + metadataJSON = `{"key":"value"}` + ) + + testRecordMethod(t, + func(srv *aibridgedserver.Server, ctx context.Context, req *proto.RecordInterceptionRequest) (*proto.RecordInterceptionResponse, error) { + return srv.RecordInterception(ctx, req) + }, + []testRecordMethodCase[*proto.RecordInterceptionRequest]{ + { + name: "valid interception", + request: &proto.RecordInterceptionRequest{ + Id: uuid.NewString(), + InitiatorId: uuid.NewString(), + Provider: "anthropic", + Model: "claude-4-opus", + Metadata: metadataProto, + StartedAt: timestamppb.Now(), + }, + setupMocks: func(t *testing.T, db *dbmock.MockStore, req *proto.RecordInterceptionRequest) { + interceptionID, err := uuid.Parse(req.GetId()) + assert.NoError(t, err, "parse interception UUID") + initiatorID, err := uuid.Parse(req.GetInitiatorId()) + assert.NoError(t, err, "parse interception initiator UUID") + + db.EXPECT().InsertAIBridgeInterception(gomock.Any(), database.InsertAIBridgeInterceptionParams{ + ID: interceptionID, + InitiatorID: initiatorID, + Provider: req.GetProvider(), + Model: req.GetModel(), + Metadata: json.RawMessage(metadataJSON), + StartedAt: req.StartedAt.AsTime().UTC(), + }).Return(database.AIBridgeInterception{ + ID: interceptionID, + InitiatorID: initiatorID, + Provider: req.GetProvider(), + Model: req.GetModel(), + StartedAt: req.StartedAt.AsTime().UTC(), + }, nil) + }, + }, + { + name: "invalid interception ID", + request: &proto.RecordInterceptionRequest{ + Id: "not-a-uuid", + InitiatorId: uuid.NewString(), + Provider: "anthropic", + Model: "claude-4-opus", + StartedAt: timestamppb.Now(), + }, + expectedErr: "invalid interception ID", + }, + { + name: "invalid initiator ID", + request: &proto.RecordInterceptionRequest{ + Id: uuid.NewString(), + InitiatorId: "not-a-uuid", + Provider: "anthropic", + Model: "claude-4-opus", + StartedAt: timestamppb.Now(), + }, + expectedErr: "invalid initiator ID", + }, + { + name: "database error", + request: &proto.RecordInterceptionRequest{ + Id: uuid.NewString(), + InitiatorId: uuid.NewString(), + Provider: "anthropic", + Model: "claude-4-opus", + StartedAt: timestamppb.Now(), + }, + setupMocks: func(t *testing.T, db *dbmock.MockStore, req *proto.RecordInterceptionRequest) { + db.EXPECT().InsertAIBridgeInterception(gomock.Any(), gomock.Any()).Return(database.AIBridgeInterception{}, sql.ErrConnDone) + }, + expectedErr: "start interception", + }, + }, + ) +} + +func TestRecordTokenUsage(t *testing.T) { + t.Parallel() + + var ( + metadataProto = map[string]*anypb.Any{ + "key": mustMarshalAny(t, &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "value"}}), + } + metadataJSON = `{"key":"value"}` + ) + + testRecordMethod(t, + func(srv *aibridgedserver.Server, ctx context.Context, req *proto.RecordTokenUsageRequest) (*proto.RecordTokenUsageResponse, error) { + return srv.RecordTokenUsage(ctx, req) + }, + []testRecordMethodCase[*proto.RecordTokenUsageRequest]{ + { + name: "valid token usage", + request: &proto.RecordTokenUsageRequest{ + InterceptionId: uuid.NewString(), + MsgId: "msg_123", + InputTokens: 100, + OutputTokens: 200, + Metadata: metadataProto, + CreatedAt: timestamppb.Now(), + }, + setupMocks: func(t *testing.T, db *dbmock.MockStore, req *proto.RecordTokenUsageRequest) { + interceptionID, err := uuid.Parse(req.GetInterceptionId()) + assert.NoError(t, err, "parse interception UUID") + + db.EXPECT().InsertAIBridgeTokenUsage(gomock.Any(), gomock.Cond(func(p database.InsertAIBridgeTokenUsageParams) bool { + if !assert.NotEqual(t, uuid.Nil, p.ID, "ID") || + !assert.Equal(t, interceptionID, p.InterceptionID, "interception ID") || + !assert.Equal(t, req.GetMsgId(), p.ProviderResponseID, "provider response ID") || + !assert.Equal(t, req.GetInputTokens(), p.InputTokens, "input tokens") || + !assert.Equal(t, req.GetOutputTokens(), p.OutputTokens, "output tokens") || + !assert.JSONEq(t, metadataJSON, string(p.Metadata), "metadata") || + !assert.WithinDuration(t, req.GetCreatedAt().AsTime(), p.CreatedAt, time.Second, "created at") { + return false + } + return true + })).Return(nil) + }, + }, + { + name: "invalid interception ID", + request: &proto.RecordTokenUsageRequest{ + InterceptionId: "not-a-uuid", + MsgId: "msg_123", + InputTokens: 100, + OutputTokens: 200, + CreatedAt: timestamppb.Now(), + }, + expectedErr: "failed to parse interception_id", + }, + { + name: "database error", + request: &proto.RecordTokenUsageRequest{ + InterceptionId: uuid.NewString(), + MsgId: "msg_123", + InputTokens: 100, + OutputTokens: 200, + CreatedAt: timestamppb.Now(), + }, + setupMocks: func(t *testing.T, db *dbmock.MockStore, req *proto.RecordTokenUsageRequest) { + db.EXPECT().InsertAIBridgeTokenUsage(gomock.Any(), gomock.Any()).Return(sql.ErrConnDone) + }, + expectedErr: "insert token usage", + }, + }, + ) +} + +func TestRecordPromptUsage(t *testing.T) { + t.Parallel() + + var ( + metadataProto = map[string]*anypb.Any{ + "key": mustMarshalAny(t, &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "value"}}), + } + metadataJSON = `{"key":"value"}` + ) + + testRecordMethod(t, + func(srv *aibridgedserver.Server, ctx context.Context, req *proto.RecordPromptUsageRequest) (*proto.RecordPromptUsageResponse, error) { + return srv.RecordPromptUsage(ctx, req) + }, + []testRecordMethodCase[*proto.RecordPromptUsageRequest]{ + { + name: "valid prompt usage", + request: &proto.RecordPromptUsageRequest{ + InterceptionId: uuid.NewString(), + MsgId: "msg_123", + Prompt: "yo", + Metadata: metadataProto, + CreatedAt: timestamppb.Now(), + }, + setupMocks: func(t *testing.T, db *dbmock.MockStore, req *proto.RecordPromptUsageRequest) { + interceptionID, err := uuid.Parse(req.GetInterceptionId()) + assert.NoError(t, err, "parse interception UUID") + + db.EXPECT().InsertAIBridgeUserPrompt(gomock.Any(), gomock.Cond(func(p database.InsertAIBridgeUserPromptParams) bool { + if !assert.NotEqual(t, uuid.Nil, p.ID, "ID") || + !assert.Equal(t, interceptionID, p.InterceptionID, "interception ID") || + !assert.Equal(t, req.GetMsgId(), p.ProviderResponseID, "provider response ID") || + !assert.Equal(t, req.GetPrompt(), p.Prompt, "prompt") || + !assert.JSONEq(t, metadataJSON, string(p.Metadata), "metadata") || + !assert.WithinDuration(t, req.GetCreatedAt().AsTime(), p.CreatedAt, time.Second, "created at") { + return false + } + return true + })).Return(nil) + }, + }, + { + name: "invalid interception ID", + request: &proto.RecordPromptUsageRequest{ + InterceptionId: "not-a-uuid", + MsgId: "msg_123", + Prompt: "yo", + CreatedAt: timestamppb.Now(), + }, + expectedErr: "failed to parse interception_id", + }, + { + name: "database error", + request: &proto.RecordPromptUsageRequest{ + InterceptionId: uuid.NewString(), + MsgId: "msg_123", + Prompt: "yo", + CreatedAt: timestamppb.Now(), + }, + setupMocks: func(t *testing.T, db *dbmock.MockStore, req *proto.RecordPromptUsageRequest) { + db.EXPECT().InsertAIBridgeUserPrompt(gomock.Any(), gomock.Any()).Return(sql.ErrConnDone) + }, + expectedErr: "insert user prompt", + }, + }, + ) +} + +func TestRecordToolUsage(t *testing.T) { + t.Parallel() + + var ( + metadataProto = map[string]*anypb.Any{ + "key": mustMarshalAny(t, &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: 123.45}}), + } + metadataJSON = `{"key":123.45}` + ) + + testRecordMethod(t, + func(srv *aibridgedserver.Server, ctx context.Context, req *proto.RecordToolUsageRequest) (*proto.RecordToolUsageResponse, error) { + return srv.RecordToolUsage(ctx, req) + }, + []testRecordMethodCase[*proto.RecordToolUsageRequest]{ + { + name: "valid tool usage with all fields", + request: &proto.RecordToolUsageRequest{ + InterceptionId: uuid.NewString(), + MsgId: "msg_123", + ServerUrl: strPtr("https://api.example.com"), + Tool: "read_file", + Input: `{"path": "/etc/hosts"}`, + Injected: false, + InvocationError: strPtr("permission denied"), + Metadata: metadataProto, + CreatedAt: timestamppb.Now(), + }, + setupMocks: func(t *testing.T, db *dbmock.MockStore, req *proto.RecordToolUsageRequest) { + interceptionID, err := uuid.Parse(req.GetInterceptionId()) + assert.NoError(t, err, "parse interception UUID") + + dbServerURL := sql.NullString{} + if req.ServerUrl != nil { + dbServerURL.String = *req.ServerUrl + dbServerURL.Valid = true + } + + dbInvocationError := sql.NullString{} + if req.InvocationError != nil { + dbInvocationError.String = *req.InvocationError + dbInvocationError.Valid = true + } + + db.EXPECT().InsertAIBridgeToolUsage(gomock.Any(), gomock.Cond(func(p database.InsertAIBridgeToolUsageParams) bool { + if !assert.NotEqual(t, uuid.Nil, p.ID, "ID") || + !assert.Equal(t, interceptionID, p.InterceptionID, "interception ID") || + !assert.Equal(t, req.GetMsgId(), p.ProviderResponseID, "provider response ID") || + !assert.Equal(t, req.GetTool(), p.Tool, "tool") || + !assert.Equal(t, dbServerURL, p.ServerUrl, "server URL") || + !assert.Equal(t, req.GetInput(), p.Input, "input") || + !assert.Equal(t, req.GetInjected(), p.Injected, "injected") || + !assert.Equal(t, dbInvocationError, p.InvocationError, "invocation error") || + !assert.JSONEq(t, metadataJSON, string(p.Metadata), "metadata") || + !assert.WithinDuration(t, req.GetCreatedAt().AsTime(), p.CreatedAt, time.Second, "created at") { + return false + } + return true + })).Return(nil) + }, + }, + { + name: "invalid interception ID", + request: &proto.RecordToolUsageRequest{ + InterceptionId: "not-a-uuid", + MsgId: "msg_123", + Tool: "read_file", + Input: `{"path": "/etc/hosts"}`, + CreatedAt: timestamppb.Now(), + }, + expectedErr: "failed to parse interception_id", + }, + { + name: "database error", + request: &proto.RecordToolUsageRequest{ + InterceptionId: uuid.NewString(), + MsgId: "msg_123", + Tool: "read_file", + Input: `{"path": "/etc/hosts"}`, + CreatedAt: timestamppb.Now(), + }, + setupMocks: func(t *testing.T, db *dbmock.MockStore, req *proto.RecordToolUsageRequest) { + db.EXPECT().InsertAIBridgeToolUsage(gomock.Any(), gomock.Any()).Return(sql.ErrConnDone) + }, + expectedErr: "insert tool usage", + }, + }, + ) +} + +type testRecordMethodCase[Req any] struct { + name string + request Req + // setupMocks is called with the mock store and the above request. + setupMocks func(t *testing.T, db *dbmock.MockStore, req Req) + expectedErr string +} + +// testRecordMethod is a helper that abstracts the common testing pattern for all Record* methods. +func testRecordMethod[Req any, Resp any]( + t *testing.T, + callMethod func(srv *aibridgedserver.Server, ctx context.Context, req Req) (Resp, error), + cases []testRecordMethodCase[Req], +) { + t.Helper() + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + db := dbmock.NewMockStore(ctrl) + logger := testutil.Logger(t) + + if tc.setupMocks != nil { + tc.setupMocks(t, db, tc.request) + } + + ctx := testutil.Context(t, testutil.WaitLong) + srv, err := aibridgedserver.NewServer(ctx, db, logger, "/", nil, requiredExperiments) + require.NoError(t, err) + + resp, err := callMethod(srv, ctx, tc.request) + if tc.expectedErr != "" { + require.Error(t, err, "Expected error for test case: %s", tc.name) + require.Contains(t, err.Error(), tc.expectedErr) + } else { + require.NoError(t, err, "Unexpected error for test case: %s", tc.name) + require.NotNil(t, resp) + } + }) + } +} + +// Helper functions. +func mustMarshalAny(t *testing.T, msg protobufproto.Message) *anypb.Any { + t.Helper() + v, err := anypb.New(msg) + require.NoError(t, err) + return v +} + +func strPtr(s string) *string { + return &s +}