feat: add gateway config/update restart flow

This commit is contained in:
Peter Steinberger
2026-01-08 01:29:56 +01:00
parent 3398fc3820
commit 71c31266a1
28 changed files with 1630 additions and 50 deletions

View File

@@ -1,6 +1,8 @@
import { Type } from "@sinclair/typebox";
import { scheduleGatewaySigusr1Restart } from "../../infra/restart.js";
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
import { callGatewayTool } from "./gateway.js";
const GatewayToolSchema = Type.Union([
Type.Object({
@@ -8,46 +10,137 @@ const GatewayToolSchema = Type.Union([
delayMs: Type.Optional(Type.Number()),
reason: Type.Optional(Type.String()),
}),
Type.Object({
action: Type.Literal("config.get"),
gatewayUrl: Type.Optional(Type.String()),
gatewayToken: Type.Optional(Type.String()),
timeoutMs: Type.Optional(Type.Number()),
}),
Type.Object({
action: Type.Literal("config.schema"),
gatewayUrl: Type.Optional(Type.String()),
gatewayToken: Type.Optional(Type.String()),
timeoutMs: Type.Optional(Type.Number()),
}),
Type.Object({
action: Type.Literal("config.apply"),
raw: Type.String(),
sessionKey: Type.Optional(Type.String()),
note: Type.Optional(Type.String()),
restartDelayMs: Type.Optional(Type.Number()),
gatewayUrl: Type.Optional(Type.String()),
gatewayToken: Type.Optional(Type.String()),
timeoutMs: Type.Optional(Type.Number()),
}),
Type.Object({
action: Type.Literal("update.run"),
sessionKey: Type.Optional(Type.String()),
note: Type.Optional(Type.String()),
restartDelayMs: Type.Optional(Type.Number()),
timeoutMs: Type.Optional(Type.Number()),
gatewayUrl: Type.Optional(Type.String()),
gatewayToken: Type.Optional(Type.String()),
}),
]);
export function createGatewayTool(): AnyAgentTool {
export function createGatewayTool(opts?: {
agentSessionKey?: string;
}): AnyAgentTool {
return {
label: "Gateway",
name: "gateway",
description:
"Restart the running gateway process in-place (SIGUSR1) without needing an external supervisor. Use delayMs to avoid interrupting an in-flight reply.",
"Restart, apply config, or update the gateway in-place (SIGUSR1). Use config.apply/update.run to write config or run updates with validation and restart.",
parameters: GatewayToolSchema,
execute: async (_toolCallId, args) => {
const params = args as Record<string, unknown>;
const action = readStringParam(params, "action", { required: true });
if (action !== "restart") throw new Error(`Unknown action: ${action}`);
if (action === "restart") {
const delayMs =
typeof params.delayMs === "number" && Number.isFinite(params.delayMs)
? Math.floor(params.delayMs)
: undefined;
const reason =
typeof params.reason === "string" && params.reason.trim()
? params.reason.trim().slice(0, 200)
: undefined;
const scheduled = scheduleGatewaySigusr1Restart({
delayMs,
reason,
});
return jsonResult(scheduled);
}
const delayMsRaw =
typeof params.delayMs === "number" && Number.isFinite(params.delayMs)
? Math.floor(params.delayMs)
: 2000;
const delayMs = Math.min(Math.max(delayMsRaw, 0), 60_000);
const reason =
typeof params.reason === "string" && params.reason.trim()
? params.reason.trim().slice(0, 200)
const gatewayUrl =
typeof params.gatewayUrl === "string" && params.gatewayUrl.trim()
? params.gatewayUrl.trim()
: undefined;
const gatewayToken =
typeof params.gatewayToken === "string" && params.gatewayToken.trim()
? params.gatewayToken.trim()
: undefined;
const timeoutMs =
typeof params.timeoutMs === "number" &&
Number.isFinite(params.timeoutMs)
? Math.max(1, Math.floor(params.timeoutMs))
: undefined;
const gatewayOpts = { gatewayUrl, gatewayToken, timeoutMs };
const pid = process.pid;
setTimeout(() => {
try {
process.kill(pid, "SIGUSR1");
} catch {
/* ignore */
}
}, delayMs);
if (action === "config.get") {
const result = await callGatewayTool("config.get", gatewayOpts, {});
return jsonResult({ ok: true, result });
}
if (action === "config.schema") {
const result = await callGatewayTool("config.schema", gatewayOpts, {});
return jsonResult({ ok: true, result });
}
if (action === "config.apply") {
const raw = readStringParam(params, "raw", { required: true });
const sessionKey =
typeof params.sessionKey === "string" && params.sessionKey.trim()
? params.sessionKey.trim()
: opts?.agentSessionKey?.trim() || undefined;
const note =
typeof params.note === "string" && params.note.trim()
? params.note.trim()
: undefined;
const restartDelayMs =
typeof params.restartDelayMs === "number" &&
Number.isFinite(params.restartDelayMs)
? Math.floor(params.restartDelayMs)
: undefined;
const result = await callGatewayTool("config.apply", gatewayOpts, {
raw,
sessionKey,
note,
restartDelayMs,
});
return jsonResult({ ok: true, result });
}
if (action === "update.run") {
const sessionKey =
typeof params.sessionKey === "string" && params.sessionKey.trim()
? params.sessionKey.trim()
: opts?.agentSessionKey?.trim() || undefined;
const note =
typeof params.note === "string" && params.note.trim()
? params.note.trim()
: undefined;
const restartDelayMs =
typeof params.restartDelayMs === "number" &&
Number.isFinite(params.restartDelayMs)
? Math.floor(params.restartDelayMs)
: undefined;
const result = await callGatewayTool("update.run", gatewayOpts, {
sessionKey,
note,
restartDelayMs,
timeoutMs,
});
return jsonResult({ ok: true, result });
}
return jsonResult({
ok: true,
pid,
signal: "SIGUSR1",
delayMs,
reason: reason ?? null,
});
throw new Error(`Unknown action: ${action}`);
},
};
}