feat: wire device pod gateway dispatch
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
import {
|
||||
SUPPORTED_RPC_METHODS,
|
||||
createErrorEnvelope,
|
||||
createResultEnvelope,
|
||||
handleJsonRpcRequest
|
||||
} from "./json-rpc.ts";
|
||||
import {
|
||||
@@ -341,6 +342,11 @@ async function handleRestAdapter(request, response, url, options) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.pathname === "/v1/internal/device-pod/gateway-dispatch") {
|
||||
await handleInternalDevicePodGatewayDispatch(request, response, options);
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.method === "POST" && url.pathname === "/v1/gateway/poll") {
|
||||
await handleGatewayPollHttp(request, response, options);
|
||||
return;
|
||||
@@ -459,6 +465,91 @@ async function handleRestAdapter(request, response, url, options) {
|
||||
sendJson(response, statusForRpcResponse(rpcResponse), rpcResponse);
|
||||
}
|
||||
|
||||
async function handleInternalDevicePodGatewayDispatch(request, response, options) {
|
||||
if (request.method !== "POST") {
|
||||
sendJson(response, 405, { error: { code: "method_not_allowed", message: "Internal device-pod gateway dispatch only supports POST" } });
|
||||
return;
|
||||
}
|
||||
if (internalServiceHeader(request) !== "hwlab-device-pod") {
|
||||
sendJson(response, 403, { error: { code: "device_pod_internal_authority_required", message: "Only hwlab-device-pod may use the internal gateway dispatch route" } });
|
||||
return;
|
||||
}
|
||||
|
||||
const body = await readJsonObject(request, options.bodyLimitBytes);
|
||||
if (!body.ok) {
|
||||
sendJson(response, 400, body.error);
|
||||
return;
|
||||
}
|
||||
const params = body.value?.params && typeof body.value.params === "object" && !Array.isArray(body.value.params) ? body.value.params : {};
|
||||
const gatewaySessionId = typeof params.gatewaySessionId === "string" ? params.gatewaySessionId.trim() : "";
|
||||
if (!gatewaySessionId || options.gatewayRegistry?.isOnline?.(gatewaySessionId) !== true) {
|
||||
sendJson(response, 409, {
|
||||
accepted: false,
|
||||
status: "blocked",
|
||||
blocker: {
|
||||
code: "gateway_dispatch_unavailable",
|
||||
layer: "device-pod",
|
||||
retryable: true,
|
||||
summary: gatewaySessionId ? "gateway session is not connected" : "profile route.gatewaySessionId is missing"
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const operationId = typeof params.operationId === "string" && params.operationId ? params.operationId : `op_devicepod_gateway_${randomUUID()}`;
|
||||
const traceId = typeof params.traceId === "string" && params.traceId ? params.traceId : `trc_devicepod_gateway_${randomUUID()}`;
|
||||
const rpcResponse = await handleJsonRpcRequest({
|
||||
jsonrpc: "2.0",
|
||||
id: typeof body.value.id === "string" && body.value.id ? body.value.id : getHeader(request, "x-request-id") || `req_devicepod_gateway_${randomUUID()}`,
|
||||
method: "hardware.invoke.shell",
|
||||
params: { ...params, gatewaySessionId, operationId, traceId },
|
||||
meta: {
|
||||
traceId,
|
||||
actorId: typeof body.value.actorId === "string" ? body.value.actorId : "svc_hwlab-device-pod",
|
||||
serviceId: "hwlab-device-pod",
|
||||
environment: runtimeEnvironment(options.env ?? process.env)
|
||||
}
|
||||
}, {
|
||||
adapter: "device-pod-internal",
|
||||
runtimeStore: options.runtimeStore,
|
||||
env: options.env,
|
||||
dbProbe: options.dbProbe,
|
||||
m3IoRequestJson: options.m3IoRequestJson,
|
||||
gatewayRegistry: options.gatewayRegistry
|
||||
});
|
||||
|
||||
if (rpcResponse.error) {
|
||||
sendJson(response, statusForRpcResponse(rpcResponse), rpcResponse);
|
||||
return;
|
||||
}
|
||||
const result = rpcResponse.result ?? {};
|
||||
const dispatch = result.dispatch ?? {};
|
||||
const completed = result.status === "succeeded" || result.status === "completed" || dispatch.dispatchStatus === "succeeded" || dispatch.dispatchStatus === "completed";
|
||||
sendJson(response, completed ? 200 : 409, createResultEnvelope({
|
||||
id: rpcResponse.id,
|
||||
result: {
|
||||
accepted: completed,
|
||||
status: completed ? "completed" : "failed",
|
||||
operationId: result.operationId ?? operationId,
|
||||
traceId,
|
||||
gatewaySessionId,
|
||||
resourceId: result.resourceId ?? params.resourceId ?? null,
|
||||
capabilityId: result.capabilityId ?? params.capabilityId ?? null,
|
||||
dispatch,
|
||||
auditId: result.auditId ?? null,
|
||||
evidenceId: result.evidenceId ?? null,
|
||||
gateway: result.gateway ?? null,
|
||||
blocker: completed ? null : {
|
||||
code: "gateway_dispatch_failed",
|
||||
layer: "device-pod",
|
||||
retryable: true,
|
||||
summary: dispatch.message ?? dispatch.stderr ?? dispatch.status ?? result.status ?? "gateway dispatch failed"
|
||||
}
|
||||
},
|
||||
requestMeta: { traceId, serviceId: "hwlab-device-pod" }
|
||||
}));
|
||||
}
|
||||
|
||||
async function codeAgentOptions(request, response, options) {
|
||||
const auth = await options.accessController.authenticate(request, { required: options.accessController.required });
|
||||
if (!auth.ok) {
|
||||
@@ -468,6 +559,23 @@ async function codeAgentOptions(request, response, options) {
|
||||
return { ...options, actor: auth.actor ?? null, authSession: auth.session ?? null };
|
||||
}
|
||||
|
||||
async function readJsonObject(request, limitBytes) {
|
||||
const body = await readBody(request, limitBytes);
|
||||
try {
|
||||
const value = body ? JSON.parse(body) : {};
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return { ok: false, error: { accepted: false, error: { code: "invalid_params", message: "body must be a JSON object" } } };
|
||||
}
|
||||
return { ok: true, value };
|
||||
} catch (error) {
|
||||
return { ok: false, error: { accepted: false, error: { code: "parse_error", message: "Invalid JSON body", reason: error.message } } };
|
||||
}
|
||||
}
|
||||
|
||||
function internalServiceHeader(request) {
|
||||
return String(getHeader(request, "x-hwlab-internal-service") ?? "").trim();
|
||||
}
|
||||
|
||||
async function handleGatewayPollHttp(request, response, options) {
|
||||
const body = await readBody(request, options.bodyLimitBytes);
|
||||
let params = {};
|
||||
|
||||
Reference in New Issue
Block a user