@mnemom/mnemom 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,474 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import * as os from "node:os";
4
- import { getGatewayUrl } from "./config.js";
5
- // OpenClaw paths
6
- export const OPENCLAW_DIR = path.join(os.homedir(), ".openclaw");
7
- export const OPENCLAW_CONFIG_FILE = path.join(OPENCLAW_DIR, "openclaw.json");
8
- export const AUTH_PROFILES_FILE = path.join(OPENCLAW_DIR, "agents", "main", "agent", "auth-profiles.json");
9
- function buildProviderRoutes() {
10
- const gw = getGatewayUrl();
11
- return {
12
- anthropic: { baseUrl: `${gw}/anthropic`, apiType: "anthropic-messages" },
13
- openai: { baseUrl: `${gw}/openai`, apiType: "openai-chat" },
14
- gemini: { baseUrl: `${gw}/gemini`, apiType: "gemini-messages" },
15
- };
16
- }
17
- export const PROVIDER_ROUTES = buildProviderRoutes();
18
- /**
19
- * Mnemom provider key names in OpenClaw config (canonical, new installs).
20
- * mnemom -> Anthropic
21
- * mnemom-openai -> OpenAI
22
- * mnemom-gemini -> Gemini
23
- */
24
- export const PROVIDER_CONFIG_KEYS = {
25
- anthropic: "mnemom",
26
- openai: "mnemom-openai",
27
- gemini: "mnemom-gemini",
28
- };
29
- /**
30
- * Legacy smoltbot provider key names — used for backward-compat detection
31
- * of existing configurations and migration source keys.
32
- */
33
- export const LEGACY_PROVIDER_CONFIG_KEYS = {
34
- anthropic: "smoltbot",
35
- openai: "smoltbot-openai",
36
- gemini: "smoltbot-gemini",
37
- };
38
- // ============================================================================
39
- // API Key Configuration per Provider
40
- // ============================================================================
41
- const PROVIDER_KEY_CONFIG = {
42
- anthropic: {
43
- profileKey: "anthropic:default",
44
- profileProvider: "anthropic",
45
- validate: (key) => key.startsWith("sk-ant-"),
46
- },
47
- openai: {
48
- profileKey: "openai:default",
49
- profileProvider: "openai",
50
- validate: (key) => key.startsWith("sk-") && !key.startsWith("sk-ant-"),
51
- },
52
- gemini: {
53
- profileKey: "google:default",
54
- profileProvider: "google",
55
- validate: (key) => key.startsWith("AIza"),
56
- },
57
- };
58
- // ============================================================================
59
- // Core Functions
60
- // ============================================================================
61
- /**
62
- * Check if OpenClaw is installed
63
- */
64
- export function openclawExists() {
65
- return fs.existsSync(OPENCLAW_DIR) && fs.existsSync(OPENCLAW_CONFIG_FILE);
66
- }
67
- /**
68
- * Load and parse auth-profiles.json
69
- */
70
- export function loadAuthProfiles() {
71
- if (!fs.existsSync(AUTH_PROFILES_FILE)) {
72
- return null;
73
- }
74
- try {
75
- const content = fs.readFileSync(AUTH_PROFILES_FILE, "utf-8");
76
- return JSON.parse(content);
77
- }
78
- catch {
79
- return null;
80
- }
81
- }
82
- /**
83
- * Get API key for a specific provider from auth-profiles.json.
84
- */
85
- export function getProviderApiKey(provider) {
86
- const profiles = loadAuthProfiles();
87
- if (!profiles) {
88
- return { key: null, isOAuth: false };
89
- }
90
- const config = PROVIDER_KEY_CONFIG[provider];
91
- // Look for provider:default or any matching provider profile
92
- const profile = profiles.profiles[config.profileKey] ||
93
- Object.values(profiles.profiles).find((p) => p.provider === config.profileProvider);
94
- if (!profile) {
95
- return { key: null, isOAuth: false };
96
- }
97
- if (profile.type === "oauth" || !profile.key) {
98
- return { key: null, isOAuth: true };
99
- }
100
- // Validate key format
101
- if (!config.validate(profile.key)) {
102
- return { key: null, isOAuth: false, invalidFormat: true };
103
- }
104
- return { key: profile.key, isOAuth: false };
105
- }
106
- /**
107
- * Get the Anthropic API key from auth-profiles.json.
108
- * Backward-compatible wrapper around getProviderApiKey().
109
- */
110
- export function getAnthropicApiKey() {
111
- return getProviderApiKey("anthropic");
112
- }
113
- /**
114
- * Load openclaw.json config
115
- */
116
- export function loadOpenClawConfig() {
117
- if (!fs.existsSync(OPENCLAW_CONFIG_FILE)) {
118
- return null;
119
- }
120
- try {
121
- const content = fs.readFileSync(OPENCLAW_CONFIG_FILE, "utf-8");
122
- return JSON.parse(content);
123
- }
124
- catch {
125
- return null;
126
- }
127
- }
128
- /**
129
- * Save openclaw.json config (preserves all existing fields)
130
- */
131
- export function saveOpenClawConfig(config) {
132
- // Update meta timestamp
133
- if (!config.meta) {
134
- config.meta = {};
135
- }
136
- config.meta.lastTouchedAt = new Date().toISOString();
137
- fs.writeFileSync(OPENCLAW_CONFIG_FILE, JSON.stringify(config, null, 2));
138
- }
139
- /**
140
- * Get the current default model from OpenClaw config
141
- * Returns both the full model path (provider/model) and parsed parts
142
- */
143
- export function getCurrentModel() {
144
- const config = loadOpenClawConfig();
145
- if (!config) {
146
- return { fullPath: null, provider: null, modelId: null };
147
- }
148
- const primary = config.agents?.defaults?.model?.primary;
149
- if (!primary) {
150
- return { fullPath: null, provider: null, modelId: null };
151
- }
152
- // Parse provider/model format (e.g., "anthropic/claude-opus-4-5-20251101")
153
- const parts = primary.split("/");
154
- if (parts.length === 2) {
155
- return {
156
- fullPath: primary,
157
- provider: parts[0],
158
- modelId: parts[1],
159
- };
160
- }
161
- // No provider prefix, assume it's just the model ID
162
- return {
163
- fullPath: primary,
164
- provider: null,
165
- modelId: primary,
166
- };
167
- }
168
- /**
169
- * Check if smoltbot/mnemom provider is already configured (any provider).
170
- * Returns true if either mnemom* OR smoltbot* provider keys exist in the OpenClaw config.
171
- */
172
- export function isSmoltbotConfigured() {
173
- const config = loadOpenClawConfig();
174
- if (!config?.models?.providers)
175
- return false;
176
- const allKeys = [
177
- ...Object.values(PROVIDER_CONFIG_KEYS),
178
- ...Object.values(LEGACY_PROVIDER_CONFIG_KEYS),
179
- ];
180
- return allKeys.some((key) => !!config.models?.providers?.[key]);
181
- }
182
- /**
183
- * Get list of smoltbot/mnemom-configured providers.
184
- * Checks both new mnemom* keys and legacy smoltbot* keys.
185
- */
186
- export function getSmoltbotConfiguredProviders() {
187
- const config = loadOpenClawConfig();
188
- if (!config?.models?.providers)
189
- return [];
190
- const configured = new Set();
191
- for (const [provider, key] of Object.entries(PROVIDER_CONFIG_KEYS)) {
192
- if (config.models.providers[key]) {
193
- configured.add(provider);
194
- }
195
- }
196
- for (const [provider, key] of Object.entries(LEGACY_PROVIDER_CONFIG_KEYS)) {
197
- if (config.models.providers[key]) {
198
- configured.add(provider);
199
- }
200
- }
201
- return Array.from(configured);
202
- }
203
- /**
204
- * Get the existing mnemom/smoltbot provider config (Anthropic provider).
205
- * Checks mnemom key first, falls back to legacy smoltbot key.
206
- */
207
- export function getSmoltbotProvider() {
208
- const config = loadOpenClawConfig();
209
- return (config?.models?.providers?.mnemom ||
210
- config?.models?.providers?.smoltbot ||
211
- null);
212
- }
213
- /**
214
- * Comprehensive detection of OpenClaw setup (backward compatible)
215
- */
216
- export function detectOpenClaw() {
217
- // Check if OpenClaw is installed
218
- if (!openclawExists()) {
219
- return {
220
- installed: false,
221
- hasApiKey: false,
222
- isOAuth: false,
223
- smoltbotAlreadyConfigured: false,
224
- error: "OpenClaw is not installed. Install from https://openclaw.ai",
225
- };
226
- }
227
- // Check auth profile
228
- const { key, isOAuth, invalidFormat } = getAnthropicApiKey();
229
- if (invalidFormat) {
230
- return {
231
- installed: true,
232
- hasApiKey: false,
233
- isOAuth: false,
234
- smoltbotAlreadyConfigured: isSmoltbotConfigured(),
235
- error: "Invalid API key format. Anthropic API keys start with 'sk-ant-'.\n" +
236
- "Get a valid key from https://console.anthropic.com/settings/keys",
237
- };
238
- }
239
- if (isOAuth) {
240
- return {
241
- installed: true,
242
- hasApiKey: false,
243
- isOAuth: true,
244
- smoltbotAlreadyConfigured: isSmoltbotConfigured(),
245
- error: "OAuth authentication detected. smoltbot only supports API key authentication.\n" +
246
- "To use smoltbot, add an API key to your Anthropic auth profile.",
247
- };
248
- }
249
- if (!key) {
250
- return {
251
- installed: true,
252
- hasApiKey: false,
253
- isOAuth: false,
254
- smoltbotAlreadyConfigured: isSmoltbotConfigured(),
255
- error: "No Anthropic API key found in auth-profiles.json.\n" +
256
- "Run `openclaw auth` to configure your API key.",
257
- };
258
- }
259
- // Get current model
260
- const { fullPath, provider, modelId } = getCurrentModel();
261
- return {
262
- installed: true,
263
- hasApiKey: true,
264
- isOAuth: false,
265
- apiKey: key,
266
- currentModel: fullPath || undefined,
267
- currentModelId: modelId || undefined,
268
- currentProvider: provider || undefined,
269
- smoltbotAlreadyConfigured: isSmoltbotConfigured(),
270
- };
271
- }
272
- /**
273
- * Detect all available providers.
274
- * Checks for API keys across Anthropic, OpenAI, and Gemini.
275
- */
276
- export function detectProviders() {
277
- if (!openclawExists()) {
278
- return {
279
- installed: false,
280
- providers: {
281
- anthropic: { hasApiKey: false },
282
- openai: { hasApiKey: false },
283
- gemini: { hasApiKey: false },
284
- },
285
- smoltbotConfiguredProviders: [],
286
- error: "OpenClaw is not installed. Install from https://openclaw.ai",
287
- };
288
- }
289
- const providers = {
290
- anthropic: { hasApiKey: false },
291
- openai: { hasApiKey: false },
292
- gemini: { hasApiKey: false },
293
- };
294
- for (const provider of ["anthropic", "openai", "gemini"]) {
295
- const result = getProviderApiKey(provider);
296
- providers[provider] = {
297
- hasApiKey: !!result.key,
298
- apiKey: result.key || undefined,
299
- isOAuth: result.isOAuth || undefined,
300
- invalidFormat: result.invalidFormat || undefined,
301
- };
302
- }
303
- const { fullPath, provider: currentProvider, modelId } = getCurrentModel();
304
- return {
305
- installed: true,
306
- providers,
307
- currentModel: fullPath || undefined,
308
- currentModelId: modelId || undefined,
309
- currentProvider: currentProvider || undefined,
310
- smoltbotConfiguredProviders: getSmoltbotConfiguredProviders(),
311
- };
312
- }
313
- /**
314
- * Configure the smoltbot provider in OpenClaw config.
315
- * Backward-compatible — configures the Anthropic ("smoltbot") provider.
316
- */
317
- export function configureSmoltbotProvider(apiKey, models) {
318
- configureSmoltbotProviderForType("anthropic", apiKey, models);
319
- }
320
- /**
321
- * Configure a smoltbot provider for a specific provider type.
322
- */
323
- export function configureSmoltbotProviderForType(provider, apiKey, models) {
324
- const config = loadOpenClawConfig();
325
- if (!config) {
326
- throw new Error("Could not load OpenClaw config");
327
- }
328
- // Ensure models section exists
329
- if (!config.models) {
330
- config.models = {};
331
- }
332
- if (!config.models.providers) {
333
- config.models.providers = {};
334
- }
335
- // Set mode to merge if not set
336
- if (!config.models.mode) {
337
- config.models.mode = "merge";
338
- }
339
- const route = PROVIDER_ROUTES[provider];
340
- const configKey = PROVIDER_CONFIG_KEYS[provider];
341
- config.models.providers[configKey] = {
342
- baseUrl: route.baseUrl,
343
- apiKey: apiKey,
344
- api: route.apiType,
345
- models: models,
346
- };
347
- saveOpenClawConfig(config);
348
- }
349
- /**
350
- * Configure all available providers at once.
351
- * Returns the list of providers that were configured.
352
- */
353
- export function configureSmoltbotProviders(providerKeys) {
354
- const config = loadOpenClawConfig();
355
- if (!config) {
356
- throw new Error("Could not load OpenClaw config");
357
- }
358
- // Ensure models section exists
359
- if (!config.models) {
360
- config.models = {};
361
- }
362
- if (!config.models.providers) {
363
- config.models.providers = {};
364
- }
365
- if (!config.models.mode) {
366
- config.models.mode = "merge";
367
- }
368
- const configured = [];
369
- for (const [provider, data] of Object.entries(providerKeys)) {
370
- if (!data)
371
- continue;
372
- const route = PROVIDER_ROUTES[provider];
373
- const configKey = PROVIDER_CONFIG_KEYS[provider];
374
- config.models.providers[configKey] = {
375
- baseUrl: route.baseUrl,
376
- apiKey: data.apiKey,
377
- api: route.apiType,
378
- models: data.models,
379
- };
380
- configured.push(provider);
381
- }
382
- saveOpenClawConfig(config);
383
- return configured;
384
- }
385
- // ============================================================================
386
- // Named Agent Provider Functions
387
- // ============================================================================
388
- /**
389
- * Get provider routes for a named agent.
390
- * Named agents use the same base URLs as default, but add x-mnemom-agent header.
391
- */
392
- export function getProviderRoutes(agentName) {
393
- // Same URLs for all agents — agent identity is in the x-mnemom-agent header
394
- return PROVIDER_ROUTES;
395
- }
396
- /**
397
- * Get provider config keys for a named agent.
398
- * Default agent uses mnemom* keys (PROVIDER_CONFIG_KEYS).
399
- * Named agents use: mnemom-{name}, mnemom-{name}-openai, mnemom-{name}-gemini.
400
- */
401
- export function getProviderConfigKeys(agentName) {
402
- if (!agentName) {
403
- return PROVIDER_CONFIG_KEYS;
404
- }
405
- return {
406
- anthropic: `mnemom-${agentName}`,
407
- openai: `mnemom-${agentName}-openai`,
408
- gemini: `mnemom-${agentName}-gemini`,
409
- };
410
- }
411
- /**
412
- * Configure OpenClaw providers for a named agent.
413
- * Each named agent gets its own provider entries with URL-prefixed base URLs.
414
- */
415
- export function configureNamedAgentProviders(agentName, providerKeys) {
416
- const config = loadOpenClawConfig();
417
- if (!config) {
418
- throw new Error("Could not load OpenClaw config");
419
- }
420
- if (!config.models) {
421
- config.models = {};
422
- }
423
- if (!config.models.providers) {
424
- config.models.providers = {};
425
- }
426
- if (!config.models.mode) {
427
- config.models.mode = "merge";
428
- }
429
- const routes = getProviderRoutes(agentName);
430
- const keys = getProviderConfigKeys(agentName);
431
- const configured = [];
432
- for (const [provider, data] of Object.entries(providerKeys)) {
433
- if (!data)
434
- continue;
435
- const route = routes[provider];
436
- const configKey = keys[provider];
437
- config.models.providers[configKey] = {
438
- baseUrl: route.baseUrl,
439
- apiKey: data.apiKey,
440
- api: route.apiType,
441
- defaultHeaders: { "x-mnemom-agent": agentName },
442
- models: data.models,
443
- };
444
- configured.push(provider);
445
- }
446
- saveOpenClawConfig(config);
447
- return configured;
448
- }
449
- /**
450
- * Set the default model in OpenClaw config
451
- */
452
- export function setDefaultModel(modelPath) {
453
- const config = loadOpenClawConfig();
454
- if (!config) {
455
- throw new Error("Could not load OpenClaw config");
456
- }
457
- // Ensure agents.defaults.model section exists
458
- if (!config.agents) {
459
- config.agents = {};
460
- }
461
- if (!config.agents.defaults) {
462
- config.agents.defaults = {};
463
- }
464
- if (!config.agents.defaults.model) {
465
- config.agents.defaults.model = {};
466
- }
467
- // Also add to models map if not present
468
- if (!config.agents.defaults.models) {
469
- config.agents.defaults.models = {};
470
- }
471
- config.agents.defaults.model.primary = modelPath;
472
- config.agents.defaults.models[modelPath] = {};
473
- saveOpenClawConfig(config);
474
- }