feat(openclaw): add llmProvider/llmModel plugin config options (#274)

Add llmProvider, llmModel, and llmApiKeyEnv to the plugin config schema.
These allow users to choose which LLM Hindsight uses directly from
openclaw.json config without needing HINDSIGHT_API_LLM_* env vars.

Priority order (highest to lowest):
1. HINDSIGHT_API_LLM_PROVIDER env var (unchanged)
2. Plugin config llmProvider/llmModel (NEW)
3. Auto-detect from provider env vars (unchanged)

Backward compatible: no config = same behavior as before.
This commit is contained in:
Dewaldt Huysamen 2026-02-02 13:40:23 +02:00 committed by GitHub
parent 44d912533c
commit 8564135b2a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 76 additions and 12 deletions

View file

@ -24,6 +24,19 @@
"type": "string",
"description": "hindsight-embed version to use (e.g. 'latest', '0.4.2', or empty for latest)",
"default": "latest"
},
"llmProvider": {
"type": "string",
"description": "LLM provider for Hindsight memory (e.g. 'openai', 'anthropic', 'gemini', 'groq', 'ollama'). Takes priority over auto-detection but not over HINDSIGHT_API_LLM_PROVIDER env var.",
"enum": ["openai", "anthropic", "gemini", "groq", "ollama"]
},
"llmModel": {
"type": "string",
"description": "LLM model to use (e.g. 'gpt-4o-mini', 'claude-3-5-haiku-20241022'). Used with llmProvider."
},
"llmApiKeyEnv": {
"type": "string",
"description": "Name of the env var holding the API key (e.g. 'MY_CUSTOM_KEY'). If not set, uses the standard env var for the chosen provider."
}
},
"additionalProperties": false
@ -44,6 +57,18 @@
"embedVersion": {
"label": "Hindsight Embed Version",
"placeholder": "latest (or pin to specific version like 0.4.2)"
},
"llmProvider": {
"label": "LLM Provider",
"placeholder": "e.g. openai, anthropic, gemini, groq"
},
"llmModel": {
"label": "LLM Model",
"placeholder": "e.g. gpt-4o-mini, claude-3-5-haiku-20241022"
},
"llmApiKeyEnv": {
"label": "API Key Env Var",
"placeholder": "e.g. MY_CUSTOM_API_KEY (optional)"
}
}
}

View file

@ -1,12 +1,12 @@
{
"name": "@vectorize-io/hindsight-openclaw",
"version": "0.4.5",
"version": "0.4.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@vectorize-io/hindsight-openclaw",
"version": "0.4.5",
"version": "0.4.7",
"license": "MIT",
"dependencies": {
"node-fetch": "^3.3.2"

View file

@ -37,7 +37,7 @@ const PROVIDER_DETECTION = [
{ name: 'ollama', keyEnv: '', defaultModel: 'llama3.2' },
];
function detectLLMConfig(): {
function detectLLMConfig(pluginConfig?: PluginConfig): {
provider: string;
apiKey: string;
model?: string;
@ -50,7 +50,7 @@ function detectLLMConfig(): {
const overrideKey = process.env.HINDSIGHT_API_LLM_API_KEY;
const overrideBaseUrl = process.env.HINDSIGHT_API_LLM_BASE_URL;
// If provider is explicitly set, use that (with overrides)
// Priority 1: If provider is explicitly set via env var, use that
if (overrideProvider) {
if (!overrideKey && overrideProvider !== 'ollama') {
throw new Error(
@ -69,7 +69,37 @@ function detectLLMConfig(): {
};
}
// Auto-detect from standard provider env vars
// Priority 2: Plugin config llmProvider/llmModel
if (pluginConfig?.llmProvider) {
const providerInfo = PROVIDER_DETECTION.find(p => p.name === pluginConfig.llmProvider);
// Resolve API key: llmApiKeyEnv > provider's standard keyEnv
let apiKey = '';
if (pluginConfig.llmApiKeyEnv) {
apiKey = process.env[pluginConfig.llmApiKeyEnv] || '';
} else if (providerInfo?.keyEnv) {
apiKey = process.env[providerInfo.keyEnv] || '';
}
if (!apiKey && pluginConfig.llmProvider !== 'ollama') {
const keySource = pluginConfig.llmApiKeyEnv || providerInfo?.keyEnv || 'unknown';
throw new Error(
`Plugin config llmProvider is set to "${pluginConfig.llmProvider}" but no API key found.\n` +
`Expected env var: ${keySource}\n` +
`Set the env var or use llmApiKeyEnv in plugin config to specify a custom env var name.`
);
}
return {
provider: pluginConfig.llmProvider,
apiKey,
model: pluginConfig.llmModel || overrideModel || providerInfo?.defaultModel,
baseUrl: overrideBaseUrl,
source: 'plugin config',
};
}
// Priority 3: Auto-detect from standard provider env vars
for (const providerInfo of PROVIDER_DETECTION) {
const apiKey = providerInfo.keyEnv ? process.env[providerInfo.keyEnv] : '';
@ -97,7 +127,9 @@ function detectLLMConfig(): {
` export ANTHROPIC_API_KEY=your-key # Uses claude-3-5-haiku\n` +
` export GEMINI_API_KEY=your-key # Uses gemini-2.0-flash-exp\n` +
` export GROQ_API_KEY=your-key # Uses llama-3.3-70b-versatile\n\n` +
`Option 2: Override with Hindsight-specific config:\n` +
`Option 2: Set llmProvider in openclaw.json plugin config:\n` +
` "llmProvider": "openai", "llmModel": "gpt-4o-mini"\n\n` +
`Option 3: Override with Hindsight-specific env vars:\n` +
` export HINDSIGHT_API_LLM_PROVIDER=openai\n` +
` export HINDSIGHT_API_LLM_MODEL=gpt-4o-mini\n` +
` export HINDSIGHT_API_LLM_API_KEY=sk-your-key\n` +
@ -115,6 +147,9 @@ function getPluginConfig(api: MoltbotPluginAPI): PluginConfig {
embedPort: config.embedPort || 0,
daemonIdleTimeout: config.daemonIdleTimeout !== undefined ? config.daemonIdleTimeout : 0,
embedVersion: config.embedVersion || 'latest',
llmProvider: config.llmProvider,
llmModel: config.llmModel,
llmApiKeyEnv: config.llmApiKeyEnv,
};
}
@ -122,9 +157,13 @@ export default function (api: MoltbotPluginAPI) {
try {
console.log('[Hindsight] Plugin loading...');
// Detect LLM configuration from environment
// Get plugin config first (needed for LLM detection)
console.log('[Hindsight] Getting plugin config...');
const pluginConfig = getPluginConfig(api);
// Detect LLM configuration (env vars > plugin config > auto-detect)
console.log('[Hindsight] Detecting LLM config...');
const llmConfig = detectLLMConfig();
const llmConfig = detectLLMConfig(pluginConfig);
const baseUrlInfo = llmConfig.baseUrl ? `, base URL: ${llmConfig.baseUrl}` : '';
const modelInfo = llmConfig.model || 'default';
@ -134,9 +173,6 @@ export default function (api: MoltbotPluginAPI) {
} else {
console.log(`[Hindsight] ✓ Using provider: ${llmConfig.provider}, model: ${modelInfo} (${llmConfig.source}${baseUrlInfo})`);
}
console.log('[Hindsight] Getting plugin config...');
const pluginConfig = getPluginConfig(api);
if (pluginConfig.bankMission) {
console.log(`[Hindsight] Custom bank mission configured: "${pluginConfig.bankMission.substring(0, 50)}..."`);
}
@ -225,8 +261,8 @@ export default function (api: MoltbotPluginAPI) {
// Reinitialize if needed (fresh start or recovery from dead daemon)
if (!isInitialized) {
console.log('[Hindsight] Reinitializing daemon...');
const llmConfig = detectLLMConfig();
const pluginConfig = getPluginConfig(api);
const llmConfig = detectLLMConfig(pluginConfig);
const port = pluginConfig.embedPort || Math.floor(Math.random() * 10000) + 10000;
embedManager = new HindsightEmbedManager(

View file

@ -32,6 +32,9 @@ export interface PluginConfig {
embedPort?: number;
daemonIdleTimeout?: number; // Seconds before daemon shuts down (0 = never)
embedVersion?: string; // hindsight-embed version (default: "latest")
llmProvider?: string; // LLM provider override (e.g. 'openai', 'anthropic', 'gemini', 'groq', 'ollama')
llmModel?: string; // LLM model override (e.g. 'gpt-4o-mini', 'claude-3-5-haiku-20241022')
llmApiKeyEnv?: string; // Env var name holding the API key (e.g. 'MY_CUSTOM_KEY')
}
export interface ServiceConfig {