Files
memind_adm/server/skills/goosed-client.mjs
T

92 lines
2.7 KiB
JavaScript

/**
* Goosed HTTP client with dual-node support.
*
* 103 production: two goosed nodes on :18006 and :18007 (HTTPS + self-signed TLS).
* Local dev: one node (GOOSED_URLS=https://127.0.0.1:18006 or falls back to TKMIND_API_TARGET).
*
* Skill calls are stateless one-shot requests — no session affinity needed.
* We round-robin across available nodes for basic load distribution.
*/
import https from 'node:https';
// Self-signed cert agent — mirrors what Memind server.mjs does for tkmind-proxy
const tlsAgent = new https.Agent({ rejectUnauthorized: false });
function parseGoosedUrls() {
const raw =
process.env.GOOSED_URLS ??
[
process.env.TKMIND_API_TARGET,
process.env.TKMIND_API_TARGET_1,
]
.filter(Boolean)
.join(',');
if (!raw) return ['https://127.0.0.1:18006'];
return raw
.split(',')
.map((u) => u.trim())
.filter(Boolean);
}
let _roundRobinIdx = 0;
export function pickGoosedUrl() {
const urls = parseGoosedUrls();
const url = urls[_roundRobinIdx % urls.length];
_roundRobinIdx = (_roundRobinIdx + 1) % urls.length;
return url;
}
export function goosedUrls() {
return parseGoosedUrls();
}
/**
* Make a JSON request to a goosed node.
* Returns { ok, status, body } — never throws on HTTP errors, only on network failure.
* Uses https.request directly so rejectUnauthorized: false works for self-signed certs.
*/
export function goosedFetch(path, { method = 'GET', body, node } = {}) {
const base = node ?? pickGoosedUrl();
const secret =
process.env.GOOSE_SERVER__SECRET_KEY ??
process.env.TKMIND_SERVER__SECRET_KEY ??
'local-dev-secret';
const url = new URL(`${base}${path}`);
const payload = body !== undefined ? JSON.stringify(body) : null;
return new Promise((resolve, reject) => {
const options = {
hostname: url.hostname,
port: url.port || (url.protocol === 'https:' ? 443 : 80),
path: url.pathname + url.search,
method,
headers: {
'Content-Type': 'application/json',
'X-Secret-Key': secret,
...(payload ? { 'Content-Length': Buffer.byteLength(payload) } : {}),
},
agent: tlsAgent,
};
const req = https.request(options, (res) => {
let text = '';
res.setEncoding('utf8');
res.on('data', (chunk) => { text += chunk; });
res.on('end', () => {
let json = null;
try { json = JSON.parse(text); } catch { json = { raw: text }; }
resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode, body: json });
});
res.on('error', reject);
});
req.on('error', reject);
if (payload) req.write(payload);
req.end();
});
}