Add MindSpace service runtime release chain
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
import http from 'node:http';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
async function loadContract(env = process.env) {
|
||||
const memindRoot = path.resolve(__dirname, env.MINDSPACE_MEMIND_ROOT ?? '../memind-source');
|
||||
return import(
|
||||
pathToFileURL(path.join(memindRoot, 'mindspace-server-adapter-contract.mjs')).href
|
||||
);
|
||||
}
|
||||
|
||||
function parseBearerToken(headers) {
|
||||
const auth = String(headers.authorization ?? '');
|
||||
if (!auth.startsWith('Bearer ')) return '';
|
||||
return auth.slice('Bearer '.length).trim();
|
||||
}
|
||||
|
||||
async function readJsonBody(req) {
|
||||
const chunks = [];
|
||||
for await (const chunk of req) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
const text = Buffer.concat(chunks).toString('utf8').trim();
|
||||
return text ? JSON.parse(text) : {};
|
||||
}
|
||||
|
||||
function reviveRpcValue(value) {
|
||||
if (
|
||||
value &&
|
||||
typeof value === 'object' &&
|
||||
value.type === 'Buffer' &&
|
||||
Array.isArray(value.data)
|
||||
) {
|
||||
return Buffer.from(value.data);
|
||||
}
|
||||
if (Array.isArray(value)) return value.map((item) => reviveRpcValue(item));
|
||||
if (value && typeof value === 'object') {
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([key, item]) => [key, reviveRpcValue(item)]),
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function json(res, statusCode, payload) {
|
||||
const body = JSON.stringify(payload);
|
||||
res.writeHead(statusCode, {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'Content-Length': Buffer.byteLength(body),
|
||||
});
|
||||
res.end(body);
|
||||
}
|
||||
|
||||
export async function createMindSpaceRpcRequestHandler({
|
||||
adapter,
|
||||
env = process.env,
|
||||
logger = console,
|
||||
serviceMeta = {},
|
||||
} = {}) {
|
||||
const {
|
||||
MINDSPACE_SERVER_ADAPTER_BINDINGS,
|
||||
MINDSPACE_SERVER_ADAPTER_BINDING_KEYS,
|
||||
} = await loadContract(env);
|
||||
const operationBasePath = String(
|
||||
serviceMeta.operationBasePath ?? env.MINDSPACE_REMOTE_OPERATION_BASE_PATH ?? '/mindspace/v1/adapter',
|
||||
)
|
||||
.trim()
|
||||
.replace(/\/+$/, '') || '/mindspace/v1/adapter';
|
||||
const authToken = String(env.MINDSPACE_REMOTE_AUTH_TOKEN ?? '').trim();
|
||||
|
||||
return async function handleMindSpaceRpc(req, res) {
|
||||
try {
|
||||
const url = new URL(req.url, 'http://127.0.0.1');
|
||||
if (req.method === 'GET' && url.pathname === '/health') {
|
||||
return json(res, 200, {
|
||||
ok: true,
|
||||
service: 'mindspace-service',
|
||||
adapterKind: adapter.kind,
|
||||
implementationStatus: adapter.implementationStatus,
|
||||
operationBasePath,
|
||||
backgroundJobsManagedLocally: adapter.kind === 'local',
|
||||
});
|
||||
}
|
||||
if (req.method === 'GET' && url.pathname === '/mindspace/v1/contract') {
|
||||
return json(res, 200, {
|
||||
bindings: MINDSPACE_SERVER_ADAPTER_BINDINGS,
|
||||
bindingKeys: MINDSPACE_SERVER_ADAPTER_BINDING_KEYS,
|
||||
});
|
||||
}
|
||||
if (req.method !== 'POST' || !url.pathname.startsWith(`${operationBasePath}/`)) {
|
||||
return json(res, 404, { message: 'Not found' });
|
||||
}
|
||||
if (authToken && parseBearerToken(req.headers) !== authToken) {
|
||||
return json(res, 401, { message: 'Unauthorized' });
|
||||
}
|
||||
const segments = url.pathname.slice(operationBasePath.length + 1).split('/').filter(Boolean);
|
||||
if (segments.length !== 2) {
|
||||
return json(res, 400, { message: 'Expected /:binding/:method' });
|
||||
}
|
||||
const [bindingKey, method] = segments.map(decodeURIComponent);
|
||||
const service = adapter?.[bindingKey];
|
||||
const allowedMethods = MINDSPACE_SERVER_ADAPTER_BINDINGS[bindingKey];
|
||||
if (!service || !allowedMethods) {
|
||||
return json(res, 404, { message: `Unknown binding: ${bindingKey}` });
|
||||
}
|
||||
if (!allowedMethods.includes(method)) {
|
||||
return json(res, 404, { message: `Unknown method: ${bindingKey}.${method}` });
|
||||
}
|
||||
const body = await readJsonBody(req);
|
||||
const args = Array.isArray(body?.args) ? reviveRpcValue(body.args) : [];
|
||||
const result = await service[method](...args);
|
||||
return json(res, 200, result);
|
||||
} catch (error) {
|
||||
logger.error?.('[MindSpace RPC Error]', error);
|
||||
return json(res, 500, {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function startMindSpaceRpcServer({
|
||||
adapter,
|
||||
env = process.env,
|
||||
logger = console,
|
||||
serviceMeta = {},
|
||||
} = {}) {
|
||||
const handler = await createMindSpaceRpcRequestHandler({
|
||||
adapter,
|
||||
env,
|
||||
logger,
|
||||
serviceMeta,
|
||||
});
|
||||
const host = env.MINDSPACE_SERVICE_HOST ?? '127.0.0.1';
|
||||
const port = Number(env.MINDSPACE_SERVICE_PORT ?? 8082);
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
void handler(req, res);
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
server.once('error', reject);
|
||||
server.listen(port, host, () => resolve());
|
||||
});
|
||||
|
||||
return {
|
||||
server,
|
||||
host,
|
||||
port,
|
||||
close: () =>
|
||||
new Promise((resolve, reject) => {
|
||||
server.close((error) => {
|
||||
if (error) reject(error);
|
||||
else resolve();
|
||||
});
|
||||
}),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user