Mona commited on
Commit Β·
5cdc985
1
Parent(s): 64bc9a3
mo: platform-aware API client, sngine integration, Docker support
Browse files- Config auto-detects platform type (sngine vs Docker) and sets correct API paths
- Cloud client uses platform-aware endpoints for verify, think, tool-result
- API client works with both sngine-based and Docker control planes
- REST API now accepts Bearer tokens (agent auth) in addition to session cookies
- Fixed agent ID regex to support hyphens in IDs
- WebSocket relay running on agent.mona.expert:4390
- All 14 tests passing
- src/api.js +56 -34
- src/cloud.js +57 -37
- src/config.js +63 -7
src/api.js
CHANGED
|
@@ -4,29 +4,36 @@
|
|
| 4 |
// - Executing tools directly
|
| 5 |
// - Testing connectivity
|
| 6 |
// - Managing agent registration
|
|
|
|
|
|
|
| 7 |
|
| 8 |
import { CLOUD, DEFAULTS } from './config.js';
|
| 9 |
import { log } from './log.js';
|
| 10 |
|
| 11 |
const UA = `mona-agent/${DEFAULTS.version}`;
|
|
|
|
| 12 |
|
| 13 |
// ββ Generic fetch with auth βββββββββββββββββββββββββββββββββββββββ
|
| 14 |
-
async function apiFetch(apiKey, path, { method = 'GET', body, signal } = {}) {
|
| 15 |
const url = CLOUD.base + path;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
const res = await fetch(url, {
|
| 17 |
method,
|
| 18 |
-
headers
|
| 19 |
-
'authorization': `Bearer ${apiKey}`,
|
| 20 |
-
'content-type': 'application/json',
|
| 21 |
-
'user-agent': UA,
|
| 22 |
-
'x-mona-agent': DEFAULTS.version,
|
| 23 |
-
},
|
| 24 |
body: body ? JSON.stringify(body) : undefined,
|
| 25 |
signal,
|
| 26 |
});
|
| 27 |
|
| 28 |
if (!res.ok) {
|
| 29 |
-
|
|
|
|
| 30 |
const err = new Error(`API ${res.status}: ${text.slice(0, 200)}`);
|
| 31 |
err.status = res.status;
|
| 32 |
throw err;
|
|
@@ -37,23 +44,29 @@ async function apiFetch(apiKey, path, { method = 'GET', body, signal } = {}) {
|
|
| 37 |
// ββ Health check ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 38 |
export async function checkHealth(apiKey) {
|
| 39 |
try {
|
| 40 |
-
const res = await apiFetch(apiKey,
|
| 41 |
const data = await res.json();
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
} catch (err) {
|
| 44 |
-
return { ok: false, error: err.message };
|
| 45 |
}
|
| 46 |
}
|
| 47 |
|
| 48 |
// ββ Verify API key ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 49 |
export async function verifyKey(apiKey) {
|
| 50 |
-
const res = await apiFetch(apiKey,
|
| 51 |
return res.json();
|
| 52 |
}
|
| 53 |
|
| 54 |
// ββ Send chat message βββββββββββββββββββββββββββββββββββββββββββββ
|
| 55 |
export async function sendChat(apiKey, agentId, message) {
|
| 56 |
-
const res = await apiFetch(apiKey,
|
| 57 |
method: 'POST',
|
| 58 |
body: { message },
|
| 59 |
});
|
|
@@ -62,7 +75,7 @@ export async function sendChat(apiKey, agentId, message) {
|
|
| 62 |
|
| 63 |
// ββ Execute tool directly via API βββββββββββββββββββββββββββββββββ
|
| 64 |
export async function execTool(apiKey, agentId, tool, args) {
|
| 65 |
-
const res = await apiFetch(apiKey,
|
| 66 |
method: 'POST',
|
| 67 |
body: { tool, args },
|
| 68 |
});
|
|
@@ -71,19 +84,22 @@ export async function execTool(apiKey, agentId, tool, args) {
|
|
| 71 |
|
| 72 |
// ββ List agents βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 73 |
export async function listAgents(apiKey) {
|
| 74 |
-
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
| 76 |
}
|
| 77 |
|
| 78 |
// ββ Get agent status ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 79 |
export async function getAgent(apiKey, agentId) {
|
| 80 |
-
const res = await apiFetch(apiKey,
|
| 81 |
return res.json();
|
| 82 |
}
|
| 83 |
|
| 84 |
// ββ Stream reasoning from cloud (SSE) βββββββββββββββββββββββββββββ
|
| 85 |
export async function think({ apiKey, messages, tools, onChunk, onUsage, signal }) {
|
| 86 |
-
const res = await apiFetch(apiKey,
|
| 87 |
method: 'POST',
|
| 88 |
body: { messages, tools, stream: true },
|
| 89 |
});
|
|
@@ -126,27 +142,33 @@ export async function think({ apiKey, messages, tools, onChunk, onUsage, signal
|
|
| 126 |
}
|
| 127 |
|
| 128 |
// ββ Force connection test βββββββββββββββββββββββββββββββββββββββββ
|
| 129 |
-
export async function testConnection(apiKey) {
|
|
|
|
|
|
|
| 130 |
const results = {};
|
| 131 |
|
| 132 |
-
// 1. Health check
|
| 133 |
-
log.info('Testing HTTP health...');
|
| 134 |
-
results.health = await checkHealth(apiKey);
|
| 135 |
-
|
| 136 |
-
// 2. Verify auth
|
| 137 |
-
log.info('Verifying API key...');
|
| 138 |
try {
|
| 139 |
-
|
| 140 |
-
} catch (err) {
|
| 141 |
-
results.auth = { error: err.message };
|
| 142 |
-
}
|
| 143 |
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
} catch (err) {
|
| 149 |
-
results.
|
| 150 |
}
|
| 151 |
|
| 152 |
return results;
|
|
|
|
| 4 |
// - Executing tools directly
|
| 5 |
// - Testing connectivity
|
| 6 |
// - Managing agent registration
|
| 7 |
+
//
|
| 8 |
+
// Supports both sngine-based (agent.mona.expert) and Docker-based platforms.
|
| 9 |
|
| 10 |
import { CLOUD, DEFAULTS } from './config.js';
|
| 11 |
import { log } from './log.js';
|
| 12 |
|
| 13 |
const UA = `mona-agent/${DEFAULTS.version}`;
|
| 14 |
+
const P = CLOUD.paths; // platform-aware API paths
|
| 15 |
|
| 16 |
// ββ Generic fetch with auth βββββββββββββββββββββββββββββββββββββββ
|
| 17 |
+
async function apiFetch(apiKey, path, { method = 'GET', body, signal, headers: extraHeaders } = {}) {
|
| 18 |
const url = CLOUD.base + path;
|
| 19 |
+
const headers = {
|
| 20 |
+
'authorization': `Bearer ${apiKey}`,
|
| 21 |
+
'content-type': 'application/json',
|
| 22 |
+
'user-agent': UA,
|
| 23 |
+
'x-mona-agent': DEFAULTS.version,
|
| 24 |
+
...(extraHeaders || {}),
|
| 25 |
+
};
|
| 26 |
+
|
| 27 |
const res = await fetch(url, {
|
| 28 |
method,
|
| 29 |
+
headers,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
body: body ? JSON.stringify(body) : undefined,
|
| 31 |
signal,
|
| 32 |
});
|
| 33 |
|
| 34 |
if (!res.ok) {
|
| 35 |
+
let text;
|
| 36 |
+
try { text = await res.text(); } catch { text = ''; }
|
| 37 |
const err = new Error(`API ${res.status}: ${text.slice(0, 200)}`);
|
| 38 |
err.status = res.status;
|
| 39 |
throw err;
|
|
|
|
| 44 |
// ββ Health check ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 45 |
export async function checkHealth(apiKey) {
|
| 46 |
try {
|
| 47 |
+
const res = await apiFetch(apiKey, P.health);
|
| 48 |
const data = await res.json();
|
| 49 |
+
// Normalize response
|
| 50 |
+
return {
|
| 51 |
+
ok: true,
|
| 52 |
+
uptime: data.uptime || data.ok ? 1 : 0,
|
| 53 |
+
platform: CLOUD.platform,
|
| 54 |
+
...data,
|
| 55 |
+
};
|
| 56 |
} catch (err) {
|
| 57 |
+
return { ok: false, error: err.message, platform: CLOUD.platform };
|
| 58 |
}
|
| 59 |
}
|
| 60 |
|
| 61 |
// ββ Verify API key ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 62 |
export async function verifyKey(apiKey) {
|
| 63 |
+
const res = await apiFetch(apiKey, P.verifyKey, { method: 'GET' });
|
| 64 |
return res.json();
|
| 65 |
}
|
| 66 |
|
| 67 |
// ββ Send chat message βββββββββββββββββββββββββββββββββββββββββββββ
|
| 68 |
export async function sendChat(apiKey, agentId, message) {
|
| 69 |
+
const res = await apiFetch(apiKey, P.chat(agentId), {
|
| 70 |
method: 'POST',
|
| 71 |
body: { message },
|
| 72 |
});
|
|
|
|
| 75 |
|
| 76 |
// ββ Execute tool directly via API βββββββββββββββββββββββββββββββββ
|
| 77 |
export async function execTool(apiKey, agentId, tool, args) {
|
| 78 |
+
const res = await apiFetch(apiKey, P.toolExec(agentId), {
|
| 79 |
method: 'POST',
|
| 80 |
body: { tool, args },
|
| 81 |
});
|
|
|
|
| 84 |
|
| 85 |
// ββ List agents βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 86 |
export async function listAgents(apiKey) {
|
| 87 |
+
// Sngine returns array directly, Docker returns { ok: true, agents: [...] }
|
| 88 |
+
const res = await apiFetch(apiKey, P.agents);
|
| 89 |
+
const data = await res.json();
|
| 90 |
+
if (Array.isArray(data)) return data;
|
| 91 |
+
return data.agents || data.agent || data;
|
| 92 |
}
|
| 93 |
|
| 94 |
// ββ Get agent status ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 95 |
export async function getAgent(apiKey, agentId) {
|
| 96 |
+
const res = await apiFetch(apiKey, P.agents + '/' + agentId);
|
| 97 |
return res.json();
|
| 98 |
}
|
| 99 |
|
| 100 |
// ββ Stream reasoning from cloud (SSE) βββββββββββββββββββββββββββββ
|
| 101 |
export async function think({ apiKey, messages, tools, onChunk, onUsage, signal }) {
|
| 102 |
+
const res = await apiFetch(apiKey, P.think, {
|
| 103 |
method: 'POST',
|
| 104 |
body: { messages, tools, stream: true },
|
| 105 |
});
|
|
|
|
| 142 |
}
|
| 143 |
|
| 144 |
// ββ Force connection test βββββββββββββββββββββββββββββββββββββββββ
|
| 145 |
+
export async function testConnection(apiKey, targetUrl) {
|
| 146 |
+
// Override base temporarily if targetUrl provided
|
| 147 |
+
const base = targetUrl || CLOUD.base;
|
| 148 |
const results = {};
|
| 149 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
try {
|
| 151 |
+
log.info(`Testing ${base}...`);
|
|
|
|
|
|
|
|
|
|
| 152 |
|
| 153 |
+
// 1. Health check
|
| 154 |
+
results.health = await checkHealth(apiKey);
|
| 155 |
+
|
| 156 |
+
// 2. Verify auth
|
| 157 |
+
try {
|
| 158 |
+
results.auth = await verifyKey(apiKey);
|
| 159 |
+
} catch (err) {
|
| 160 |
+
results.auth = { error: err.message };
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
// 3. List agents
|
| 164 |
+
try {
|
| 165 |
+
const agents = await listAgents(apiKey);
|
| 166 |
+
results.agents = agents;
|
| 167 |
+
} catch (err) {
|
| 168 |
+
results.agents = { error: err.message };
|
| 169 |
+
}
|
| 170 |
} catch (err) {
|
| 171 |
+
results.error = err.message;
|
| 172 |
}
|
| 173 |
|
| 174 |
return results;
|
src/cloud.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
// Cloud API client β all LLM reasoning runs remotely on agent.mona.expert.
|
|
|
|
| 2 |
// This device sends prompts up and receives streamed reasoning back.
|
| 3 |
// No LLM provider keys are ever stored or used locally.
|
| 4 |
|
|
@@ -6,18 +7,22 @@ import { CLOUD, DEFAULTS } from './config.js';
|
|
| 6 |
import { log } from './log.js';
|
| 7 |
|
| 8 |
const UA = `mona-agent/${DEFAULTS.version}`;
|
|
|
|
| 9 |
|
| 10 |
// ββ Generic API fetch βββββββββββββββββββββββββββββββββββββββββββββ
|
| 11 |
-
async function apiFetch(path, { apiKey, method = 'POST', body, signal } = {}) {
|
| 12 |
const url = CLOUD.base + path;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
const res = await fetch(url, {
|
| 14 |
method,
|
| 15 |
-
headers
|
| 16 |
-
'authorization': `Bearer ${apiKey}`,
|
| 17 |
-
'content-type': 'application/json',
|
| 18 |
-
'user-agent': UA,
|
| 19 |
-
'x-mona-agent': DEFAULTS.version,
|
| 20 |
-
},
|
| 21 |
body: body ? JSON.stringify(body) : undefined,
|
| 22 |
signal,
|
| 23 |
});
|
|
@@ -33,7 +38,7 @@ async function apiFetch(path, { apiKey, method = 'POST', body, signal } = {}) {
|
|
| 33 |
|
| 34 |
// ββ Verify API key ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 35 |
export async function verifyKey(apiKey) {
|
| 36 |
-
const res = await apiFetch(
|
| 37 |
return res.json();
|
| 38 |
}
|
| 39 |
|
|
@@ -43,47 +48,62 @@ export async function verifyKey(apiKey) {
|
|
| 43 |
// onUsage(usage) β called with final token counts (if provided)
|
| 44 |
// Returns the full assembled response text.
|
| 45 |
export async function think({ apiKey, messages, tools, onChunk, onUsage, signal }) {
|
| 46 |
-
const res = await apiFetch(
|
| 47 |
apiKey,
|
| 48 |
body: { messages, tools, stream: true },
|
| 49 |
signal,
|
| 50 |
});
|
| 51 |
|
| 52 |
-
|
| 53 |
-
const
|
| 54 |
-
let buf = '';
|
| 55 |
-
let full = '';
|
| 56 |
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
| 61 |
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
}
|
| 78 |
-
if (j.usage) onUsage?.(j.usage);
|
| 79 |
-
} catch {
|
| 80 |
-
// Skip malformed or keepalive lines
|
| 81 |
}
|
| 82 |
}
|
|
|
|
|
|
|
| 83 |
}
|
| 84 |
-
|
| 85 |
-
reader.releaseLock();
|
| 86 |
}
|
| 87 |
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
}
|
|
|
|
| 1 |
// Cloud API client β all LLM reasoning runs remotely on agent.mona.expert.
|
| 2 |
+
// Supports both sngine-based (agent.mona.expert) and Docker-based platforms.
|
| 3 |
// This device sends prompts up and receives streamed reasoning back.
|
| 4 |
// No LLM provider keys are ever stored or used locally.
|
| 5 |
|
|
|
|
| 7 |
import { log } from './log.js';
|
| 8 |
|
| 9 |
const UA = `mona-agent/${DEFAULTS.version}`;
|
| 10 |
+
const P = CLOUD.paths; // platform-aware API paths
|
| 11 |
|
| 12 |
// ββ Generic API fetch βββββββββββββββββββββββββββββββββββββββββββββ
|
| 13 |
+
async function apiFetch(path, { apiKey, method = 'POST', body, signal, headers: extraHeaders } = {}) {
|
| 14 |
const url = CLOUD.base + path;
|
| 15 |
+
const headers = {
|
| 16 |
+
'authorization': `Bearer ${apiKey}`,
|
| 17 |
+
'content-type': 'application/json',
|
| 18 |
+
'user-agent': UA,
|
| 19 |
+
'x-mona-agent': DEFAULTS.version,
|
| 20 |
+
...(extraHeaders || {}),
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
const res = await fetch(url, {
|
| 24 |
method,
|
| 25 |
+
headers,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
body: body ? JSON.stringify(body) : undefined,
|
| 27 |
signal,
|
| 28 |
});
|
|
|
|
| 38 |
|
| 39 |
// ββ Verify API key ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 40 |
export async function verifyKey(apiKey) {
|
| 41 |
+
const res = await apiFetch(P.verifyKey, { apiKey, method: 'GET' });
|
| 42 |
return res.json();
|
| 43 |
}
|
| 44 |
|
|
|
|
| 48 |
// onUsage(usage) β called with final token counts (if provided)
|
| 49 |
// Returns the full assembled response text.
|
| 50 |
export async function think({ apiKey, messages, tools, onChunk, onUsage, signal }) {
|
| 51 |
+
const res = await apiFetch(P.think, {
|
| 52 |
apiKey,
|
| 53 |
body: { messages, tools, stream: true },
|
| 54 |
signal,
|
| 55 |
});
|
| 56 |
|
| 57 |
+
// Handle both SSE streaming and plain JSON responses
|
| 58 |
+
const contentType = res.headers.get('content-type') || '';
|
|
|
|
|
|
|
| 59 |
|
| 60 |
+
if (contentType.includes('text/event-stream')) {
|
| 61 |
+
// SSE streaming
|
| 62 |
+
const reader = res.body.getReader();
|
| 63 |
+
const dec = new TextDecoder();
|
| 64 |
+
let buf = '';
|
| 65 |
+
let full = '';
|
| 66 |
|
| 67 |
+
try {
|
| 68 |
+
while (true) {
|
| 69 |
+
const { value, done } = await reader.read();
|
| 70 |
+
if (done) break;
|
| 71 |
|
| 72 |
+
buf += dec.decode(value, { stream: true });
|
| 73 |
+
const lines = buf.split('\n');
|
| 74 |
+
buf = lines.pop() || '';
|
| 75 |
+
|
| 76 |
+
for (const line of lines) {
|
| 77 |
+
if (!line.startsWith('data:')) continue;
|
| 78 |
+
const payload = line.slice(5).trim();
|
| 79 |
+
if (payload === '[DONE]') return full;
|
| 80 |
+
try {
|
| 81 |
+
const j = JSON.parse(payload);
|
| 82 |
+
if (j.delta) {
|
| 83 |
+
full += j.delta;
|
| 84 |
+
onChunk?.(j.delta);
|
| 85 |
+
}
|
| 86 |
+
if (j.usage) onUsage?.(j.usage);
|
| 87 |
+
} catch {
|
| 88 |
+
// Skip malformed or keepalive lines
|
| 89 |
}
|
|
|
|
|
|
|
|
|
|
| 90 |
}
|
| 91 |
}
|
| 92 |
+
} finally {
|
| 93 |
+
reader.releaseLock();
|
| 94 |
}
|
| 95 |
+
return full;
|
|
|
|
| 96 |
}
|
| 97 |
|
| 98 |
+
// Plain JSON response
|
| 99 |
+
const data = await res.json();
|
| 100 |
+
return data.content || data.text || JSON.stringify(data);
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
// ββ Report tool result to cloud βββββββββββββββββββββββββββββββββββ
|
| 104 |
+
export async function reportToolResult(apiKey, agentId, tool, result) {
|
| 105 |
+
return apiFetch(P.toolResult, {
|
| 106 |
+
apiKey,
|
| 107 |
+
body: { agentId, tool, result },
|
| 108 |
+
});
|
| 109 |
}
|
src/config.js
CHANGED
|
@@ -1,32 +1,88 @@
|
|
| 1 |
// Configuration & credential management.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
// IMPORTANT: only the agent.mona.expert API key lives locally.
|
| 3 |
// No LLM provider keys (OpenAI, Anthropic, etc.) are ever stored on this device.
|
| 4 |
|
| 5 |
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
| 6 |
import { homedir } from 'node:os';
|
| 7 |
-
import { join
|
| 8 |
|
| 9 |
const DIR = join(homedir(), '.mona-agent');
|
| 10 |
const CRED_FILE = join(DIR, 'credentials.json');
|
| 11 |
const CONFIG_FILE = join(DIR, 'config.json');
|
| 12 |
|
| 13 |
// ββ Cloud endpoints βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
get wsUrl() {
|
| 18 |
if (this.ws) return this.ws;
|
| 19 |
const proto = this.base.startsWith('https') ? 'wss' : 'ws';
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
},
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
// ββ Agent defaults ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 25 |
export const DEFAULTS = Object.freeze({
|
| 26 |
metricsIntervalMs: 10_000,
|
| 27 |
reconnectMinMs: 1_000,
|
| 28 |
reconnectMaxMs: 30_000,
|
| 29 |
-
version: '1.
|
| 30 |
});
|
| 31 |
|
| 32 |
// ββ Credential management βββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 1 |
// Configuration & credential management.
|
| 2 |
+
// Supports:
|
| 3 |
+
// - agent.mona.expert (sngine-based cloud) β default
|
| 4 |
+
// - Self-hosted Docker platform (port 4300)
|
| 5 |
+
// - Custom control planes via MONA_CLOUD / MONA_CLOUD_WS
|
| 6 |
+
//
|
| 7 |
// IMPORTANT: only the agent.mona.expert API key lives locally.
|
| 8 |
// No LLM provider keys (OpenAI, Anthropic, etc.) are ever stored on this device.
|
| 9 |
|
| 10 |
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
| 11 |
import { homedir } from 'node:os';
|
| 12 |
+
import { join } from 'node:path';
|
| 13 |
|
| 14 |
const DIR = join(homedir(), '.mona-agent');
|
| 15 |
const CRED_FILE = join(DIR, 'credentials.json');
|
| 16 |
const CONFIG_FILE = join(DIR, 'config.json');
|
| 17 |
|
| 18 |
// ββ Cloud endpoints βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 19 |
+
const baseUrl = process.env.MONA_CLOUD || 'https://agent.mona.expert';
|
| 20 |
+
|
| 21 |
+
// Auto-detect platform type from URL
|
| 22 |
+
function detectPlatform(url) {
|
| 23 |
+
try {
|
| 24 |
+
const host = new URL(url).host;
|
| 25 |
+
// Sngine-based: agent.mona.expert (and subdomains)
|
| 26 |
+
if (host.includes('mona.expert') && !url.includes(':4300')) return 'sngine';
|
| 27 |
+
// Docker platform: localhost:4300
|
| 28 |
+
if (url.includes(':4300')) return 'docker';
|
| 29 |
+
return 'sngine'; // default
|
| 30 |
+
} catch { return 'sngine'; }
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
const PLATFORM = detectPlatform(baseUrl);
|
| 34 |
+
|
| 35 |
+
export const CLOUD = {
|
| 36 |
+
base: baseUrl,
|
| 37 |
+
ws: process.env.MONA_CLOUD_WS || null,
|
| 38 |
+
platform: PLATFORM,
|
| 39 |
+
|
| 40 |
get wsUrl() {
|
| 41 |
if (this.ws) return this.ws;
|
| 42 |
const proto = this.base.startsWith('https') ? 'wss' : 'ws';
|
| 43 |
+
const host = new URL(this.base).host;
|
| 44 |
+
|
| 45 |
+
if (PLATFORM === 'docker') {
|
| 46 |
+
return `ws://${host}/ws?type=agent`;
|
| 47 |
+
}
|
| 48 |
+
// Sngine: WebSocket relay on same host, port 4390
|
| 49 |
+
// Nginx proxies /ws to localhost:4390
|
| 50 |
+
return `${proto}://${host}/ws?role=device`;
|
| 51 |
},
|
| 52 |
+
|
| 53 |
+
// API paths β vary by platform
|
| 54 |
+
get paths() {
|
| 55 |
+
if (PLATFORM === 'docker') {
|
| 56 |
+
return {
|
| 57 |
+
verifyKey: '/api/keys/verify',
|
| 58 |
+
think: '/api/llm/think',
|
| 59 |
+
health: '/health',
|
| 60 |
+
agents: '/api/agents',
|
| 61 |
+
chat: (id) => `/api/agents/${id}/chat`,
|
| 62 |
+
toolExec: (id) => `/api/agents/${id}/tool`,
|
| 63 |
+
};
|
| 64 |
+
}
|
| 65 |
+
// Sngine/mona.expert
|
| 66 |
+
return {
|
| 67 |
+
verifyKey: '/api/v1/agent/verify',
|
| 68 |
+
think: '/api/v1/agent/think',
|
| 69 |
+
toolResult: '/api/v1/agent/tool-result',
|
| 70 |
+
health: '/api/v1/mona/system', // uses system endpoint as health check
|
| 71 |
+
agents: '/api/v1/mona/agents',
|
| 72 |
+
chat: (id) => `/api/v1/mona/agents/${id}/chat`,
|
| 73 |
+
toolExec: (id) => `/api/v1/mona/agents/${id}/tool`,
|
| 74 |
+
};
|
| 75 |
+
},
|
| 76 |
+
};
|
| 77 |
+
|
| 78 |
+
Object.freeze(CLOUD);
|
| 79 |
|
| 80 |
// ββ Agent defaults ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 81 |
export const DEFAULTS = Object.freeze({
|
| 82 |
metricsIntervalMs: 10_000,
|
| 83 |
reconnectMinMs: 1_000,
|
| 84 |
reconnectMaxMs: 30_000,
|
| 85 |
+
version: '1.1.0',
|
| 86 |
});
|
| 87 |
|
| 88 |
// ββ Credential management βββββββββββββββββββββββββββββββββββββββββ
|