168 lines
6.2 KiB
TypeScript
168 lines
6.2 KiB
TypeScript
import { MemoryConfig, MemoryConfigSchema } from "../types";
|
|
import { DEFAULT_MEMORY_CONFIG } from "./defaults";
|
|
|
|
export class ConfigManager {
|
|
static mergeConfig(userConfig: Partial<MemoryConfig> = {}): MemoryConfig {
|
|
const mergedConfig = {
|
|
version: userConfig.version || DEFAULT_MEMORY_CONFIG.version,
|
|
embedder: {
|
|
provider:
|
|
userConfig.embedder?.provider ||
|
|
DEFAULT_MEMORY_CONFIG.embedder.provider,
|
|
config: (() => {
|
|
const defaultConf = DEFAULT_MEMORY_CONFIG.embedder.config;
|
|
const userConf = userConfig.embedder?.config;
|
|
let finalModel: string | any = defaultConf.model;
|
|
|
|
if (userConf?.model && typeof userConf.model === "object") {
|
|
finalModel = userConf.model;
|
|
} else if (userConf?.model && typeof userConf.model === "string") {
|
|
finalModel = userConf.model;
|
|
}
|
|
|
|
// Normalize snake_case keys from Python SDK / OpenClaw configs
|
|
const baseURL =
|
|
userConf?.baseURL ??
|
|
((userConf as Record<string, unknown>)?.lmstudio_base_url as
|
|
| string
|
|
| undefined) ??
|
|
userConf?.url;
|
|
const embeddingDims =
|
|
userConf?.embeddingDims ??
|
|
((userConf as Record<string, unknown>)?.embedding_dims as
|
|
| number
|
|
| undefined);
|
|
|
|
return {
|
|
apiKey:
|
|
userConf?.apiKey !== undefined
|
|
? userConf.apiKey
|
|
: defaultConf.apiKey,
|
|
model: finalModel,
|
|
baseURL,
|
|
url: userConf?.url,
|
|
embeddingDims,
|
|
modelProperties:
|
|
userConf?.modelProperties !== undefined
|
|
? userConf.modelProperties
|
|
: defaultConf.modelProperties,
|
|
};
|
|
})(),
|
|
},
|
|
vectorStore: {
|
|
provider:
|
|
userConfig.vectorStore?.provider ||
|
|
DEFAULT_MEMORY_CONFIG.vectorStore.provider,
|
|
config: (() => {
|
|
const defaultConf = DEFAULT_MEMORY_CONFIG.vectorStore.config;
|
|
const userConf = userConfig.vectorStore?.config;
|
|
|
|
// Resolve the vector store dimension. If the user explicitly
|
|
// provided one, use it. Otherwise leave it undefined so that
|
|
// Memory._autoInitialize() can auto-detect it by running a
|
|
// probe embedding at startup — this makes *any* embedder work
|
|
// out of the box without the user needing to know or set the
|
|
// dimension manually.
|
|
const explicitDimension =
|
|
userConf?.dimension ||
|
|
userConfig.embedder?.config?.embeddingDims ||
|
|
undefined;
|
|
|
|
// Prioritize user-provided client instance
|
|
if (userConf?.client && typeof userConf.client === "object") {
|
|
return {
|
|
client: userConf.client,
|
|
collectionName: userConf.collectionName,
|
|
dimension: explicitDimension,
|
|
...userConf, // Include any other passthrough fields from user
|
|
};
|
|
} else {
|
|
// If no client provided, merge standard fields
|
|
return {
|
|
collectionName:
|
|
userConf?.collectionName || defaultConf.collectionName,
|
|
dimension: explicitDimension,
|
|
// Ensure client is not carried over from defaults if not provided by user
|
|
client: undefined,
|
|
// Include other passthrough fields from userConf even if no client
|
|
...userConf,
|
|
};
|
|
}
|
|
})(),
|
|
},
|
|
llm: {
|
|
provider:
|
|
userConfig.llm?.provider || DEFAULT_MEMORY_CONFIG.llm.provider,
|
|
config: (() => {
|
|
const defaultConf = DEFAULT_MEMORY_CONFIG.llm.config;
|
|
const userConf = userConfig.llm?.config;
|
|
let finalModel: string | any = defaultConf.model;
|
|
|
|
if (userConf?.model && typeof userConf.model === "object") {
|
|
finalModel = userConf.model;
|
|
} else if (userConf?.model && typeof userConf.model === "string") {
|
|
finalModel = userConf.model;
|
|
}
|
|
|
|
// Normalize snake_case keys from Python SDK / OpenClaw configs
|
|
const llmBaseURL =
|
|
userConf?.baseURL ??
|
|
((userConf as Record<string, unknown>)?.lmstudio_base_url as
|
|
| string
|
|
| undefined) ??
|
|
defaultConf.baseURL;
|
|
|
|
return {
|
|
baseURL: llmBaseURL,
|
|
url: userConf?.url,
|
|
apiKey:
|
|
userConf?.apiKey !== undefined
|
|
? userConf.apiKey
|
|
: defaultConf.apiKey,
|
|
model: finalModel,
|
|
modelProperties:
|
|
userConf?.modelProperties !== undefined
|
|
? userConf.modelProperties
|
|
: defaultConf.modelProperties,
|
|
};
|
|
})(),
|
|
},
|
|
historyDbPath:
|
|
userConfig.historyDbPath ||
|
|
userConfig.historyStore?.config?.historyDbPath ||
|
|
DEFAULT_MEMORY_CONFIG.historyStore?.config?.historyDbPath,
|
|
customPrompt: userConfig.customPrompt,
|
|
graphStore: {
|
|
...DEFAULT_MEMORY_CONFIG.graphStore,
|
|
...userConfig.graphStore,
|
|
},
|
|
historyStore: (() => {
|
|
const defaultHistoryStore = DEFAULT_MEMORY_CONFIG.historyStore!;
|
|
const historyProvider =
|
|
userConfig.historyStore?.provider || defaultHistoryStore.provider;
|
|
const isSqlite = historyProvider.toLowerCase() === "sqlite";
|
|
|
|
// Precedence: explicit historyStore.config > top-level historyDbPath > default
|
|
return {
|
|
...defaultHistoryStore,
|
|
...userConfig.historyStore,
|
|
provider: historyProvider,
|
|
config: {
|
|
...(isSqlite ? defaultHistoryStore.config : {}),
|
|
...(isSqlite && userConfig.historyDbPath
|
|
? { historyDbPath: userConfig.historyDbPath }
|
|
: {}),
|
|
...userConfig.historyStore?.config,
|
|
},
|
|
};
|
|
})(),
|
|
disableHistory:
|
|
userConfig.disableHistory || DEFAULT_MEMORY_CONFIG.disableHistory,
|
|
enableGraph: userConfig.enableGraph || DEFAULT_MEMORY_CONFIG.enableGraph,
|
|
};
|
|
|
|
// Validate the merged config
|
|
return MemoryConfigSchema.parse(mergedConfig);
|
|
}
|
|
}
|