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