mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 19:14:58 +00:00
refactor(gateway): share server-method param validation
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import type { GatewayRequestHandlers } from "./types.js";
|
import type { GatewayRequestHandlers, RespondFn } from "./types.js";
|
||||||
import { resolveDefaultAgentId } from "../../agents/agent-scope.js";
|
import { resolveDefaultAgentId } from "../../agents/agent-scope.js";
|
||||||
import { abortEmbeddedPiRun, waitForEmbeddedPiRunEnd } from "../../agents/pi-embedded.js";
|
import { abortEmbeddedPiRun, waitForEmbeddedPiRunEnd } from "../../agents/pi-embedded.js";
|
||||||
import { stopSubagentsForRequester } from "../../auto-reply/reply/abort.js";
|
import { stopSubagentsForRequester } from "../../auto-reply/reply/abort.js";
|
||||||
@@ -18,7 +18,6 @@ import { normalizeAgentId, parseAgentSessionKey } from "../../routing/session-ke
|
|||||||
import {
|
import {
|
||||||
ErrorCodes,
|
ErrorCodes,
|
||||||
errorShape,
|
errorShape,
|
||||||
formatValidationErrors,
|
|
||||||
validateSessionsCompactParams,
|
validateSessionsCompactParams,
|
||||||
validateSessionsDeleteParams,
|
validateSessionsDeleteParams,
|
||||||
validateSessionsListParams,
|
validateSessionsListParams,
|
||||||
@@ -44,6 +43,22 @@ import {
|
|||||||
} from "../session-utils.js";
|
} from "../session-utils.js";
|
||||||
import { applySessionsPatchToStore } from "../sessions-patch.js";
|
import { applySessionsPatchToStore } from "../sessions-patch.js";
|
||||||
import { resolveSessionKeyFromResolveParams } from "../sessions-resolve.js";
|
import { resolveSessionKeyFromResolveParams } from "../sessions-resolve.js";
|
||||||
|
import { assertValidParams } from "./validation.js";
|
||||||
|
|
||||||
|
function requireSessionKey(key: unknown, respond: RespondFn): string | null {
|
||||||
|
const normalized = String(key ?? "").trim();
|
||||||
|
if (!normalized) {
|
||||||
|
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "key required"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveGatewaySessionTargetFromKey(key: string) {
|
||||||
|
const cfg = loadConfig();
|
||||||
|
const target = resolveGatewaySessionStoreTarget({ cfg, key });
|
||||||
|
return { cfg, target, storePath: target.storePath };
|
||||||
|
}
|
||||||
|
|
||||||
function migrateAndPruneSessionStoreKey(params: {
|
function migrateAndPruneSessionStoreKey(params: {
|
||||||
cfg: ReturnType<typeof loadConfig>;
|
cfg: ReturnType<typeof loadConfig>;
|
||||||
@@ -118,15 +133,7 @@ async function ensureSessionRuntimeCleanup(params: {
|
|||||||
|
|
||||||
export const sessionsHandlers: GatewayRequestHandlers = {
|
export const sessionsHandlers: GatewayRequestHandlers = {
|
||||||
"sessions.list": ({ params, respond }) => {
|
"sessions.list": ({ params, respond }) => {
|
||||||
if (!validateSessionsListParams(params)) {
|
if (!assertValidParams(params, validateSessionsListParams, "sessions.list", respond)) {
|
||||||
respond(
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
errorShape(
|
|
||||||
ErrorCodes.INVALID_REQUEST,
|
|
||||||
`invalid sessions.list params: ${formatValidationErrors(validateSessionsListParams.errors)}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const p = params;
|
const p = params;
|
||||||
@@ -141,17 +148,7 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
|||||||
respond(true, result, undefined);
|
respond(true, result, undefined);
|
||||||
},
|
},
|
||||||
"sessions.preview": ({ params, respond }) => {
|
"sessions.preview": ({ params, respond }) => {
|
||||||
if (!validateSessionsPreviewParams(params)) {
|
if (!assertValidParams(params, validateSessionsPreviewParams, "sessions.preview", respond)) {
|
||||||
respond(
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
errorShape(
|
|
||||||
ErrorCodes.INVALID_REQUEST,
|
|
||||||
`invalid sessions.preview params: ${formatValidationErrors(
|
|
||||||
validateSessionsPreviewParams.errors,
|
|
||||||
)}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const p = params;
|
const p = params;
|
||||||
@@ -213,15 +210,7 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
|||||||
respond(true, { ts: Date.now(), previews } satisfies SessionsPreviewResult, undefined);
|
respond(true, { ts: Date.now(), previews } satisfies SessionsPreviewResult, undefined);
|
||||||
},
|
},
|
||||||
"sessions.resolve": async ({ params, respond }) => {
|
"sessions.resolve": async ({ params, respond }) => {
|
||||||
if (!validateSessionsResolveParams(params)) {
|
if (!assertValidParams(params, validateSessionsResolveParams, "sessions.resolve", respond)) {
|
||||||
respond(
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
errorShape(
|
|
||||||
ErrorCodes.INVALID_REQUEST,
|
|
||||||
`invalid sessions.resolve params: ${formatValidationErrors(validateSessionsResolveParams.errors)}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const p = params;
|
const p = params;
|
||||||
@@ -235,27 +224,16 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
|||||||
respond(true, { ok: true, key: resolved.key }, undefined);
|
respond(true, { ok: true, key: resolved.key }, undefined);
|
||||||
},
|
},
|
||||||
"sessions.patch": async ({ params, respond, context }) => {
|
"sessions.patch": async ({ params, respond, context }) => {
|
||||||
if (!validateSessionsPatchParams(params)) {
|
if (!assertValidParams(params, validateSessionsPatchParams, "sessions.patch", respond)) {
|
||||||
respond(
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
errorShape(
|
|
||||||
ErrorCodes.INVALID_REQUEST,
|
|
||||||
`invalid sessions.patch params: ${formatValidationErrors(validateSessionsPatchParams.errors)}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const p = params;
|
const p = params;
|
||||||
const key = String(p.key ?? "").trim();
|
const key = requireSessionKey(p.key, respond);
|
||||||
if (!key) {
|
if (!key) {
|
||||||
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "key required"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cfg = loadConfig();
|
const { cfg, target, storePath } = resolveGatewaySessionTargetFromKey(key);
|
||||||
const target = resolveGatewaySessionStoreTarget({ cfg, key });
|
|
||||||
const storePath = target.storePath;
|
|
||||||
const applied = await updateSessionStore(storePath, async (store) => {
|
const applied = await updateSessionStore(storePath, async (store) => {
|
||||||
const { primaryKey } = migrateAndPruneSessionStoreKey({ cfg, key, store });
|
const { primaryKey } = migrateAndPruneSessionStoreKey({ cfg, key, store });
|
||||||
return await applySessionsPatchToStore({
|
return await applySessionsPatchToStore({
|
||||||
@@ -286,26 +264,16 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
|||||||
respond(true, result, undefined);
|
respond(true, result, undefined);
|
||||||
},
|
},
|
||||||
"sessions.reset": async ({ params, respond }) => {
|
"sessions.reset": async ({ params, respond }) => {
|
||||||
if (!validateSessionsResetParams(params)) {
|
if (!assertValidParams(params, validateSessionsResetParams, "sessions.reset", respond)) {
|
||||||
respond(
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
errorShape(
|
|
||||||
ErrorCodes.INVALID_REQUEST,
|
|
||||||
`invalid sessions.reset params: ${formatValidationErrors(validateSessionsResetParams.errors)}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const p = params;
|
const p = params;
|
||||||
const key = String(p.key ?? "").trim();
|
const key = requireSessionKey(p.key, respond);
|
||||||
if (!key) {
|
if (!key) {
|
||||||
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "key required"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cfg = loadConfig();
|
const { cfg, target, storePath } = resolveGatewaySessionTargetFromKey(key);
|
||||||
const target = resolveGatewaySessionStoreTarget({ cfg, key });
|
|
||||||
const { entry } = loadSessionEntry(key);
|
const { entry } = loadSessionEntry(key);
|
||||||
const commandReason = p.reason === "new" ? "new" : "reset";
|
const commandReason = p.reason === "new" ? "new" : "reset";
|
||||||
const hookEvent = createInternalHookEvent(
|
const hookEvent = createInternalHookEvent(
|
||||||
@@ -326,7 +294,6 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
|||||||
respond(false, undefined, cleanupError);
|
respond(false, undefined, cleanupError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const storePath = target.storePath;
|
|
||||||
let oldSessionId: string | undefined;
|
let oldSessionId: string | undefined;
|
||||||
let oldSessionFile: string | undefined;
|
let oldSessionFile: string | undefined;
|
||||||
const next = await updateSessionStore(storePath, (store) => {
|
const next = await updateSessionStore(storePath, (store) => {
|
||||||
@@ -372,27 +339,17 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
|||||||
respond(true, { ok: true, key: target.canonicalKey, entry: next }, undefined);
|
respond(true, { ok: true, key: target.canonicalKey, entry: next }, undefined);
|
||||||
},
|
},
|
||||||
"sessions.delete": async ({ params, respond }) => {
|
"sessions.delete": async ({ params, respond }) => {
|
||||||
if (!validateSessionsDeleteParams(params)) {
|
if (!assertValidParams(params, validateSessionsDeleteParams, "sessions.delete", respond)) {
|
||||||
respond(
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
errorShape(
|
|
||||||
ErrorCodes.INVALID_REQUEST,
|
|
||||||
`invalid sessions.delete params: ${formatValidationErrors(validateSessionsDeleteParams.errors)}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const p = params;
|
const p = params;
|
||||||
const key = String(p.key ?? "").trim();
|
const key = requireSessionKey(p.key, respond);
|
||||||
if (!key) {
|
if (!key) {
|
||||||
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "key required"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cfg = loadConfig();
|
const { cfg, target, storePath } = resolveGatewaySessionTargetFromKey(key);
|
||||||
const mainKey = resolveMainSessionKey(cfg);
|
const mainKey = resolveMainSessionKey(cfg);
|
||||||
const target = resolveGatewaySessionStoreTarget({ cfg, key });
|
|
||||||
if (target.canonicalKey === mainKey) {
|
if (target.canonicalKey === mainKey) {
|
||||||
respond(
|
respond(
|
||||||
false,
|
false,
|
||||||
@@ -404,7 +361,6 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
|||||||
|
|
||||||
const deleteTranscript = typeof p.deleteTranscript === "boolean" ? p.deleteTranscript : true;
|
const deleteTranscript = typeof p.deleteTranscript === "boolean" ? p.deleteTranscript : true;
|
||||||
|
|
||||||
const storePath = target.storePath;
|
|
||||||
const { entry } = loadSessionEntry(key);
|
const { entry } = loadSessionEntry(key);
|
||||||
const sessionId = entry?.sessionId;
|
const sessionId = entry?.sessionId;
|
||||||
const existed = Boolean(entry);
|
const existed = Boolean(entry);
|
||||||
@@ -433,21 +389,12 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
|||||||
respond(true, { ok: true, key: target.canonicalKey, deleted: existed, archived }, undefined);
|
respond(true, { ok: true, key: target.canonicalKey, deleted: existed, archived }, undefined);
|
||||||
},
|
},
|
||||||
"sessions.compact": async ({ params, respond }) => {
|
"sessions.compact": async ({ params, respond }) => {
|
||||||
if (!validateSessionsCompactParams(params)) {
|
if (!assertValidParams(params, validateSessionsCompactParams, "sessions.compact", respond)) {
|
||||||
respond(
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
errorShape(
|
|
||||||
ErrorCodes.INVALID_REQUEST,
|
|
||||||
`invalid sessions.compact params: ${formatValidationErrors(validateSessionsCompactParams.errors)}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const p = params;
|
const p = params;
|
||||||
const key = String(p.key ?? "").trim();
|
const key = requireSessionKey(p.key, respond);
|
||||||
if (!key) {
|
if (!key) {
|
||||||
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "key required"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,9 +403,7 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
|||||||
? Math.max(1, Math.floor(p.maxLines))
|
? Math.max(1, Math.floor(p.maxLines))
|
||||||
: 400;
|
: 400;
|
||||||
|
|
||||||
const cfg = loadConfig();
|
const { cfg, target, storePath } = resolveGatewaySessionTargetFromKey(key);
|
||||||
const target = resolveGatewaySessionStoreTarget({ cfg, key });
|
|
||||||
const storePath = target.storePath;
|
|
||||||
// Lock + read in a short critical section; transcript work happens outside.
|
// Lock + read in a short critical section; transcript work happens outside.
|
||||||
const compactTarget = await updateSessionStore(storePath, (store) => {
|
const compactTarget = await updateSessionStore(storePath, (store) => {
|
||||||
const { entry, primaryKey } = migrateAndPruneSessionStoreKey({ cfg, key, store });
|
const { entry, primaryKey } = migrateAndPruneSessionStoreKey({ cfg, key, store });
|
||||||
|
|||||||
27
src/gateway/server-methods/validation.ts
Normal file
27
src/gateway/server-methods/validation.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type { ErrorObject } from "ajv";
|
||||||
|
import type { RespondFn } from "./types.js";
|
||||||
|
import { ErrorCodes, errorShape, formatValidationErrors } from "../protocol/index.js";
|
||||||
|
|
||||||
|
export type Validator<T> = ((params: unknown) => params is T) & {
|
||||||
|
errors?: ErrorObject[] | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function assertValidParams<T>(
|
||||||
|
params: unknown,
|
||||||
|
validate: Validator<T>,
|
||||||
|
method: string,
|
||||||
|
respond: RespondFn,
|
||||||
|
): params is T {
|
||||||
|
if (validate(params)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
respond(
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
errorShape(
|
||||||
|
ErrorCodes.INVALID_REQUEST,
|
||||||
|
`invalid ${method} params: ${formatValidationErrors(validate.errors)}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { ErrorObject } from "ajv";
|
|
||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
import type { GatewayRequestHandlers, RespondFn } from "./types.js";
|
import type { GatewayRequestHandlers, RespondFn } from "./types.js";
|
||||||
import { defaultRuntime } from "../../runtime.js";
|
import { defaultRuntime } from "../../runtime.js";
|
||||||
@@ -6,37 +5,13 @@ import { WizardSession } from "../../wizard/session.js";
|
|||||||
import {
|
import {
|
||||||
ErrorCodes,
|
ErrorCodes,
|
||||||
errorShape,
|
errorShape,
|
||||||
formatValidationErrors,
|
|
||||||
validateWizardCancelParams,
|
validateWizardCancelParams,
|
||||||
validateWizardNextParams,
|
validateWizardNextParams,
|
||||||
validateWizardStartParams,
|
validateWizardStartParams,
|
||||||
validateWizardStatusParams,
|
validateWizardStatusParams,
|
||||||
} from "../protocol/index.js";
|
} from "../protocol/index.js";
|
||||||
import { formatForLog } from "../ws-log.js";
|
import { formatForLog } from "../ws-log.js";
|
||||||
|
import { assertValidParams } from "./validation.js";
|
||||||
type Validator<T> = ((params: unknown) => params is T) & {
|
|
||||||
errors?: ErrorObject[] | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
function assertValidParams<T>(
|
|
||||||
params: unknown,
|
|
||||||
validate: Validator<T>,
|
|
||||||
method: string,
|
|
||||||
respond: RespondFn,
|
|
||||||
): params is T {
|
|
||||||
if (validate(params)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
respond(
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
errorShape(
|
|
||||||
ErrorCodes.INVALID_REQUEST,
|
|
||||||
`invalid ${method} params: ${formatValidationErrors(validate.errors)}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readWizardStatus(session: WizardSession) {
|
function readWizardStatus(session: WizardSession) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user