From 8a40ba38cca1998b53fbb35902335e4c167d49f2 Mon Sep 17 00:00:00 2001 From: xm0onh Date: Wed, 1 Jan 2025 20:08:06 -0800 Subject: [PATCH] Add zod for config format - clean coding and refactoring --- auto-agents-framework/.env.sample | 24 ++-- .../agents/tools/utils/agentMemoryContract.ts | 2 +- .../src/agents/tools/utils/agentWallet.ts | 4 +- auto-agents-framework/src/config/index.ts | 110 ++++++++++-------- auto-agents-framework/src/config/schema.ts | 72 ++++++++++++ 5 files changed, 150 insertions(+), 62 deletions(-) create mode 100644 auto-agents-framework/src/config/schema.ts diff --git a/auto-agents-framework/.env.sample b/auto-agents-framework/.env.sample index bb6a00f0..4a2d4a48 100644 --- a/auto-agents-framework/.env.sample +++ b/auto-agents-framework/.env.sample @@ -10,29 +10,29 @@ MAX_MENTIONS=20 MAX_THREAD_LENGTH=20 MAX_MY_RECENT_TWEETS=10 POST_TWEETS=false -RESPONSE_INTERVAL_MINUTES=26 -POST_INTERVAL_MINUTES=30 +RESPONSE_INTERVAL_MS=26 +POST_INTERVAL_MS=30 + +# LLM Configuration +LARGE_LLM_MODEL= +SMALL_LLM_MODEL= +OPENAI_API_KEY= # AutoDrive Configuration AUTO_DRIVE_API_KEY= AUTO_DRIVE_ENCRYPTION_PASSWORD= AUTO_DRIVE_UPLOAD=false -# SC Configuration +# Blockchain Configuration RPC_URL= CONTRACT_ADDRESS= PRIVATE_KEY= -# LLM Configuration -SMALL_LLM_MODEL=gpt-4o-mini -LARGE_LLM_MODEL= -OPENAI_API_KEY= - # SerpAPI Configuration SERPAPI_API_KEY= -# Server Configuration -PORT= - # Environment -NODE_ENV= \ No newline at end of file +NODE_ENV= + +# Retry Limit +RETRY_LIMIT= \ No newline at end of file diff --git a/auto-agents-framework/src/agents/tools/utils/agentMemoryContract.ts b/auto-agents-framework/src/agents/tools/utils/agentMemoryContract.ts index d9ae77c9..f3654b36 100644 --- a/auto-agents-framework/src/agents/tools/utils/agentMemoryContract.ts +++ b/auto-agents-framework/src/agents/tools/utils/agentMemoryContract.ts @@ -4,7 +4,7 @@ import { config } from '../../../config/index.js'; import { wallet } from './agentWallet.js'; import { cidFromBlakeHash, cidToString } from '@autonomys/auto-dag-data'; -const CONTRACT_ADDRESS = config.autoDriveConfig.CONTRACT_ADDRESS as `0x${string}`; +const CONTRACT_ADDRESS = config.blockchainConfig.CONTRACT_ADDRESS as `0x${string}`; const contract = new ethers.Contract(CONTRACT_ADDRESS, MEMORY_ABI, wallet); diff --git a/auto-agents-framework/src/agents/tools/utils/agentWallet.ts b/auto-agents-framework/src/agents/tools/utils/agentWallet.ts index 641aa624..019a2f6d 100644 --- a/auto-agents-framework/src/agents/tools/utils/agentWallet.ts +++ b/auto-agents-framework/src/agents/tools/utils/agentWallet.ts @@ -1,9 +1,9 @@ import { ethers } from 'ethers'; import { config } from '../../../config/index.js'; -const provider = new ethers.JsonRpcProvider(config.autoDriveConfig.RPC_URL); +const provider = new ethers.JsonRpcProvider(config.blockchainConfig.RPC_URL); -export const wallet = new ethers.Wallet(config.autoDriveConfig.PRIVATE_KEY as string, provider); +export const wallet = new ethers.Wallet(config.blockchainConfig.PRIVATE_KEY as string, provider); export async function signMessage(data: object): Promise { const message = JSON.stringify(data); diff --git a/auto-agents-framework/src/config/index.ts b/auto-agents-framework/src/config/index.ts index a8059cfb..17cc5856 100644 --- a/auto-agents-framework/src/config/index.ts +++ b/auto-agents-framework/src/config/index.ts @@ -1,57 +1,73 @@ +import { z } from 'zod'; import dotenv from 'dotenv'; +import { configSchema } from './schema.js'; import path from 'path'; import { fileURLToPath } from 'url'; -import { dirname } from 'path'; -// Get the equivalent of __dirname in ESM const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); +const __dirname = path.dirname(__filename); -dotenv.config(); +dotenv.config({ path: path.resolve(__dirname, '../../.env') }); -const twitterConfig = { - USERNAME: process.env.TWITTER_USERNAME || '', - PASSWORD: process.env.TWITTER_PASSWORD || '', - COOKIES_PATH: process.env.TWITTER_COOKIES_PATH || 'cookies.json', - NUM_TIMELINE_TWEETS: Number(process.env.NUM_TIMELINE_TWEETS) || 10, - NUM_FOLLOWING_RECENT_TWEETS: Number(process.env.NUM_FOLLOWING_RECENT_TWEETS) || 10, - NUM_RANDOM_FOLLOWERS: Number(process.env.NUM_RANDOM_FOLLOWERS) || 5, - MAX_MENTIONS: Number(process.env.MAX_MENTIONS) || 5, - MAX_THREAD_LENGTH: Number(process.env.MAX_THREAD_LENGTH) || 20, - MAX_MY_RECENT_TWEETS: Number(process.env.MAX_MY_RECENT_TWEETS) || 10, - POST_TWEETS: process.env.POST_TWEETS === 'true', - RESPONSE_INTERVAL_MS: Number(process.env.RESPONSE_INTERVAL_MINUTES) * 60 * 1000 || 26 * 60 * 1000, - POST_INTERVAL_MS: Number(process.env.POST_INTERVAL_MINUTES) * 60 * 1000 || 30 * 60 * 1000, -}; +function formatZodError(error: z.ZodError) { + const missingVars = error.issues.map(issue => { + const path = issue.path.join('.'); + return `- ${path}: ${issue.message}`; + }); + return `Missing or invalid environment variables: + \n${missingVars.join('\n')} + \nPlease check your .env file and ensure all required variables are set correctly.`; +} -const llmConfig = { - LARGE_LLM_MODEL: process.env.LARGE_LLM_MODEL || 'gpt-4o', - SMALL_LLM_MODEL: process.env.SMALL_LLM_MODEL || 'gpt-4o-mini', - OPENAI_API_KEY: process.env.OPENAI_API_KEY || '', -}; +export const config = (() => { + try { + const rawConfig = { + twitterConfig: { + USERNAME: process.env.TWITTER_USERNAME || '', + PASSWORD: process.env.TWITTER_PASSWORD || '', + COOKIES_PATH: process.env.TWITTER_COOKIES_PATH || 'cookies.json', + NUM_TIMELINE_TWEETS: Number(process.env.NUM_TIMELINE_TWEETS) || 10, + NUM_FOLLOWING_RECENT_TWEETS: Number(process.env.NUM_FOLLOWING_RECENT_TWEETS) || 10, + NUM_RANDOM_FOLLOWERS: Number(process.env.NUM_RANDOM_FOLLOWERS) || 5, + MAX_MENTIONS: Number(process.env.MAX_MENTIONS) || 5, + MAX_THREAD_LENGTH: Number(process.env.MAX_THREAD_LENGTH) || 20, + MAX_MY_RECENT_TWEETS: Number(process.env.MAX_MY_RECENT_TWEETS) || 10, + POST_TWEETS: process.env.POST_TWEETS === 'true', + RESPONSE_INTERVAL_MS: (Number(process.env.RESPONSE_INTERVAL_MS) || 26) * 60 * 1000, + POST_INTERVAL_MS: (Number(process.env.POST_INTERVAL_MS) || 30) * 60 * 1000, + }, + llmConfig: { + LARGE_LLM_MODEL: process.env.LARGE_LLM_MODEL || 'gpt-4', + SMALL_LLM_MODEL: process.env.SMALL_LLM_MODEL || 'gpt-4-mini', + OPENAI_API_KEY: process.env.OPENAI_API_KEY || '', + }, + autoDriveConfig: { + AUTO_DRIVE_API_KEY: process.env.AUTO_DRIVE_API_KEY, + AUTO_DRIVE_ENCRYPTION_PASSWORD: process.env.AUTO_DRIVE_ENCRYPTION_PASSWORD, + AUTO_DRIVE_UPLOAD: process.env.AUTO_DRIVE_UPLOAD === 'true', + }, + blockchainConfig: { + RPC_URL: process.env.RPC_URL || undefined, + CONTRACT_ADDRESS: process.env.CONTRACT_ADDRESS || undefined, + PRIVATE_KEY: process.env.PRIVATE_KEY || undefined, + }, + SERPAPI_API_KEY: process.env.SERPAPI_API_KEY || '', + NODE_ENV: process.env.NODE_ENV || 'development', + RETRY_LIMIT: Number(process.env.RETRY_LIMIT) || 2, + }; -const autoDriveConfig = { - AUTO_DRIVE_API_KEY: process.env.AUTO_DRIVE_API_KEY, - AUTO_DRIVE_ENCRYPTION_PASSWORD: process.env.AUTO_DRIVE_ENCRYPTION_PASSWORD, - AUTO_DRIVE_UPLOAD: process.env.AUTO_DRIVE_UPLOAD === 'true', - // SC Configuration - RPC_URL: process.env.RPC_URL, - CONTRACT_ADDRESS: process.env.CONTRACT_ADDRESS, - PRIVATE_KEY: process.env.PRIVATE_KEY, - WALLET_ADDRESS: process.env.WALLET_ADDRESS, -}; + return configSchema.parse(rawConfig); + } catch (error) { + if (error instanceof z.ZodError) { + console.error('\x1b[31m%s\x1b[0m', formatZodError(error)); + console.info( + '\x1b[36m%s\x1b[0m', + '\nTip: Copy .env.sample to .env and fill in the required values.', + ); + process.exit(1); + } + throw error; + } +})(); -export const config = { - twitterConfig, - llmConfig, - autoDriveConfig, - - // Server Configuration - PORT: process.env.PORT || 3001, - - // Environment - NODE_ENV: process.env.NODE_ENV || 'development', - - // RESPONSE CONFIG - RETRY_LIMIT: process.env.RETRY_LIMIT || 2, -}; +export type Config = z.infer; diff --git a/auto-agents-framework/src/config/schema.ts b/auto-agents-framework/src/config/schema.ts new file mode 100644 index 00000000..f5781c9f --- /dev/null +++ b/auto-agents-framework/src/config/schema.ts @@ -0,0 +1,72 @@ +import { z } from 'zod'; + +const twitterConfigSchema = z.object({ + USERNAME: z.string().min(1, 'Twitter username is required'), + PASSWORD: z.string().min(1, 'Twitter password is required'), + COOKIES_PATH: z.string(), + NUM_TIMELINE_TWEETS: z.number().int().positive(), + NUM_FOLLOWING_RECENT_TWEETS: z.number().int().positive(), + NUM_RANDOM_FOLLOWERS: z.number().int().positive(), + MAX_MENTIONS: z.number().int().positive(), + MAX_THREAD_LENGTH: z.number().int().positive(), + MAX_MY_RECENT_TWEETS: z.number().int().positive(), + POST_TWEETS: z.boolean(), + RESPONSE_INTERVAL_MS: z.number().int().positive(), + POST_INTERVAL_MS: z.number().int().positive(), +}); + +const llmConfigSchema = z.object({ + LARGE_LLM_MODEL: z.string().min(1), + SMALL_LLM_MODEL: z.string().min(1), + OPENAI_API_KEY: z.string().min(1, 'OpenAI API key is required'), +}); + +const autoDriveConfigSchema = z.object({ + AUTO_DRIVE_API_KEY: z.string().optional(), + AUTO_DRIVE_ENCRYPTION_PASSWORD: z.string().optional(), + AUTO_DRIVE_UPLOAD: z.boolean(), +}); + +const blockchainConfigSchema = z.object({ + RPC_URL: z.string().optional(), + CONTRACT_ADDRESS: z.union([ + z + .string() + .regex( + /^0x[0-9a-fA-F]{40}$/, + "Contract address must be a 42-character string: '0x' prefix followed by 40 hex characters", + ) + .refine( + val => val.length === 42, + 'Contract address must be exactly 42 characters (0x + 40 hex characters)', + ), + z.literal(''), + z.literal(undefined), + ]), + PRIVATE_KEY: z.union([ + z + .string() + .regex( + /^0x[0-9a-fA-F]{64}$/, + "Private key must be a 66-character string: '0x' prefix followed by 64 hex characters", + ) + .refine( + val => val.length === 66, + 'Private key must be exactly 66 characters (0x + 64 hex characters)', + ), + z.literal(''), + z.literal(undefined), + ]), +}); + +const SERPAPI_API_KEY = z.string().optional(); + +export const configSchema = z.object({ + twitterConfig: twitterConfigSchema, + llmConfig: llmConfigSchema, + autoDriveConfig: autoDriveConfigSchema, + blockchainConfig: blockchainConfigSchema, + SERPAPI_API_KEY: SERPAPI_API_KEY, + NODE_ENV: z.enum(['development', 'production', 'test']), + RETRY_LIMIT: z.number().int().nonnegative(), +});