diff --git a/README.md b/README.md index e15ada3d5..7ad43696a 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ SmythOS provides a complete **Operating System for Agentic AI**. Just as traditi ### Unified Resource Abstraction -SmythOS provides a **unified interface for all resources**, ensuring consistency and simplicity across your entire AI platform. Whether you're storing a file locally, on S3, or any other storage provider, you don't need to worry about the underlying implementation details. SmythOS offers a powerful abstraction layer where all providers expose the same functions and APIs. +SmythOS provides a **unified interface for all resources**, ensuring consistency and simplicity across your entire AI platform. Whether you're storing a file locally, on S3, GCS, or any other storage provider, you don't need to worry about the underlying implementation details. SmythOS offers a powerful abstraction layer where all providers expose the same functions and APIs. This principle applies to **all services** - not just storage. Whether you're working with VectorDBs, cache (Redis, RAM), LLMs (OpenAI, Anthropic), or any other resource, the interface remains consistent across providers. diff --git a/examples/06-Storage-no-agent/03-GCS.ts b/examples/06-Storage-no-agent/03-GCS.ts new file mode 100644 index 000000000..06ee7cb53 --- /dev/null +++ b/examples/06-Storage-no-agent/03-GCS.ts @@ -0,0 +1,20 @@ +import { Storage } from '@smythos/sdk'; + +async function main() { + const gcsStorage = Storage.GCS({ + projectId: process.env.GCP_PROJECT_ID, + clientEmail: process.env.GCP_CLIENT_EMAIL, + privateKey: process.env.GCP_PRIVATE_KEY, + bucket: process.env.GCP_BUCKET_NAME, + }); + + await gcsStorage.write('test.txt', 'Hello, world!'); + + const data = await gcsStorage.read('test.txt'); + + const dataAsString = data.toString(); + + console.log(dataAsString); +} + +main(); diff --git a/packages/core/docs/connectors/storage.md b/packages/core/docs/connectors/storage.md index 84b2342eb..75c85f68f 100644 --- a/packages/core/docs/connectors/storage.md +++ b/packages/core/docs/connectors/storage.md @@ -81,3 +81,50 @@ SRE.init({ - Store credentials securely using environment variables or AWS Secrets Manager - Configure appropriate bucket policies and CORS settings - Enable encryption at rest and in transit for sensitive data + +--- + +### GCS + +**Role**: Google Cloud Storage connector +**Summary**: Provides scalable cloud storage using Google Cloud Storage, suitable for production deployments requiring high availability and durability on Google Cloud Platform. + +| Setting | Type | Required | Default | Description | +| ------------- | ------ | -------- | ------- | ----------------------------------------------- | +| `projectId` | string | Yes | - | Google Cloud Project ID where the bucket is located | +| `clientEmail` | string | Yes | - | Service account email address | +| `privateKey` | string | Yes | - | Service account private key | +| `bucket` | string | Yes | - | GCS bucket name for storing files | + +**Example Configuration:** + +```typescript +import { SRE } from '@smythos/sre'; + +SRE.init({ + Storage: { + Connector: 'GCS', + Settings: { + projectId: 'my-project-id', + clientEmail: process.env.GCP_CLIENT_EMAIL, + privateKey: process.env.GCP_PRIVATE_KEY, + bucket: 'my-app-storage', + }, + }, +}); +``` + +**Use Cases:** + +- Production environments requiring scalability on Google Cloud Platform +- Multi-region deployments within GCP infrastructure +- Applications with high availability requirements +- Integration with Google Cloud ecosystem +- Large-scale data storage and processing + +**Security Notes:** + +- Use service accounts with minimal required permissions +- Store credentials securely using environment variables or Google Secret Manager +- Configure appropriate bucket policies and IAM settings +- Enable encryption at rest and in transit for sensitive data diff --git a/packages/core/package.json b/packages/core/package.json index 483e69c74..ff6a084c7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -61,6 +61,7 @@ "@aws-sdk/client-s3": "^3.826.0", "@aws-sdk/client-secrets-manager": "^3.826.0", "@faker-js/faker": "^9.8.0", + "@google-cloud/storage": "^7.17.0", "@google-cloud/vertexai": "^1.7.0", "@google/genai": "^1.10.0", "@google/generative-ai": "^0.14.1", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 81c3eab98..dc1a4fc8a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -160,6 +160,7 @@ export * from './subsystems/IO/NKV.service/connectors/NKVRAM.class'; export * from './subsystems/IO/NKV.service/connectors/NKVRedis.class'; export * from './subsystems/IO/Router.service/connectors/ExpressRouter.class'; export * from './subsystems/IO/Router.service/connectors/NullRouter.class'; +export * from './subsystems/IO/Storage.service/connectors/GCSStorage.class'; export * from './subsystems/IO/Storage.service/connectors/LocalStorage.class'; export * from './subsystems/IO/Storage.service/connectors/S3Storage.class'; export * from './subsystems/IO/VectorDB.service/connectors/MilvusVectorDB.class'; diff --git a/packages/core/src/subsystems/IO/Storage.service/connectors/GCSStorage.class.ts b/packages/core/src/subsystems/IO/Storage.service/connectors/GCSStorage.class.ts new file mode 100644 index 000000000..ad32b47fe --- /dev/null +++ b/packages/core/src/subsystems/IO/Storage.service/connectors/GCSStorage.class.ts @@ -0,0 +1,445 @@ +//==[ SRE: GCSStorage ]====================== + +import { Storage, File, Bucket } from '@google-cloud/storage'; + +import { Logger } from '@sre/helpers/Log.helper'; +import { IStorageRequest, StorageConnector } from '@sre/IO/Storage.service/StorageConnector'; +import { ACL } from '@sre/Security/AccessControl/ACL.class'; +import { IAccessCandidate, IACL, TAccessLevel, TAccessResult, TAccessRole } from '@sre/types/ACL.types'; +import { StorageData, StorageMetadata } from '@sre/types/Storage.types'; + +import { AccessRequest } from '@sre/Security/AccessControl/AccessRequest.class'; +import { AccessCandidate } from '@sre/Security/AccessControl/AccessCandidate.class'; +import { SecureConnector } from '@sre/Security/SecureConnector.class'; +import { ConnectorService } from '@sre/Core/ConnectorsService'; + +const console = Logger('GCSStorage'); + +export type GCSConfig = { + projectId: string; + clientEmail: string; // Service account email (equivalent to accessKeyId) + privateKey: string; // Service account private key (equivalent to secretAccessKey) + bucket: string; +}; + +export class GCSStorage extends StorageConnector { + public name = 'GCSStorage'; + private storage: Storage; + private bucket: Bucket; + private bucketName: string; + private isInitialized: boolean = false; + private initializationPromise: Promise | null = null; + + constructor(protected _settings: GCSConfig) { + super(_settings); + + // Validate required configuration + if (!_settings.bucket || _settings.bucket.trim() === '') { + console.warn('GCS bucket name is required and cannot be empty, connector not initialized'); + return; + } + + if (!_settings.projectId || _settings.projectId.trim() === '') { + console.warn('GCS project ID is required and cannot be empty, connector not initialized'); + return; + } + + if (!_settings.clientEmail || _settings.clientEmail.trim() === '') { + console.warn('GCS client email is required and cannot be empty, connector not initialized'); + return; + } + + if (!_settings.privateKey || _settings.privateKey.trim() === '') { + console.warn('GCS private key is required and cannot be empty, connector not initialized'); + return; + } + + this.bucketName = _settings.bucket; + + const clientConfig: any = { + projectId: _settings.projectId, + }; + + if (_settings.clientEmail && _settings.privateKey) { + clientConfig.credentials = { + client_email: _settings.clientEmail, + private_key: _settings.privateKey.replace(/\\n/g, '\n'), // Handle escaped newlines + type: 'service_account' + }; + } + + this.storage = new Storage(clientConfig); + this.bucket = this.storage.bucket(this.bucketName); + + // Don't call initialize() synchronously in constructor + // It will be called when needed by methods that require initialization + } + + private async ensureInitialized(): Promise { + if (this.isInitialized) { + return; + } + + if (this.initializationPromise) { + return this.initializationPromise; + } + + this.initializationPromise = this.initialize(); + return this.initializationPromise; + } + + private async initialize(): Promise { + if (!this.storage) { + console.warn('GCS storage client not initialized'); + return; + } + if (this.isInitialized) { + return; + } + + // No initialization needed - bucket lifecycle rule should be configured externally + this.isInitialized = true; + } + + /** + * Reads an object from the GCS bucket. + * + * @param {string} resourceId - The name of the object to be read. + * @returns {Promise} - A promise that resolves with the object data. + */ + @SecureConnector.AccessControl + public async read(acRequest: AccessRequest, resourceId: string) { + await this.ensureInitialized(); + + const file = this.bucket.file(resourceId); + + try { + // Check if file exists and get metadata + const [exists] = await file.exists(); + if (!exists) { + return undefined; + } + + const [metadata] = await file.getMetadata(); + + + const [buffer] = await file.download(); + return buffer; + } catch (error) { + if (error.code === 404) { + return undefined; + } + console.error(`Error reading object from GCS`, error.name, error.message); + throw error; + } + } + + @SecureConnector.AccessControl + async getMetadata(acRequest: AccessRequest, resourceId: string): Promise { + await this.ensureInitialized(); + + try { + const gcsMetadata = await this.getGCSMetadata(resourceId); + return gcsMetadata as StorageMetadata; + } catch (error) { + console.error(`Error getting metadata from GCS`, error.name, error.message); + throw error; + } + } + + @SecureConnector.AccessControl + async setMetadata(acRequest: AccessRequest, resourceId: string, metadata: StorageMetadata) { + await this.ensureInitialized(); + + try { + let gcsMetadata = await this.getGCSMetadata(resourceId); + if (!gcsMetadata) gcsMetadata = {}; + gcsMetadata = { ...gcsMetadata, ...metadata }; + await this.setGCSMetadata(resourceId, gcsMetadata); + } catch (error) { + console.error(`Error setting metadata in GCS`, error); + throw error; + } + } + + /** + * Writes an object to the GCS bucket. + * + * @param {string} resourceId - The name of the object to be written. + * @param {any} value - The value of the object to be written. + * @param {Metadata} metadata - Optional metadata to be associated with the object. + * @returns {Promise} - A promise that resolves when the object has been written. + */ + @SecureConnector.AccessControl + async write(acRequest: AccessRequest, resourceId: string, value: StorageData, acl?: IACL, metadata?: StorageMetadata, ttl?: number): Promise { + await this.ensureInitialized(); + + const accessCandidate = acRequest.candidate; + let aclData = ACL.from(acl).addAccess(accessCandidate.role, accessCandidate.id, TAccessLevel.Owner).ACL; + let gcsMetadata = { + ...metadata, + 'gcs-acl': aclData, + }; + + const file = this.bucket.file(resourceId); + + try { + const uploadOptions: any = { + metadata: { + metadata: this.serializeGCSMetadata(gcsMetadata), + }, + }; + + if (gcsMetadata['ContentType']) { + uploadOptions.metadata.contentType = gcsMetadata['ContentType']; + } + + if (ttl) { + const deleteAt = new Date(Date.now() + ttl * 1000); + uploadOptions.metadata.customTime = deleteAt.toISOString(); + } + + await file.save(value, uploadOptions); + } catch (error) { + console.error(`Error writing object to GCS`, error.name, error.message); + throw error; + } + } + + /** + * Deletes an object from the GCS bucket. + * + * @param {string} resourceId - The name of the object to be deleted. + * @returns {Promise} - A promise that resolves when the object has been deleted. + */ + @SecureConnector.AccessControl + async delete(acRequest: AccessRequest, resourceId: string): Promise { + await this.ensureInitialized(); + + const file = this.bucket.file(resourceId); + + try { + await file.delete(); + } catch (error) { + console.error(`Error deleting object from GCS`, error.name, error.message); + throw error; + } + } + + @SecureConnector.AccessControl + async exists(acRequest: AccessRequest, resourceId: string): Promise { + await this.ensureInitialized(); + + const file = this.bucket.file(resourceId); + + try { + const [exists] = await file.exists(); + return exists; + } catch (error) { + console.error(`Error checking object existence in GCS`, error.name, error.message); + throw error; + } + } + + //this determines the access rights for the requested resource + //the connector should check if the resource exists or not + //if the resource exists we read it's ACL and return it + //if the resource does not exist we return an write access ACL for the candidate + public async getResourceACL(resourceId: string, candidate: IAccessCandidate) { + await this.ensureInitialized(); + + try { + const gcsMetadata = await this.getGCSMetadata(resourceId); + const exists = gcsMetadata !== undefined; //undefined metadata means the resource does not exist + + if (!exists) { + //the resource does not exist yet, we grant write access to the candidate in order to allow the resource creation + return new ACL().addAccess(candidate.role, candidate.id, TAccessLevel.Owner); + } + return ACL.from(gcsMetadata?.['gcs-acl'] as IACL); + } catch (error) { + console.error('Error in getResourceACL:', error); + // If we can't check the resource, assume it doesn't exist and grant write access + return new ACL().addAccess(candidate.role, candidate.id, TAccessLevel.Owner); + } + } + + @SecureConnector.AccessControl + async getACL(acRequest: AccessRequest, resourceId: string): Promise { + await this.ensureInitialized(); + + try { + const gcsMetadata = await this.getGCSMetadata(resourceId); + return ACL.from(gcsMetadata?.['gcs-acl'] as IACL); + } catch (error) { + console.error(`Error getting access rights from GCS`, error.name, error.message); + throw error; + } + } + + @SecureConnector.AccessControl + async setACL(acRequest: AccessRequest, resourceId: string, acl: IACL) { + await this.ensureInitialized(); + + try { + let gcsMetadata = await this.getGCSMetadata(resourceId); + if (!gcsMetadata) gcsMetadata = {}; + //when setting ACL make sure to not lose ownership + gcsMetadata['gcs-acl'] = ACL.from(acl).addAccess(acRequest.candidate.role, acRequest.candidate.id, TAccessLevel.Owner).ACL; + await this.setGCSMetadata(resourceId, gcsMetadata); + } catch (error) { + console.error(`Error setting access rights in GCS`, error); + throw error; + } + } + + @SecureConnector.AccessControl + async expire(acRequest: AccessRequest, resourceId: string, ttl: number) { + await this.ensureInitialized(); + + const deleteAt = new Date(Date.now() + ttl * 1000); + + const file = this.bucket.file(resourceId); + + try { + await file.setMetadata({ + customTime: deleteAt.toISOString() + }); + } catch (error) { + console.error(`Error setting expiration for ${resourceId}:`, error); + throw error; + } + } + + private migrateMetadata(metadata: Record): Record { + if (!metadata.agentid && !metadata.teamid && !metadata.userid) return metadata as Record; + else { + const convertibleItems = ['agentid', 'teamid', 'userid']; + const aclHelper = new ACL(); + + for (let key of convertibleItems) { + if (!metadata[key]) continue; + const role = key === 'agentid' ? TAccessRole.Agent : key === 'teamid' ? TAccessRole.Team : TAccessRole.User; + aclHelper.addAccess(role, metadata[key].toString(), [TAccessLevel.Owner, TAccessLevel.Read, TAccessLevel.Write]); + delete metadata[key]; + } + aclHelper.migrated = true; + const newMetadata: Record = { + 'gcs-acl': aclHelper.ACL, + }; + //copy remaining metadata + for (let key in metadata) { + newMetadata[key] = metadata[key]; + } + + return newMetadata; + } + } + + private serializeGCSMetadata(gcsMetadata: Record): Record { + let metadata: Record = {}; + if (gcsMetadata['gcs-acl']) { + if (gcsMetadata['gcs-acl']) { + metadata['gcs-acl'] = + typeof gcsMetadata['gcs-acl'] == 'string' + ? gcsMetadata['gcs-acl'] + : ACL.from(gcsMetadata['gcs-acl']).serializedACL; + } + + delete gcsMetadata['gcs-acl']; + } + + for (let key in gcsMetadata) { + if (key == 'ContentType') continue; //skip ContentType as it's handled separately + const value = gcsMetadata[key]; + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value === null) { + metadata[key] = value; + } else { + metadata[key] = JSON.stringify(value); + } + } + + return metadata; + } + + private deserializeGCSMetadata(metadata: Record): Record { + let deserializedMetadata: Record = {}; + + for (let key in metadata) { + const value = metadata[key]; + if (key === 'gcs-acl') { + if (typeof value === 'string') { + deserializedMetadata[key] = ACL.from(value).ACL; + } else { + deserializedMetadata[key] = value; + } + continue; + } + + if (typeof value === 'string') { + try { + deserializedMetadata[key] = JSON.parse(value); + } catch (error) { + deserializedMetadata[key] = value; + } + } else { + deserializedMetadata[key] = value; + } + } + + // Migration support for legacy metadata format + deserializedMetadata = this.migrateMetadata(deserializedMetadata) as Record; + + return deserializedMetadata; + } + + private async getGCSMetadata(resourceId: string): Promise | undefined> { + try { + const file = this.bucket.file(resourceId); + const [exists] = await file.exists(); + + if (!exists) { + return undefined; + } + + const [metadata] = await file.getMetadata(); + const customMetadata = metadata.metadata || {}; + + let deserializedMetadata: Record = this.deserializeGCSMetadata(customMetadata); + + if (!deserializedMetadata['ContentType']) { + deserializedMetadata['ContentType'] = metadata.contentType || 'application/octet-stream'; + } + + return deserializedMetadata; + } catch (error) { + if (error.code === 404) { + return undefined; + } + console.error(`Error reading object metadata from GCS`, error.name, error.message); + throw error; + } + } + + private async setGCSMetadata(resourceId: string, metadata: Record): Promise { + try { + const file = this.bucket.file(resourceId); + + // Get current file to preserve content + const [exists] = await file.exists(); + if (!exists) { + throw new Error(`File ${resourceId} does not exist`); + } + + const serializedMetadata = this.serializeGCSMetadata(metadata); + + // Update metadata + await file.setMetadata({ + metadata: serializedMetadata, + }); + } catch (error) { + console.error(`Error setting object metadata in GCS`, error.name, error.message); + throw error; + } + } +} \ No newline at end of file diff --git a/packages/core/src/subsystems/IO/Storage.service/index.ts b/packages/core/src/subsystems/IO/Storage.service/index.ts index 87b4b5d8e..07d8b3e10 100644 --- a/packages/core/src/subsystems/IO/Storage.service/index.ts +++ b/packages/core/src/subsystems/IO/Storage.service/index.ts @@ -4,10 +4,12 @@ import { ConnectorService, ConnectorServiceProvider } from '@sre/Core/Connectors import { TConnectorService } from '@sre/types/SRE.types'; import { S3Storage } from './connectors/S3Storage.class'; import { LocalStorage } from './connectors/LocalStorage.class'; +import { GCSStorage } from './connectors/GCSStorage.class'; export class StorageService extends ConnectorServiceProvider { public register() { ConnectorService.register(TConnectorService.Storage, 'S3', S3Storage); ConnectorService.register(TConnectorService.Storage, 'LocalStorage', LocalStorage); + ConnectorService.register(TConnectorService.Storage, 'GCS', GCSStorage); } } diff --git a/packages/sdk/docs/07-services.md b/packages/sdk/docs/07-services.md index 80a2d348e..9adb158ea 100644 --- a/packages/sdk/docs/07-services.md +++ b/packages/sdk/docs/07-services.md @@ -43,7 +43,7 @@ An agent provides access to its configured storage providers. ```typescript // 'agent' is an existing Agent instance -const storage = agent.storage.LocalStorage(); // Or agent.storage.S3() etc. +const storage = agent.storage.LocalStorage(); // Or agent.storage.S3(), agent.storage.GCS() etc. const fileUri = await storage.write('my-file.txt', 'This is some important data.'); console.log(`File written to: ${fileUri}`); const content = await storage.read(fileUri); diff --git a/packages/sdk/src/types/generated/Storage.types.ts b/packages/sdk/src/types/generated/Storage.types.ts index b5c7f5310..23af7d4dd 100644 --- a/packages/sdk/src/types/generated/Storage.types.ts +++ b/packages/sdk/src/types/generated/Storage.types.ts @@ -1,5 +1,6 @@ //!!! DO NOT EDIT THIS FILE, IT IS AUTO-GENERATED !!!// +import { GCSConfig } from '@smythos/sre'; import { LocalStorageConfig } from '@smythos/sre'; import { S3Config } from '@smythos/sre'; import { AccessCandidate } from '@smythos/sre'; @@ -8,6 +9,7 @@ import { Scope } from '../SDKTypes'; // Define storage provider settings mapping export type TStorageProviderSettings = { + GCS: GCSConfig LocalStorage: LocalStorageConfig S3: S3Config }; @@ -23,6 +25,7 @@ export type TStorageProvider = TBuiltinStorageProvider | keyof IStorageProviders // For backward compatibility, export the built-in providers as enum-like object export const TStorageProvider: Record = { + GCS: 'GCS', LocalStorage: 'LocalStorage', S3: 'S3', } as const; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7c176ac5..f99713d1a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -186,6 +186,9 @@ importers: '@faker-js/faker': specifier: ^9.8.0 version: 9.8.0 + '@google-cloud/storage': + specifier: ^7.17.0 + version: 7.17.0(encoding@0.1.13) '@google-cloud/vertexai': specifier: ^1.7.0 version: 1.10.0(encoding@0.1.13) @@ -986,6 +989,22 @@ packages: '@gerrit0/mini-shiki@3.6.0': resolution: {integrity: sha512-KaeJvPNofTEZR9EzVNp/GQzbQqkGfjiu6k3CXKvhVTX+8OoAKSX/k7qxLKOX3B0yh2XqVAc93rsOu48CGt2Qug==} + '@google-cloud/paginator@5.0.2': + resolution: {integrity: sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==} + engines: {node: '>=14.0.0'} + + '@google-cloud/projectify@4.0.0': + resolution: {integrity: sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==} + engines: {node: '>=14.0.0'} + + '@google-cloud/promisify@4.0.0': + resolution: {integrity: sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==} + engines: {node: '>=14'} + + '@google-cloud/storage@7.17.0': + resolution: {integrity: sha512-5m9GoZqKh52a1UqkxDBu/+WVFDALNtHg5up5gNmNbXQWBcV813tzJKsyDtKjOPrlR1em1TxtD7NSPCrObH7koQ==} + engines: {node: '>=14'} + '@google-cloud/vertexai@1.10.0': resolution: {integrity: sha512-HqYqoivNtkq59po8m7KI0n+lWKdz4kabENncYQXZCX/hBWJfXtKAfR/2nUQsP+TwSfHKoA7zDL2RrJYIv/j3VQ==} engines: {node: '>=18.0.0'} @@ -1902,6 +1921,10 @@ packages: '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@tootallnate/once@2.0.0': + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + '@ts-morph/common@0.24.0': resolution: {integrity: sha512-c1xMmNHWpNselmpIqursHeOHHBTIsJLbB+NuovbTTRCNiTLEr/U9dbJ8qy0jd/O2x5pc3seWuOUN5R2IoOTp8A==} @@ -1923,6 +1946,9 @@ packages: '@types/body-parser@1.19.6': resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + '@types/caseless@0.12.5': + resolution: {integrity: sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==} + '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} @@ -1999,6 +2025,9 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/request@2.48.13': + resolution: {integrity: sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==} + '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -2017,6 +2046,9 @@ packages: '@types/through@0.0.33': resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/triple-beam@1.3.5': resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} @@ -2113,6 +2145,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + agent-base@7.1.3: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} @@ -2191,6 +2227,10 @@ packages: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} + arrify@2.0.1: + resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} + engines: {node: '>=8'} + assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} @@ -2694,6 +2734,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + duplexify@4.1.3: + resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -2991,6 +3034,10 @@ packages: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} engines: {node: '>= 14.17'} + form-data@2.5.5: + resolution: {integrity: sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==} + engines: {node: '>= 0.12'} + form-data@4.0.3: resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==} engines: {node: '>= 6'} @@ -3203,6 +3250,9 @@ packages: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} + html-entities@2.6.0: + resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -3217,10 +3267,18 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + http2-wrapper@2.2.1: resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} engines: {node: '>=10.19.0'} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -3783,6 +3841,11 @@ packages: engines: {node: '>=4'} hasBin: true + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + mime@4.0.7: resolution: {integrity: sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ==} engines: {node: '>=16'} @@ -4342,6 +4405,10 @@ packages: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} + retry-request@7.0.2: + resolution: {integrity: sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==} + engines: {node: '>=14'} + retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -4621,6 +4688,12 @@ packages: resolution: {integrity: sha512-Xnt9/HHHYfjZ7NeQLvuQDyL1LnbsbddgMFKCuaQKwGCdJm8LnstZIXop+uOY36UR1UXXoHXfMbC1KlVdVd2JLA==} engines: {node: '>=8.0.0'} + stream-events@1.0.5: + resolution: {integrity: sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==} + + stream-shift@1.0.3: + resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -4680,6 +4753,9 @@ packages: stubborn-fs@1.2.5: resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==} + stubs@3.0.0: + resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -4703,6 +4779,10 @@ packages: teamcity-service-messages@0.1.14: resolution: {integrity: sha512-29aQwaHqm8RMX74u2o/h1KbMLP89FjNiMxD9wbF2BbWOnbM+q+d1sCEC+MqCc4QW3NJykn77OMpTFw/xTHIc0w==} + teeny-request@9.0.0: + resolution: {integrity: sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==} + engines: {node: '>=14'} + terser@5.41.0: resolution: {integrity: sha512-H406eLPXpZbAX14+B8psIuvIr8+3c+2hkuYzpMkoE0ij+NdsVATbA78vb8neA/eqrj7rywa2pIkdmWRsXW6wmw==} engines: {node: '>=10'} @@ -6581,6 +6661,36 @@ snapshots: '@shikijs/types': 3.6.0 '@shikijs/vscode-textmate': 10.0.2 + '@google-cloud/paginator@5.0.2': + dependencies: + arrify: 2.0.1 + extend: 3.0.2 + + '@google-cloud/projectify@4.0.0': {} + + '@google-cloud/promisify@4.0.0': {} + + '@google-cloud/storage@7.17.0(encoding@0.1.13)': + dependencies: + '@google-cloud/paginator': 5.0.2 + '@google-cloud/projectify': 4.0.0 + '@google-cloud/promisify': 4.0.0 + abort-controller: 3.0.0 + async-retry: 1.3.3 + duplexify: 4.1.3 + fast-xml-parser: 4.4.1 + gaxios: 6.7.1(encoding@0.1.13) + google-auth-library: 9.15.1(encoding@0.1.13) + html-entities: 2.6.0 + mime: 3.0.0 + p-limit: 3.1.0 + retry-request: 7.0.2(encoding@0.1.13) + teeny-request: 9.0.0(encoding@0.1.13) + uuid: 8.3.2 + transitivePeerDependencies: + - encoding + - supports-color + '@google-cloud/vertexai@1.10.0(encoding@0.1.13)': dependencies: google-auth-library: 9.15.1(encoding@0.1.13) @@ -7701,6 +7811,8 @@ snapshots: '@tokenizer/token@0.3.0': {} + '@tootallnate/once@2.0.0': {} + '@ts-morph/common@0.24.0': dependencies: fast-glob: 3.3.3 @@ -7726,6 +7838,8 @@ snapshots: '@types/connect': 3.4.38 '@types/node': 20.19.0 + '@types/caseless@0.12.5': {} + '@types/chai@5.2.2': dependencies: '@types/deep-eql': 4.0.2 @@ -7812,6 +7926,13 @@ snapshots: '@types/range-parser@1.2.7': {} + '@types/request@2.48.13': + dependencies: + '@types/caseless': 0.12.5 + '@types/node': 20.19.0 + '@types/tough-cookie': 4.0.5 + form-data: 2.5.5 + '@types/resolve@1.20.2': {} '@types/send@0.17.5': @@ -7835,6 +7956,8 @@ snapshots: dependencies: '@types/node': 20.19.0 + '@types/tough-cookie@4.0.5': {} + '@types/triple-beam@1.3.5': {} '@types/unist@3.0.3': {} @@ -7958,6 +8081,12 @@ snapshots: acorn@8.14.1: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + agent-base@7.1.3: {} agentkeepalive@4.6.0: @@ -8029,6 +8158,8 @@ snapshots: array-union@2.1.0: {} + arrify@2.0.1: {} + assertion-error@1.1.0: {} assertion-error@2.0.1: {} @@ -8592,6 +8723,13 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + duplexify@4.1.3: + dependencies: + end-of-stream: 1.4.5 + inherits: 2.0.4 + readable-stream: 3.6.2 + stream-shift: 1.0.3 + eastasianwidth@0.2.0: {} ecdsa-sig-formatter@1.0.11: @@ -8958,6 +9096,15 @@ snapshots: form-data-encoder@2.1.4: {} + form-data@2.5.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + safe-buffer: 5.2.1 + form-data@4.0.3: dependencies: asynckit: 0.4.0 @@ -9207,6 +9354,8 @@ snapshots: dependencies: lru-cache: 10.4.3 + html-entities@2.6.0: {} + html-escaper@2.0.2: {} http-cache-semantics@4.2.0: {} @@ -9230,11 +9379,26 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + http-proxy-agent@5.0.0: + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + http2-wrapper@2.2.1: dependencies: quick-lru: 5.1.1 resolve-alpn: 1.2.1 + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 @@ -9830,6 +9994,8 @@ snapshots: mime@1.6.0: {} + mime@3.0.0: {} + mime@4.0.7: {} mimic-fn@2.1.0: {} @@ -10411,6 +10577,15 @@ snapshots: onetime: 7.0.0 signal-exit: 4.1.0 + retry-request@7.0.2(encoding@0.1.13): + dependencies: + '@types/request': 2.48.13 + extend: 3.0.2 + teeny-request: 9.0.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + - supports-color + retry@0.13.1: {} reusify@1.1.0: {} @@ -10767,6 +10942,12 @@ snapshots: transitivePeerDependencies: - supports-color + stream-events@1.0.5: + dependencies: + stubs: 3.0.0 + + stream-shift@1.0.3: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -10826,6 +11007,8 @@ snapshots: stubborn-fs@1.2.5: {} + stubs@3.0.0: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -10845,6 +11028,17 @@ snapshots: teamcity-service-messages@0.1.14: {} + teeny-request@9.0.0(encoding@0.1.13): + dependencies: + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + node-fetch: 2.7.0(encoding@0.1.13) + stream-events: 1.0.5 + uuid: 9.0.1 + transitivePeerDependencies: + - encoding + - supports-color + terser@5.41.0: dependencies: '@jridgewell/source-map': 0.3.6