mrmd-server 0.1.19 → 0.1.20
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.
- package/package.json +1 -1
- package/src/ai-service.js +62 -2
- package/src/api/system.js +18 -1
package/package.json
CHANGED
package/src/ai-service.js
CHANGED
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The AI server is shared across all sessions (stateless).
|
|
5
5
|
* It's started once on first request and kept running.
|
|
6
|
+
*
|
|
7
|
+
* API keys are passed as environment variables to the AI server process
|
|
8
|
+
* because dspy/litellm reads them from env vars.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
import { spawn, execSync } from 'child_process';
|
|
9
12
|
import net from 'net';
|
|
10
13
|
import path from 'path';
|
|
11
|
-
import { existsSync } from 'fs';
|
|
14
|
+
import { existsSync, readFileSync } from 'fs';
|
|
12
15
|
import os from 'os';
|
|
13
16
|
|
|
14
17
|
// AI server singleton
|
|
@@ -75,11 +78,54 @@ function findUv() {
|
|
|
75
78
|
return null;
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Load API keys from settings file
|
|
83
|
+
*/
|
|
84
|
+
function loadApiKeysFromSettings() {
|
|
85
|
+
const settingsPath = path.join(os.homedir(), '.mrmd', 'settings.json');
|
|
86
|
+
try {
|
|
87
|
+
if (existsSync(settingsPath)) {
|
|
88
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
89
|
+
return settings.apiKeys || {};
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {
|
|
92
|
+
console.warn('[ai] Failed to load API keys from settings:', e.message);
|
|
93
|
+
}
|
|
94
|
+
return {};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Build environment variables for AI server from API keys
|
|
99
|
+
*/
|
|
100
|
+
function buildEnvFromApiKeys(apiKeys) {
|
|
101
|
+
const env = { ...process.env };
|
|
102
|
+
|
|
103
|
+
// Map settings keys to litellm environment variable names
|
|
104
|
+
const envMapping = {
|
|
105
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
106
|
+
openai: 'OPENAI_API_KEY',
|
|
107
|
+
groq: 'GROQ_API_KEY',
|
|
108
|
+
gemini: 'GEMINI_API_KEY',
|
|
109
|
+
openrouter: 'OPENROUTER_API_KEY',
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
for (const [provider, envVar] of Object.entries(envMapping)) {
|
|
113
|
+
if (apiKeys[provider]) {
|
|
114
|
+
env[envVar] = apiKeys[provider];
|
|
115
|
+
console.log(`[ai] Setting ${envVar} from settings`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return env;
|
|
120
|
+
}
|
|
121
|
+
|
|
78
122
|
/**
|
|
79
123
|
* Ensure AI server is running
|
|
124
|
+
* @param {Object} options
|
|
125
|
+
* @param {Object} [options.apiKeys] - API keys to pass as env vars (optional, will load from settings if not provided)
|
|
80
126
|
* Returns { port, url, success } or { error, success: false }
|
|
81
127
|
*/
|
|
82
|
-
export async function ensureAiServer() {
|
|
128
|
+
export async function ensureAiServer(options = {}) {
|
|
83
129
|
// Already running
|
|
84
130
|
if (aiServer) {
|
|
85
131
|
return {
|
|
@@ -103,6 +149,10 @@ export async function ensureAiServer() {
|
|
|
103
149
|
};
|
|
104
150
|
}
|
|
105
151
|
|
|
152
|
+
// Load API keys - from options or settings file
|
|
153
|
+
const apiKeys = options.apiKeys || loadApiKeysFromSettings();
|
|
154
|
+
const env = buildEnvFromApiKeys(apiKeys);
|
|
155
|
+
|
|
106
156
|
const port = await findFreePort();
|
|
107
157
|
console.log(`[ai] Starting mrmd-ai on port ${port}...`);
|
|
108
158
|
|
|
@@ -113,6 +163,7 @@ export async function ensureAiServer() {
|
|
|
113
163
|
'--port', port.toString(),
|
|
114
164
|
], {
|
|
115
165
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
166
|
+
env, // Pass API keys as environment variables
|
|
116
167
|
});
|
|
117
168
|
|
|
118
169
|
proc.stdout.on('data', (d) => console.log('[ai]', d.toString().trim()));
|
|
@@ -182,3 +233,12 @@ export function stopAiServer() {
|
|
|
182
233
|
startPromise = null;
|
|
183
234
|
}
|
|
184
235
|
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Restart AI server with new API keys
|
|
239
|
+
* Call this when API keys change in settings
|
|
240
|
+
*/
|
|
241
|
+
export async function restartAiServer(apiKeys) {
|
|
242
|
+
stopAiServer();
|
|
243
|
+
return ensureAiServer({ apiKeys });
|
|
244
|
+
}
|
package/src/api/system.js
CHANGED
|
@@ -10,7 +10,7 @@ import path from 'path';
|
|
|
10
10
|
import fs from 'fs/promises';
|
|
11
11
|
import { existsSync } from 'fs';
|
|
12
12
|
import { spawn, execSync } from 'child_process';
|
|
13
|
-
import { ensureAiServer, getAiServer } from '../ai-service.js';
|
|
13
|
+
import { ensureAiServer, getAiServer, restartAiServer } from '../ai-service.js';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Create system routes
|
|
@@ -210,6 +210,23 @@ export function createSystemRoutes(ctx) {
|
|
|
210
210
|
res.json(getAiServer());
|
|
211
211
|
});
|
|
212
212
|
|
|
213
|
+
/**
|
|
214
|
+
* POST /api/system/ai/restart
|
|
215
|
+
* Restart AI server with new API keys
|
|
216
|
+
* Call this after changing API keys in settings
|
|
217
|
+
*/
|
|
218
|
+
router.post('/ai/restart', async (req, res) => {
|
|
219
|
+
try {
|
|
220
|
+
const { apiKeys } = req.body;
|
|
221
|
+
console.log('[system:ai:restart] Restarting AI server with new API keys');
|
|
222
|
+
const result = await restartAiServer(apiKeys);
|
|
223
|
+
res.json(result);
|
|
224
|
+
} catch (err) {
|
|
225
|
+
console.error('[system:ai:restart]', err);
|
|
226
|
+
res.status(500).json({ success: false, error: err.message });
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
213
230
|
/**
|
|
214
231
|
* POST /api/system/discover-venvs
|
|
215
232
|
* Discover virtual environments
|