Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
20 changes: 20 additions & 0 deletions examples/06-Storage-no-agent/03-GCS.ts
Original file line number Diff line number Diff line change
@@ -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,
Comment on lines +5 to +8
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Env private key likely breaks without newline restoration

Most env-injected SA keys require replace(/\\n/g, '\n'). Without it, auth fails.

Apply:

-        privateKey: process.env.GCP_PRIVATE_KEY,
+        privateKey: process.env.GCP_PRIVATE_KEY?.replace(/\\n/g, '\n'),
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
projectId: process.env.GCP_PROJECT_ID,
clientEmail: process.env.GCP_CLIENT_EMAIL,
privateKey: process.env.GCP_PRIVATE_KEY,
bucket: process.env.GCP_BUCKET_NAME,
projectId: process.env.GCP_PROJECT_ID,
clientEmail: process.env.GCP_CLIENT_EMAIL,
privateKey: process.env.GCP_PRIVATE_KEY?.replace(/\\n/g, '\n'),
bucket: process.env.GCP_BUCKET_NAME,
πŸ€– Prompt for AI Agents
In examples/06-Storage-no-agent/03-GCS.ts around lines 5 to 8 the service
account privateKey pulled from process.env.GCP_PRIVATE_KEY will fail auth if
newline characters are escaped; update the assignment to restore real newlines
by replacing escaped sequences (e.g. call replace(/\\n/g, '\n') on
process.env.GCP_PRIVATE_KEY) before passing it to the client constructor,
ensuring you handle undefined safely (e.g. fallback or throw a clear error if
the env var is missing).

});

await gcsStorage.write('test.txt', 'Hello, world!');

const data = await gcsStorage.read('test.txt');

const dataAsString = data.toString();

console.log(dataAsString);
}

main();
47 changes: 47 additions & 0 deletions packages/core/docs/connectors/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
},
});
```
Comment on lines +101 to +115
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

Private key newline gotcha (common failure in env vars)

Service account keys stored in env vars often need newline restoration. Add a tip:

-            privateKey: process.env.GCP_PRIVATE_KEY,
+            // If stored as single-line env var, restore newlines:
+            privateKey: process.env.GCP_PRIVATE_KEY?.replace(/\\n/g, '\n'),

I can update the example + add a warning callout.

πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```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',
},
},
});
```
πŸ€– Prompt for AI Agents
In packages/core/docs/connectors/storage.md around lines 101 to 115, the example
uses process.env.GCP_PRIVATE_KEY directly which commonly fails because
environment variables lose literal newlines; update the docs to include a short
tip and example showing restoration of newlines (e.g., replace escaped "\\n"
with actual "\n" before use) and add a brief warning callout about storing
service account keys in env vars and needing to restore newlines so the private
key is parsed correctly by GCP clients.


**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
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
145 changes: 145 additions & 0 deletions packages/core/src/helpers/GCSCache.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { Storage, Bucket, LifecycleRule } from '@google-cloud/storage';
import { Logger } from '@sre/helpers/Log.helper';

const console = Logger('GCSCache');

export function generateLifecycleRules(): LifecycleRule[] {
const rules: LifecycleRule[] = [];

// Add rules for 1-100 days
for (let i = 1; i < 100; i++) {
rules.push({
condition: {
age: i,
matchesSuffix: [`ExpireAfter${i}Days`]
},
action: {
type: 'Delete'
}
});
}

// Add rules for 110-1000 days with 10-day steps
for (let i = 100; i < 1000; i += 10) {
rules.push({
condition: {
age: i,
matchesSuffix: [`ExpireAfter${i}Days`]
},
action: {
type: 'Delete'
}
});
}

// Add rules for 1000-10000 days with 100-day steps
for (let i = 1000; i <= 10000; i += 100) {
rules.push({
condition: {
age: i,
matchesSuffix: [`ExpireAfter${i}Days`]
},
action: {
type: 'Delete'
}
});
}

return rules;
}

export function generateExpiryMetadata(expiryDays: number) {
let metadataValue: string;

if (expiryDays >= 1 && expiryDays < 100) {
metadataValue = `ExpireAfter${expiryDays}Days`;
} else if (expiryDays >= 100 && expiryDays < 1000) {
const roundedUpDays = Math.ceil(expiryDays / 10) * 10;
metadataValue = `ExpireAfter${roundedUpDays}Days`;
} else if (expiryDays >= 1000 && expiryDays <= 10000) {
const roundedUpDays = Math.ceil(expiryDays / 100) * 100;
metadataValue = `ExpireAfter${roundedUpDays}Days`;
} else {
throw new Error('Invalid expiry days. Please provide a valid expiry days value.');
}

return {
Key: 'expiry-tag',
Value: metadataValue,
};
}

export function getNonExistingRules(existingRules: LifecycleRule[], newRules: LifecycleRule[]): LifecycleRule[] {
return newRules.filter((newRule) =>
!existingRules.some((existingRule) =>
existingRule.condition.age === newRule.condition.age &&
JSON.stringify(existingRule.condition.matchesSuffix) === JSON.stringify(newRule.condition.matchesSuffix)
)
);
}

export function ttlToExpiryDays(ttl: number): number {
// seconds to days
return Math.ceil(ttl / (60 * 60 * 24));
}

export async function checkAndInstallLifecycleRules(bucketName: string, storage: Storage): Promise<void> {
// Validate inputs
if (!bucketName || bucketName.trim() === '') {
throw new Error('Bucket name is required and cannot be empty');
}

if (!storage) {
throw new Error('Storage client is required');
}

console.log(`Checking lifecycle rules for GCS bucket: ${bucketName}`);

try {
const bucket = storage.bucket(bucketName);

// Check existing lifecycle configuration
const [metadata] = await bucket.getMetadata();
const existingRules = metadata.lifecycle?.rule || [];

const newRules = generateLifecycleRules();
const nonExistingNewRules = getNonExistingRules(existingRules, newRules);

if (nonExistingNewRules.length > 0) {
const allRules = [...existingRules, ...nonExistingNewRules];

await bucket.setMetadata({
lifecycle: {
rule: allRules
}
});

console.log(`Added ${nonExistingNewRules.length} new lifecycle rules to GCS bucket: ${bucketName}`);
} else {
console.log('Lifecycle configuration already exists');
}
} catch (error) {
if (error.code === 404) {
console.log('Bucket not found or no lifecycle configuration. Creating new configuration...');

const bucket = storage.bucket(bucketName);
const lifecycleRules = generateLifecycleRules();

await bucket.setMetadata({
lifecycle: {
rule: lifecycleRules
}
});

console.log('Lifecycle configuration created successfully.');
} else {
console.error('Error checking lifecycle configuration:', error);
console.error('Bucket name provided:', bucketName);
console.error('Error details:', {
name: error.name,
message: error.message,
code: error.code,
});
}
}
}
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export * from './helpers/JsonContent.helper';
export * from './helpers/LocalCache.helper';
export * from './helpers/Log.helper';
export * from './helpers/OpenApiParser.helper';
export * from './helpers/S3Cache.helper';
export * from './helpers/SmythURI.helper';
export * from './helpers/Sysconfig.helper';
export * from './helpers/TemplateString.helper';
Expand Down Expand Up @@ -160,6 +159,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';
Expand Down
Loading