mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
refactor: dedupe gateway trimmed readers
This commit is contained in:
@@ -110,7 +110,9 @@ type TailscaleUser = {
|
||||
type TailscaleWhoisLookup = (ip: string) => Promise<TailscaleWhoisIdentity | null>;
|
||||
|
||||
function hasExplicitSharedSecretAuth(connectAuth?: ConnectAuth | null): boolean {
|
||||
return Boolean(connectAuth?.token?.trim() || connectAuth?.password?.trim());
|
||||
return Boolean(
|
||||
normalizeOptionalString(connectAuth?.token) || normalizeOptionalString(connectAuth?.password),
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeLogin(login: string): string {
|
||||
|
||||
@@ -13,7 +13,10 @@ import type {
|
||||
MemoryEmbeddingProvider,
|
||||
MemoryEmbeddingProviderAdapter,
|
||||
} from "../plugins/memory-embedding-providers.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import type { AuthRateLimiter } from "./auth-rate-limit.js";
|
||||
import type { ResolvedGatewayAuth } from "./auth.js";
|
||||
import { sendJson } from "./http-common.js";
|
||||
@@ -227,7 +230,7 @@ export async function handleOpenAiEmbeddingsHttpRequest(
|
||||
}
|
||||
|
||||
const payload = coerceRequest(handled.body);
|
||||
const requestModel = typeof payload.model === "string" ? payload.model.trim() : "";
|
||||
const requestModel = normalizeOptionalString(payload.model) ?? "";
|
||||
if (!requestModel) {
|
||||
sendJson(res, 400, {
|
||||
error: { message: "Missing `model`.", type: "invalid_request_error" },
|
||||
@@ -268,7 +271,10 @@ export async function handleOpenAiEmbeddingsHttpRequest(
|
||||
const agentDir = resolveAgentDir(cfg, agentId);
|
||||
const memorySearch = resolveMemorySearchConfig(cfg, agentId);
|
||||
const configuredProvider = memorySearch?.provider ?? "openai";
|
||||
const overrideModel = getHeader(req, "x-openclaw-model")?.trim() || memorySearch?.model || "";
|
||||
const overrideModel =
|
||||
normalizeOptionalString(getHeader(req, "x-openclaw-model")) ||
|
||||
normalizeOptionalString(memorySearch?.model) ||
|
||||
"";
|
||||
const target = resolveEmbeddingsTarget({
|
||||
requestModel: overrideModel,
|
||||
configuredProvider,
|
||||
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
} from "../agents/model-selection.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { buildAgentMainSessionKey, normalizeAgentId } from "../routing/session-key.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import { normalizeMessageChannel } from "../utils/message-channel.js";
|
||||
import type { AuthRateLimiter } from "./auth-rate-limit.js";
|
||||
import {
|
||||
@@ -36,12 +39,11 @@ export function getHeader(req: IncomingMessage, name: string): string | undefine
|
||||
}
|
||||
|
||||
export function getBearerToken(req: IncomingMessage): string | undefined {
|
||||
const raw = getHeader(req, "authorization")?.trim() ?? "";
|
||||
const raw = normalizeOptionalString(getHeader(req, "authorization")) ?? "";
|
||||
if (!normalizeLowercaseStringOrEmpty(raw).startsWith("bearer ")) {
|
||||
return undefined;
|
||||
}
|
||||
const token = raw.slice(7).trim();
|
||||
return token || undefined;
|
||||
return normalizeOptionalString(raw.slice(7));
|
||||
}
|
||||
|
||||
type SharedSecretGatewayAuth = Pick<ResolvedGatewayAuth, "mode">;
|
||||
@@ -191,8 +193,8 @@ export function resolveOpenAiCompatibleHttpSenderIsOwner(
|
||||
|
||||
export function resolveAgentIdFromHeader(req: IncomingMessage): string | undefined {
|
||||
const raw =
|
||||
getHeader(req, "x-openclaw-agent-id")?.trim() ||
|
||||
getHeader(req, "x-openclaw-agent")?.trim() ||
|
||||
normalizeOptionalString(getHeader(req, "x-openclaw-agent-id")) ||
|
||||
normalizeOptionalString(getHeader(req, "x-openclaw-agent")) ||
|
||||
"";
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
|
||||
export type GatewayProbeTargetResolution = {
|
||||
gatewayMode: "local" | "remote";
|
||||
@@ -8,8 +9,7 @@ export type GatewayProbeTargetResolution = {
|
||||
|
||||
export function resolveGatewayProbeTarget(cfg: OpenClawConfig): GatewayProbeTargetResolution {
|
||||
const gatewayMode = cfg.gateway?.mode === "remote" ? "remote" : "local";
|
||||
const remoteUrlRaw =
|
||||
typeof cfg.gateway?.remote?.url === "string" ? cfg.gateway.remote.url.trim() : "";
|
||||
const remoteUrlRaw = normalizeOptionalString(cfg.gateway?.remote?.url) ?? "";
|
||||
const remoteUrlMissing = gatewayMode === "remote" && !remoteUrlRaw;
|
||||
return {
|
||||
gatewayMode,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
|
||||
export const ConnectErrorDetailCodes = {
|
||||
AUTH_REQUIRED: "AUTH_REQUIRED",
|
||||
AUTH_UNAUTHORIZED: "AUTH_UNAUTHORIZED",
|
||||
@@ -126,8 +128,7 @@ export function readConnectErrorRecoveryAdvice(details: unknown): ConnectErrorRe
|
||||
};
|
||||
const canRetryWithDeviceToken =
|
||||
typeof raw.canRetryWithDeviceToken === "boolean" ? raw.canRetryWithDeviceToken : undefined;
|
||||
const normalizedNextStep =
|
||||
typeof raw.recommendedNextStep === "string" ? raw.recommendedNextStep.trim() : "";
|
||||
const normalizedNextStep = normalizeOptionalString(raw.recommendedNextStep) ?? "";
|
||||
const recommendedNextStep = CONNECT_RECOVERY_NEXT_STEP_VALUES.has(
|
||||
normalizedNextStep as ConnectRecoveryNextStep,
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ import { stopGmailWatcher } from "../hooks/gmail-watcher.js";
|
||||
import type { HeartbeatRunner } from "../infra/heartbeat-runner.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import type { PluginServicesHandle } from "../plugins/services.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
|
||||
const shutdownLog = createSubsystemLogger("gateway/shutdown");
|
||||
const WEBSOCKET_CLOSE_GRACE_MS = 1_000;
|
||||
@@ -42,7 +43,7 @@ export function createGatewayCloseHandler(params: {
|
||||
}) {
|
||||
return async (opts?: { reason?: string; restartExpectedMs?: number | null }) => {
|
||||
try {
|
||||
const reasonRaw = typeof opts?.reason === "string" ? opts.reason.trim() : "";
|
||||
const reasonRaw = normalizeOptionalString(opts?.reason) ?? "";
|
||||
const reason = reasonRaw || "gateway stopping";
|
||||
const restartExpectedMs =
|
||||
typeof opts?.restartExpectedMs === "number" && Number.isFinite(opts.restartExpectedMs)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { hasApprovalTurnSourceRoute } from "../../infra/approval-turn-source.js";
|
||||
import type { ExecApprovalDecision } from "../../infra/exec-approvals.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import type {
|
||||
ExecApprovalIdLookupResult,
|
||||
ExecApprovalManager,
|
||||
@@ -112,7 +113,7 @@ export async function handleApprovalWaitDecision<TPayload>(params: {
|
||||
inputId: unknown;
|
||||
respond: RespondFn;
|
||||
}): Promise<void> {
|
||||
const id = typeof params.inputId === "string" ? params.inputId.trim() : "";
|
||||
const id = normalizeOptionalString(params.inputId) ?? "";
|
||||
if (!id) {
|
||||
params.respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "id is required"));
|
||||
return;
|
||||
|
||||
@@ -61,11 +61,8 @@ export function respondUnavailableOnNodeInvokeError<T extends { ok: boolean; err
|
||||
res.error && typeof res.error === "object"
|
||||
? (res.error as { code?: unknown; message?: unknown })
|
||||
: null;
|
||||
const nodeCode = typeof nodeError?.code === "string" ? nodeError.code.trim() : "";
|
||||
const nodeMessage =
|
||||
typeof nodeError?.message === "string" && nodeError.message.trim().length > 0
|
||||
? nodeError.message.trim()
|
||||
: "node invoke failed";
|
||||
const nodeCode = normalizeOptionalString(nodeError?.code) ?? "";
|
||||
const nodeMessage = normalizeOptionalString(nodeError?.message) ?? "node invoke failed";
|
||||
const message = nodeCode ? `${nodeCode}: ${nodeMessage}` : nodeMessage;
|
||||
respond(
|
||||
false,
|
||||
|
||||
@@ -198,8 +198,8 @@ function shouldQueueAsPendingForegroundAction(params: {
|
||||
params.error && typeof params.error === "object"
|
||||
? (params.error as { code?: unknown; message?: unknown })
|
||||
: null;
|
||||
const code = typeof error?.code === "string" ? error.code.trim().toUpperCase() : "";
|
||||
const message = typeof error?.message === "string" ? error.message.trim().toUpperCase() : "";
|
||||
const code = normalizeOptionalString(error?.code)?.toUpperCase() ?? "";
|
||||
const message = normalizeOptionalString(error?.message)?.toUpperCase() ?? "";
|
||||
return code === "NODE_BACKGROUND_UNAVAILABLE" || message.includes("BACKGROUND_UNAVAILABLE");
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
sendApnsAlert,
|
||||
shouldClearStoredApnsRegistration,
|
||||
} from "../../infra/push-apns.js";
|
||||
import { normalizeStringifiedOptionalString } from "../../shared/string-coerce.js";
|
||||
import { ErrorCodes, errorShape, validatePushTestParams } from "../protocol/index.js";
|
||||
import { respondInvalidParams, respondUnavailableOnThrow } from "./nodes.helpers.js";
|
||||
import { normalizeTrimmedString } from "./record-shared.js";
|
||||
@@ -24,7 +25,7 @@ export const pushHandlers: GatewayRequestHandlers = {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeId = String(params.nodeId ?? "").trim();
|
||||
const nodeId = normalizeStringifiedOptionalString(params.nodeId) ?? "";
|
||||
if (!nodeId) {
|
||||
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "nodeId required"));
|
||||
return;
|
||||
|
||||
@@ -7,7 +7,11 @@ import { getLastHeartbeatEvent } from "../../infra/heartbeat-events.js";
|
||||
import { setHeartbeatsEnabled } from "../../infra/heartbeat-runner.js";
|
||||
import { enqueueSystemEvent, isSystemEventContextChanged } from "../../infra/system-events.js";
|
||||
import { listSystemPresence, updateSystemPresence } from "../../infra/system-presence.js";
|
||||
import { normalizeLowercaseStringOrEmpty, readStringValue } from "../../shared/string-coerce.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
readStringValue,
|
||||
} from "../../shared/string-coerce.js";
|
||||
import { ErrorCodes, errorShape } from "../protocol/index.js";
|
||||
import { broadcastPresenceSnapshot } from "../server/presence-events.js";
|
||||
import type { GatewayRequestHandlers } from "./types.js";
|
||||
@@ -48,7 +52,7 @@ export const systemHandlers: GatewayRequestHandlers = {
|
||||
respond(true, presence, undefined);
|
||||
},
|
||||
"system-event": ({ params, respond, context }) => {
|
||||
const text = typeof params.text === "string" ? params.text.trim() : "";
|
||||
const text = normalizeOptionalString(params.text) ?? "";
|
||||
if (!text) {
|
||||
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "text required"));
|
||||
return;
|
||||
@@ -115,18 +119,18 @@ export const systemHandlers: GatewayRequestHandlers = {
|
||||
const contextChanged = isSystemEventContextChanged(sessionKey, presenceUpdate.key);
|
||||
const parts: string[] = [];
|
||||
if (contextChanged || hostChanged || ipChanged) {
|
||||
const hostLabel = next.host?.trim() || "Unknown";
|
||||
const ipLabel = next.ip?.trim();
|
||||
const hostLabel = normalizeOptionalString(next.host) ?? "Unknown";
|
||||
const ipLabel = normalizeOptionalString(next.ip);
|
||||
parts.push(`Node: ${hostLabel}${ipLabel ? ` (${ipLabel})` : ""}`);
|
||||
}
|
||||
if (versionChanged) {
|
||||
parts.push(`app ${next.version?.trim() || "unknown"}`);
|
||||
parts.push(`app ${normalizeOptionalString(next.version) ?? "unknown"}`);
|
||||
}
|
||||
if (modeChanged) {
|
||||
parts.push(`mode ${next.mode?.trim() || "unknown"}`);
|
||||
parts.push(`mode ${normalizeOptionalString(next.mode) ?? "unknown"}`);
|
||||
}
|
||||
if (reasonChanged) {
|
||||
parts.push(`reason ${reasonValue?.trim() || "event"}`);
|
||||
parts.push(`reason ${normalizeOptionalString(reasonValue) ?? "event"}`);
|
||||
}
|
||||
const deltaText = parts.join(" · ");
|
||||
if (deltaText) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { summarizeToolDescriptionText } from "../../agents/tool-description-summary.js";
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import { getPluginToolMeta, resolvePluginTools } from "../../plugins/tools.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import {
|
||||
ErrorCodes,
|
||||
errorShape,
|
||||
@@ -42,7 +43,7 @@ type ToolCatalogGroup = {
|
||||
function resolveAgentIdOrRespondError(rawAgentId: unknown, respond: RespondFn) {
|
||||
const cfg = loadConfig();
|
||||
const knownAgents = listAgentIds(cfg);
|
||||
const requestedAgentId = typeof rawAgentId === "string" ? rawAgentId.trim() : "";
|
||||
const requestedAgentId = normalizeOptionalString(rawAgentId) ?? "";
|
||||
const agentId = requestedAgentId || resolveDefaultAgentId(cfg);
|
||||
if (requestedAgentId && !knownAgents.includes(agentId)) {
|
||||
respond(
|
||||
@@ -105,7 +106,7 @@ function buildPluginGroups(params: {
|
||||
} as ToolCatalogGroup);
|
||||
existing.tools.push({
|
||||
id: tool.name,
|
||||
label: typeof tool.label === "string" && tool.label.trim() ? tool.label.trim() : tool.name,
|
||||
label: normalizeOptionalString(tool.label) ?? tool.name,
|
||||
description: summarizeToolDescriptionText({
|
||||
rawDescription: typeof tool.description === "string" ? tool.description : undefined,
|
||||
displaySummary: tool.displaySummary,
|
||||
@@ -130,7 +131,7 @@ export function buildToolsCatalogResult(params: {
|
||||
agentId?: string;
|
||||
includePlugins?: boolean;
|
||||
}): ToolsCatalogResult {
|
||||
const agentId = params.agentId?.trim() || resolveDefaultAgentId(params.cfg);
|
||||
const agentId = normalizeOptionalString(params.agentId) || resolveDefaultAgentId(params.cfg);
|
||||
const includePlugins = params.includePlugins !== false;
|
||||
const groups = buildCoreGroups();
|
||||
if (includePlugins) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from "../../infra/session-cost-usage.js";
|
||||
import { parseAgentSessionKey } from "../../routing/session-key.js";
|
||||
import { resolvePreferredSessionKeyForSessionIdMatches } from "../../sessions/session-id-resolution.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import {
|
||||
buildUsageAggregateTail,
|
||||
mergeUsageDailyLatency,
|
||||
@@ -404,7 +405,7 @@ export const usageHandlers: GatewayRequestHandlers = {
|
||||
});
|
||||
const limit = typeof p.limit === "number" && Number.isFinite(p.limit) ? p.limit : 50;
|
||||
const includeContextWeight = p.includeContextWeight ?? false;
|
||||
const specificKey = typeof p.key === "string" ? p.key.trim() : null;
|
||||
const specificKey = normalizeOptionalString(p.key) ?? null;
|
||||
|
||||
// Load session store for named sessions
|
||||
const { storePath, store } = loadCombinedSessionStoreForGateway(config);
|
||||
@@ -824,7 +825,7 @@ export const usageHandlers: GatewayRequestHandlers = {
|
||||
respond(true, result, undefined);
|
||||
},
|
||||
"sessions.usage.timeseries": async ({ respond, params }) => {
|
||||
const key = typeof params?.key === "string" ? params.key.trim() : null;
|
||||
const key = normalizeOptionalString(params?.key) ?? null;
|
||||
if (!key) {
|
||||
respond(
|
||||
false,
|
||||
@@ -861,7 +862,7 @@ export const usageHandlers: GatewayRequestHandlers = {
|
||||
respond(true, timeseries, undefined);
|
||||
},
|
||||
"sessions.usage.logs": async ({ respond, params }) => {
|
||||
const key = typeof params?.key === "string" ? params.key.trim() : null;
|
||||
const key = normalizeOptionalString(params?.key) ?? null;
|
||||
if (!key) {
|
||||
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "key is required for logs"));
|
||||
return;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { defaultVoiceWakeTriggers } from "../infra/voicewake.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
|
||||
export function normalizeVoiceWakeTriggers(input: unknown): string[] {
|
||||
const raw = Array.isArray(input) ? input : [];
|
||||
const cleaned = raw
|
||||
.map((v) => (typeof v === "string" ? v.trim() : ""))
|
||||
.filter((v) => v.length > 0)
|
||||
.map((v) => normalizeOptionalString(v))
|
||||
.filter((v): v is string => v !== undefined)
|
||||
.slice(0, 32)
|
||||
.map((v) => v.slice(0, 64));
|
||||
return cleaned.length > 0 ? cleaned : defaultVoiceWakeTriggers();
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
OpenClawConfig,
|
||||
} from "../config/config.js";
|
||||
import { replaceConfigFile } from "../config/config.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import {
|
||||
hasConfiguredGatewayAuthSecretInput,
|
||||
resolveGatewayPasswordSecretRefValue,
|
||||
@@ -244,15 +245,12 @@ export function assertHooksTokenSeparateFromGatewayAuth(params: {
|
||||
if (params.cfg.hooks?.enabled !== true) {
|
||||
return;
|
||||
}
|
||||
const hooksToken =
|
||||
typeof params.cfg.hooks.token === "string" ? params.cfg.hooks.token.trim() : "";
|
||||
const hooksToken = normalizeOptionalString(params.cfg.hooks.token) ?? "";
|
||||
if (!hooksToken) {
|
||||
return;
|
||||
}
|
||||
const gatewayToken =
|
||||
params.auth.mode === "token" && typeof params.auth.token === "string"
|
||||
? params.auth.token.trim()
|
||||
: "";
|
||||
params.auth.mode === "token" ? (normalizeOptionalString(params.auth.token) ?? "") : "";
|
||||
if (!gatewayToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user