mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-03 13:40:34 +00:00
refactor: dedupe acp reader helpers
This commit is contained in:
@@ -3,6 +3,7 @@ import path from "node:path";
|
||||
import { isKnownCoreToolId } from "../agents/tool-catalog.js";
|
||||
import { isMutatingToolCall } from "../agents/tool-mutation.js";
|
||||
import { resolveOwnerOnlyToolApprovalClass } from "../agents/tool-policy.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import { asRecord } from "./record-shared.js";
|
||||
|
||||
const SAFE_SEARCH_TOOL_IDS = new Set(["search", "web_search", "memory_search"]);
|
||||
@@ -41,9 +42,9 @@ function readFirstStringValue(
|
||||
return undefined;
|
||||
}
|
||||
for (const key of keys) {
|
||||
const value = source[key];
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
return value.trim();
|
||||
const value = normalizeOptionalString(source[key]);
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
@@ -61,7 +62,7 @@ function parseToolNameFromTitle(title: string | undefined | null): string | unde
|
||||
if (!title) {
|
||||
return undefined;
|
||||
}
|
||||
const head = title.split(":", 1)[0]?.trim();
|
||||
const head = normalizeOptionalString(title.split(":", 1)[0]);
|
||||
return head ? normalizeToolName(head) : undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
listKnownProviderAuthEnvVarNames,
|
||||
omitEnvKeysCaseInsensitive,
|
||||
} from "../secrets/provider-env-vars.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import { sanitizeTerminalText } from "../terminal/safe-text.js";
|
||||
import { classifyAcpToolApproval, type AcpApprovalClass } from "./approval-classifier.js";
|
||||
|
||||
@@ -209,11 +210,11 @@ export function shouldStripProviderAuthEnvVarsForAcpServer(
|
||||
defaultServerArgs?: string[];
|
||||
} = {},
|
||||
): boolean {
|
||||
const serverCommand = params.serverCommand?.trim();
|
||||
const serverCommand = normalizeOptionalString(params.serverCommand);
|
||||
if (!serverCommand) {
|
||||
return true;
|
||||
}
|
||||
const defaultServerCommand = params.defaultServerCommand?.trim();
|
||||
const defaultServerCommand = normalizeOptionalString(params.defaultServerCommand);
|
||||
if (!defaultServerCommand || serverCommand !== defaultServerCommand) {
|
||||
return false;
|
||||
}
|
||||
@@ -282,7 +283,7 @@ function resolveSelfEntryPath(): string | null {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const argv1 = process.argv[1]?.trim();
|
||||
const argv1 = normalizeOptionalString(process.argv[1]);
|
||||
if (argv1) {
|
||||
return path.isAbsolute(argv1) ? argv1 : path.resolve(process.cwd(), argv1);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,11 @@ import type {
|
||||
ToolCallLocation,
|
||||
ToolKind,
|
||||
} from "@agentclientprotocol/sdk";
|
||||
import {
|
||||
hasNonEmptyString,
|
||||
normalizeOptionalString,
|
||||
readStringValue,
|
||||
} from "../shared/string-coerce.js";
|
||||
import { asRecord } from "./record-shared.js";
|
||||
|
||||
export type GatewayAttachment = {
|
||||
@@ -171,7 +176,7 @@ function collectLocationsFromTextMarkers(
|
||||
locations: Map<string, ToolCallLocation>,
|
||||
): void {
|
||||
for (const match of text.matchAll(TOOL_RESULT_PATH_MARKER_RE)) {
|
||||
const candidate = match[1]?.trim();
|
||||
const candidate = normalizeOptionalString(match[1]);
|
||||
if (candidate) {
|
||||
addToolLocation(locations, candidate);
|
||||
}
|
||||
@@ -336,7 +341,7 @@ export function inferToolKind(name?: string): ToolKind {
|
||||
}
|
||||
|
||||
export function extractToolCallContent(value: unknown): ToolCallContent[] | undefined {
|
||||
if (typeof value === "string") {
|
||||
if (hasNonEmptyString(value)) {
|
||||
return value.trim()
|
||||
? [
|
||||
{
|
||||
@@ -359,7 +364,7 @@ export function extractToolCallContent(value: unknown): ToolCallContent[] | unde
|
||||
const blocks = Array.isArray(record.content) ? record.content : [];
|
||||
for (const block of blocks) {
|
||||
const entry = asRecord(block);
|
||||
if (entry?.type === "text" && typeof entry.text === "string" && entry.text.trim()) {
|
||||
if (entry?.type === "text" && hasNonEmptyString(entry.text)) {
|
||||
contents.push({
|
||||
type: "content",
|
||||
content: {
|
||||
@@ -375,15 +380,11 @@ export function extractToolCallContent(value: unknown): ToolCallContent[] | unde
|
||||
}
|
||||
|
||||
const fallbackText =
|
||||
typeof record.text === "string"
|
||||
? record.text
|
||||
: typeof record.message === "string"
|
||||
? record.message
|
||||
: typeof record.error === "string"
|
||||
? record.error
|
||||
: undefined;
|
||||
readStringValue(record.text) ??
|
||||
readStringValue(record.message) ??
|
||||
readStringValue(record.error);
|
||||
|
||||
if (!fallbackText?.trim()) {
|
||||
if (!hasNonEmptyString(fallbackText)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ function sessionMatchesConfiguredBinding(params: {
|
||||
return false;
|
||||
}
|
||||
|
||||
const desiredBackend = params.spec.backend?.trim() || params.cfg.acp?.backend?.trim() || "";
|
||||
const desiredBackend =
|
||||
normalizeText(params.spec.backend) ?? normalizeText(params.cfg.acp?.backend) ?? "";
|
||||
if (desiredBackend) {
|
||||
const currentBackend = (params.meta.backend ?? "").trim();
|
||||
if (!currentBackend || currentBackend !== desiredBackend) {
|
||||
@@ -39,7 +40,7 @@ function sessionMatchesConfiguredBinding(params: {
|
||||
}
|
||||
}
|
||||
|
||||
const desiredCwd = params.spec.cwd?.trim();
|
||||
const desiredCwd = normalizeText(params.spec.cwd);
|
||||
if (desiredCwd !== undefined) {
|
||||
const currentCwd = (params.meta.runtimeOptions?.cwd ?? params.meta.cwd ?? "").trim();
|
||||
if (desiredCwd !== currentCwd) {
|
||||
|
||||
@@ -117,7 +117,7 @@ export function parseConfiguredAcpSessionKey(
|
||||
if (tokens.length !== 5 || tokens[0] !== "acp" || tokens[1] !== "binding") {
|
||||
return null;
|
||||
}
|
||||
const channel = tokens[2]?.trim().toLowerCase();
|
||||
const channel = normalizeText(tokens[2])?.toLowerCase();
|
||||
if (!channel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { expect } from "vitest";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import { toAcpRuntimeError } from "./errors.js";
|
||||
import type { AcpRuntime, AcpRuntimeEvent } from "./types.js";
|
||||
|
||||
@@ -75,7 +76,7 @@ export async function runAcpRuntimeAdapterContract(
|
||||
|
||||
let errorThrown: unknown = null;
|
||||
const errorEvents: AcpRuntimeEvent[] = [];
|
||||
const errorPrompt = params.errorPrompt?.trim();
|
||||
const errorPrompt = normalizeOptionalString(params.errorPrompt);
|
||||
if (errorPrompt) {
|
||||
try {
|
||||
for await (const event of runtime.runTurn({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { resolveGlobalSingleton } from "../../shared/global-singleton.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import { AcpRuntimeError } from "./errors.js";
|
||||
import type { AcpRuntime } from "./types.js";
|
||||
|
||||
@@ -26,7 +27,7 @@ function resolveAcpRuntimeRegistryGlobalState(): AcpRuntimeRegistryGlobalState {
|
||||
const ACP_BACKENDS_BY_ID = resolveAcpRuntimeRegistryGlobalState().backendsById;
|
||||
|
||||
function normalizeBackendId(id: string | undefined): string {
|
||||
return id?.trim().toLowerCase() || "";
|
||||
return normalizeOptionalString(id)?.toLowerCase() || "";
|
||||
}
|
||||
|
||||
function isBackendHealthy(backend: AcpRuntimeBackend): boolean {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { loadConfig } from "../config/config.js";
|
||||
import { resolveGatewayClientBootstrap } from "../gateway/client-bootstrap.js";
|
||||
import { GatewayClient } from "../gateway/client.js";
|
||||
import { isMainModule } from "../infra/is-main.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
|
||||
import { readSecretFromFile } from "./secret-file.js";
|
||||
import { AcpGatewayAgent } from "./translator.js";
|
||||
@@ -192,17 +193,21 @@ function parseArgs(args: string[]): AcpServerOptions {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
if (opts.gatewayToken?.trim() && tokenFile?.trim()) {
|
||||
const gatewayToken = normalizeOptionalString(opts.gatewayToken);
|
||||
const gatewayPassword = normalizeOptionalString(opts.gatewayPassword);
|
||||
const normalizedTokenFile = normalizeOptionalString(tokenFile);
|
||||
const normalizedPasswordFile = normalizeOptionalString(passwordFile);
|
||||
if (gatewayToken && normalizedTokenFile) {
|
||||
throw new Error("Use either --token or --token-file.");
|
||||
}
|
||||
if (opts.gatewayPassword?.trim() && passwordFile?.trim()) {
|
||||
if (gatewayPassword && normalizedPasswordFile) {
|
||||
throw new Error("Use either --password or --password-file.");
|
||||
}
|
||||
if (tokenFile?.trim()) {
|
||||
opts.gatewayToken = readSecretFromFile(tokenFile, "Gateway token");
|
||||
if (normalizedTokenFile) {
|
||||
opts.gatewayToken = readSecretFromFile(normalizedTokenFile, "Gateway token");
|
||||
}
|
||||
if (passwordFile?.trim()) {
|
||||
opts.gatewayPassword = readSecretFromFile(passwordFile, "Gateway password");
|
||||
if (normalizedPasswordFile) {
|
||||
opts.gatewayPassword = readSecretFromFile(normalizedPasswordFile, "Gateway password");
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import type { SessionEntry } from "../config/sessions/types.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
|
||||
export type AcpSessionInteractionMode = "interactive" | "parent-owned-background";
|
||||
|
||||
type SessionInteractionEntry = Pick<SessionEntry, "spawnedBy" | "parentSessionKey" | "acp">;
|
||||
|
||||
function normalizeText(value: string | undefined): string | undefined {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
export function resolveAcpSessionInteractionMode(
|
||||
entry?: SessionInteractionEntry | null,
|
||||
): AcpSessionInteractionMode {
|
||||
@@ -18,7 +14,7 @@ export function resolveAcpSessionInteractionMode(
|
||||
if (entry?.acp?.mode !== "oneshot") {
|
||||
return "interactive";
|
||||
}
|
||||
if (normalizeText(entry.spawnedBy) || normalizeText(entry.parentSessionKey)) {
|
||||
if (normalizeOptionalString(entry.spawnedBy) || normalizeOptionalString(entry.parentSessionKey)) {
|
||||
return "parent-owned-background";
|
||||
}
|
||||
return "interactive";
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
createFixedWindowRateLimiter,
|
||||
type FixedWindowRateLimiter,
|
||||
} from "../infra/fixed-window-rate-limit.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import { shortenHomePath } from "../utils.js";
|
||||
import { getAvailableCommands } from "./commands.js";
|
||||
import {
|
||||
@@ -232,7 +233,7 @@ function buildSessionPresentation(params: {
|
||||
...params.overrides,
|
||||
};
|
||||
const availableLevelIds: string[] = [...listThinkingLevels(row.modelProvider, row.model)];
|
||||
const currentModeId = row.thinkingLevel?.trim() || "adaptive";
|
||||
const currentModeId = normalizeOptionalString(row.thinkingLevel) || "adaptive";
|
||||
if (!availableLevelIds.includes(currentModeId)) {
|
||||
availableLevelIds.push(currentModeId);
|
||||
}
|
||||
@@ -268,14 +269,14 @@ function buildSessionPresentation(params: {
|
||||
name: "Tool verbosity",
|
||||
description:
|
||||
"Controls how much tool progress and output detail OpenClaw keeps enabled for the session.",
|
||||
currentValue: row.verboseLevel?.trim() || "off",
|
||||
currentValue: normalizeOptionalString(row.verboseLevel) || "off",
|
||||
values: ["off", "on", "full"],
|
||||
}),
|
||||
buildSelectConfigOption({
|
||||
id: ACP_REASONING_LEVEL_CONFIG_ID,
|
||||
name: "Reasoning stream",
|
||||
description: "Controls whether reasoning-capable models emit reasoning text for the session.",
|
||||
currentValue: row.reasoningLevel?.trim() || "off",
|
||||
currentValue: normalizeOptionalString(row.reasoningLevel) || "off",
|
||||
values: ["off", "on", "stream"],
|
||||
}),
|
||||
buildSelectConfigOption({
|
||||
@@ -283,14 +284,14 @@ function buildSessionPresentation(params: {
|
||||
name: "Usage detail",
|
||||
description:
|
||||
"Controls how much usage information OpenClaw attaches to responses for the session.",
|
||||
currentValue: row.responseUsage?.trim() || "off",
|
||||
currentValue: normalizeOptionalString(row.responseUsage) || "off",
|
||||
values: ["off", "tokens", "full"],
|
||||
}),
|
||||
buildSelectConfigOption({
|
||||
id: ACP_ELEVATED_LEVEL_CONFIG_ID,
|
||||
name: "Elevated actions",
|
||||
description: "Controls how aggressively the session allows elevated execution behavior.",
|
||||
currentValue: row.elevatedLevel?.trim() || "off",
|
||||
currentValue: normalizeOptionalString(row.elevatedLevel) || "off",
|
||||
values: ["off", "on", "ask", "full"],
|
||||
}),
|
||||
];
|
||||
@@ -350,9 +351,9 @@ function buildSessionMetadata(params: {
|
||||
sessionKey: string;
|
||||
}): SessionMetadata {
|
||||
const title =
|
||||
params.row?.derivedTitle?.trim() ||
|
||||
params.row?.displayName?.trim() ||
|
||||
params.row?.label?.trim() ||
|
||||
normalizeOptionalString(params.row?.derivedTitle) ||
|
||||
normalizeOptionalString(params.row?.displayName) ||
|
||||
normalizeOptionalString(params.row?.label) ||
|
||||
params.sessionKey;
|
||||
const updatedAt =
|
||||
typeof params.row?.updatedAt === "number" && Number.isFinite(params.row.updatedAt)
|
||||
|
||||
Reference in New Issue
Block a user