Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow passing secrets through environment #1454

Merged
merged 12 commits into from
Dec 26, 2024
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ packages/core/src/providers/cache
packages/core/src/providers/cache/*
cache/*
packages/plugin-coinbase/src/plugins/transactions.csv
packages/plugin-coinbase/package-lock.json

tsup.config.bundled_*.mjs

Expand Down
19 changes: 19 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,25 @@ export async function loadCharacters(
const character = JSON.parse(content);
validateCharacterConfig(character);

// .id isn't really valid
const characterId = character.id || character.name;
const characterPrefix = `CHARACTER.${characterId.toUpperCase().replace(/ /g, '_')}.`;

const characterSettings = Object.entries(process.env)
.filter(([key]) => key.startsWith(characterPrefix))
.reduce((settings, [key, value]) => {
const settingKey = key.slice(characterPrefix.length);
return { ...settings, [settingKey]: value };
}, {});

if (Object.keys(characterSettings).length > 0) {
character.settings = character.settings || {};
character.settings.secrets = {
...characterSettings,
...character.settings.secrets
};
}

// Handle plugins
if (isAllStrings(character.plugins)) {
elizaLogger.info("Plugins are: ", character.plugins);
Expand Down
9 changes: 6 additions & 3 deletions docs/docs/guides/secrets-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ A comprehensive guide for managing secrets, API keys, and sensitive configuratio

Eliza uses a hierarchical environment variable system:

1. Character-specific secrets (highest priority)
2. Environment variables
3. Default values (lowest priority)
1. Character-specific namespaced environment variables (highest priority)
2. Character-specific secrets
3. Environment variables
4. Default values (lowest priority)

### Secret Types

Expand Down Expand Up @@ -96,6 +97,8 @@ Define secrets in character files:
}
```

Alternatively, you can use the `CHARACTER.YOUR_CHARACTER_NAME.SECRET_NAME` format inside your `.env` file.

Access secrets in code:

```typescript
Expand Down
14 changes: 14 additions & 0 deletions docs/docs/packages/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,15 @@ export async function initializeClients(

### Token Management

Tokens can be configured in two ways:

1. Using namespaced environment variables:
```env
CHARACTER.YOUR_CHARACTER_NAME.OPENAI_API_KEY=sk-...
CHARACTER.YOUR_CHARACTER_NAME.ANTHROPIC_API_KEY=sk-...
```

2. Using character settings:
```typescript
export function getTokenForProvider(
provider: ModelProviderName,
Expand All @@ -181,6 +190,11 @@ export function getTokenForProvider(
}
```

The system will check for tokens in the following order:
1. Character-specific namespaced env variables
2. Character settings from JSON
3. Global environment variables

### Database Selection

```typescript
Expand Down
31 changes: 31 additions & 0 deletions packages/core/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ interface Settings {
[key: string]: string | undefined;
}

interface NamespacedSettings {
[namespace: string]: Settings;
}

let environmentSettings: Settings = {};

/**
Expand Down Expand Up @@ -91,6 +95,15 @@ export function loadEnvConfig(): Settings {
if (!result.error) {
console.log(`Loaded .env file from: ${envPath}`);
}

// Parse namespaced settings
const namespacedSettings = parseNamespacedSettings(process.env as Settings);

// Attach to process.env for backward compatibility
Object.entries(namespacedSettings).forEach(([namespace, settings]) => {
process.env[`__namespaced_${namespace}`] = JSON.stringify(settings);
});

return process.env as Settings;
}

Expand Down Expand Up @@ -135,3 +148,21 @@ elizaLogger.info("Parsed settings:", {
});

export default settings;

// Add this function to parse namespaced settings
function parseNamespacedSettings(env: Settings): NamespacedSettings {
const namespaced: NamespacedSettings = {};

for (const [key, value] of Object.entries(env)) {
if (!value) continue;

const [namespace, ...rest] = key.split('.');
if (!namespace || rest.length === 0) continue;

const settingKey = rest.join('.');
namespaced[namespace] = namespaced[namespace] || {};
namespaced[namespace][settingKey] = value;
}

return namespaced;
}
Loading