mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
refactor: dedupe agent trimmed readers
This commit is contained in:
@@ -36,6 +36,7 @@ import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||
import { applyVerboseOverride } from "../sessions/level-overrides.js";
|
||||
import { applyModelOverrideToSessionEntry } from "../sessions/model-overrides.js";
|
||||
import { resolveSendPolicy } from "../sessions/send-policy.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import { sanitizeForLog } from "../terminal/ansi.js";
|
||||
import { resolveMessageChannel } from "../utils/message-channel.js";
|
||||
import {
|
||||
@@ -252,7 +253,7 @@ async function prepareAgentCommandExecution(
|
||||
throw new Error('Invalid verbose level. Use "on", "full", or "off".');
|
||||
}
|
||||
|
||||
const laneRaw = typeof opts.lane === "string" ? opts.lane.trim() : "";
|
||||
const laneRaw = normalizeOptionalString(opts.lane) ?? "";
|
||||
const isSubagentLane = laneRaw === String(AGENT_LANE_SUBAGENT);
|
||||
const timeoutSecondsRaw =
|
||||
opts.timeout !== undefined
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import fs from "node:fs";
|
||||
import { loadJsonFile, saveJsonFile } from "../../infra/json-file.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import { AUTH_STORE_VERSION } from "./constants.js";
|
||||
import { resolveAuthStatePath } from "./paths.js";
|
||||
import type { AuthProfileState, AuthProfileStateStore, ProfileUsageStats } from "./types.js";
|
||||
@@ -13,9 +14,7 @@ function normalizeAuthProfileOrder(raw: unknown): AuthProfileState["order"] {
|
||||
if (!Array.isArray(value)) {
|
||||
return acc;
|
||||
}
|
||||
const list = value
|
||||
.map((entry) => (typeof entry === "string" ? entry.trim() : ""))
|
||||
.filter(Boolean);
|
||||
const list = value.map((entry) => normalizeOptionalString(entry) ?? "").filter(Boolean);
|
||||
if (list.length > 0) {
|
||||
acc[provider] = list;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { AgentContextInjection } from "../config/types.agent-defaults.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import { resolveAgentConfig, resolveSessionAgentIds } from "./agent-scope.js";
|
||||
import { getOrLoadBootstrapFiles } from "./bootstrap-cache.js";
|
||||
import { applyBootstrapHookOverrides } from "./bootstrap-hooks.js";
|
||||
@@ -115,7 +116,7 @@ function sanitizeBootstrapFiles(
|
||||
): WorkspaceBootstrapFile[] {
|
||||
const sanitized: WorkspaceBootstrapFile[] = [];
|
||||
for (const file of files) {
|
||||
const pathValue = typeof file.path === "string" ? file.path.trim() : "";
|
||||
const pathValue = normalizeOptionalString(file.path) ?? "";
|
||||
if (!pathValue) {
|
||||
warn?.(
|
||||
`skipping bootstrap file "${file.name}" — missing or invalid "path" field (hook may have used "filePath" instead)`,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
|
||||
import type { FetchLike, Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
||||
import { loadUndiciRuntimeDeps } from "../infra/net/undici-runtime.js";
|
||||
import { logDebug } from "../logger.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import { resolveMcpTransportConfig } from "./mcp-transport-config.js";
|
||||
|
||||
export type ResolvedMcpTransport = {
|
||||
@@ -23,7 +24,8 @@ function attachStderrLogging(serverName: string, transport: StdioClientTransport
|
||||
return undefined;
|
||||
}
|
||||
const onData = (chunk: Buffer | string) => {
|
||||
const message = String(chunk).trim();
|
||||
const message =
|
||||
normalizeOptionalString(typeof chunk === "string" ? chunk : String(chunk)) ?? "";
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import { isNonSecretApiKeyMarker } from "./model-auth-markers.js";
|
||||
import type { ProviderConfig } from "./models-config.providers.secrets.js";
|
||||
|
||||
@@ -30,7 +31,7 @@ function getProviderModelId(model: unknown): string {
|
||||
return "";
|
||||
}
|
||||
const id = (model as { id?: unknown }).id;
|
||||
return typeof id === "string" ? id.trim() : "";
|
||||
return normalizeOptionalString(id) ?? "";
|
||||
}
|
||||
|
||||
export function mergeProviderModels(
|
||||
@@ -136,7 +137,7 @@ export function mergeProviders(params: {
|
||||
}): Record<string, ProviderConfig> {
|
||||
const out: Record<string, ProviderConfig> = params.implicit ? { ...params.implicit } : {};
|
||||
for (const [key, explicit] of Object.entries(params.explicit ?? {})) {
|
||||
const providerKey = key.trim();
|
||||
const providerKey = normalizeOptionalString(key) ?? "";
|
||||
if (!providerKey) {
|
||||
continue;
|
||||
}
|
||||
@@ -147,11 +148,7 @@ export function mergeProviders(params: {
|
||||
}
|
||||
|
||||
function resolveProviderApi(entry: { api?: unknown } | undefined): string | undefined {
|
||||
if (typeof entry?.api !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const api = entry.api.trim();
|
||||
return api || undefined;
|
||||
return normalizeOptionalString(entry?.api);
|
||||
}
|
||||
|
||||
function resolveModelApiSurface(entry: { models?: unknown } | undefined): string | undefined {
|
||||
@@ -165,7 +162,8 @@ function resolveModelApiSurface(entry: { models?: unknown } | undefined): string
|
||||
return [];
|
||||
}
|
||||
const api = (model as { api?: unknown }).api;
|
||||
return typeof api === "string" && api.trim() ? [api.trim()] : [];
|
||||
const normalized = normalizeOptionalString(api);
|
||||
return normalized ? [normalized] : [];
|
||||
})
|
||||
.toSorted();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { MODEL_APIS } from "../config/types.models.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import type { ProviderConfig } from "./models-config.providers.secrets.js";
|
||||
|
||||
const GENERIC_PROVIDER_APIS = new Set<string>([
|
||||
@@ -12,7 +13,7 @@ export function resolveProviderPluginLookupKey(
|
||||
providerKey: string,
|
||||
provider?: ProviderConfig,
|
||||
): string {
|
||||
const api = typeof provider?.api === "string" ? provider.api.trim() : "";
|
||||
const api = normalizeOptionalString(provider?.api) ?? "";
|
||||
if (
|
||||
api &&
|
||||
MODEL_APIS.includes(api as (typeof MODEL_APIS)[number]) &&
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import { truncateUtf16Safe } from "../../utils.js";
|
||||
import type { WorkspaceBootstrapFile } from "../workspace.js";
|
||||
import type { EmbeddedContextFile } from "./types.js";
|
||||
@@ -210,7 +211,7 @@ export function buildBootstrapContextFiles(
|
||||
if (remainingTotalChars <= 0) {
|
||||
break;
|
||||
}
|
||||
const pathValue = typeof file.path === "string" ? file.path.trim() : "";
|
||||
const pathValue = normalizeOptionalString(file.path) ?? "";
|
||||
if (!pathValue) {
|
||||
opts?.warn?.(
|
||||
`skipping bootstrap file "${file.name}" — missing or invalid "path" field (hook may have used "filePath" instead)`,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { StreamFn } from "@mariozechner/pi-agent-core";
|
||||
import { streamSimple } from "@mariozechner/pi-ai";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
type AnthropicToolSchemaMode = "openai-functions";
|
||||
type AnthropicToolChoiceMode = "openai-string-modes";
|
||||
|
||||
@@ -70,7 +71,7 @@ function normalizeOpenAiFunctionAnthropicToolDefinition(
|
||||
return toolObj;
|
||||
}
|
||||
|
||||
const rawName = typeof toolObj.name === "string" ? toolObj.name.trim() : "";
|
||||
const rawName = normalizeOptionalString(toolObj.name) ?? "";
|
||||
if (!rawName) {
|
||||
return toolObj;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { StreamFn } from "@mariozechner/pi-agent-core";
|
||||
import type { Api, Model } from "@mariozechner/pi-ai";
|
||||
import { parseGeminiAuth } from "../../infra/gemini-auth.js";
|
||||
import { normalizeGoogleApiBaseUrl } from "../../infra/google-api-base-url.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import { buildGuardedModelFetch } from "../provider-transport-fetch.js";
|
||||
import { stableStringify } from "../stable-stringify.js";
|
||||
import { stripSystemPromptCacheBoundary } from "../system-prompt-cache-boundary.js";
|
||||
@@ -226,7 +227,7 @@ async function createGooglePromptCache(params: {
|
||||
return null;
|
||||
}
|
||||
const json = (await response.json()) as { name?: string; expireTime?: string };
|
||||
const cachedContent = typeof json.name === "string" ? json.name.trim() : "";
|
||||
const cachedContent = normalizeOptionalString(json.name) ?? "";
|
||||
return cachedContent ? { cachedContent, expireTime: json.expireTime } : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import { resolveToolCallArgumentsEncoding } from "../../../plugins/provider-mode
|
||||
import { resolveProviderSystemPromptContribution } from "../../../plugins/provider-runtime.js";
|
||||
import { isSubagentSessionKey } from "../../../routing/session-key.js";
|
||||
import { normalizeOptionalLowercaseString } from "../../../shared/string-coerce.js";
|
||||
import { normalizeOptionalString } from "../../../shared/string-coerce.js";
|
||||
import { buildTtsSystemPromptHint } from "../../../tts/tts.js";
|
||||
import { resolveUserPath } from "../../../utils.js";
|
||||
import { normalizeMessageChannel } from "../../../utils/message-channel.js";
|
||||
@@ -1654,8 +1655,7 @@ export async function runEmbeddedAttempt(
|
||||
`hooks: prepended context to prompt (${hookResult.prependContext.length} chars)`,
|
||||
);
|
||||
}
|
||||
const legacySystemPrompt =
|
||||
typeof hookResult?.systemPrompt === "string" ? hookResult.systemPrompt.trim() : "";
|
||||
const legacySystemPrompt = normalizeOptionalString(hookResult?.systemPrompt) ?? "";
|
||||
if (legacySystemPrompt) {
|
||||
applySystemPromptOverrideToSession(activeSession, legacySystemPrompt);
|
||||
systemPromptText = legacySystemPrompt;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { emitAgentEvent } from "../infra/agent-events.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import type { InlineCodeState } from "../markdown/code-spans.js";
|
||||
import { buildCodeSpanIndex, createInlineCodeState } from "../markdown/code-spans.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import { EmbeddedBlockChunker } from "./pi-embedded-block-chunker.js";
|
||||
import {
|
||||
isMessagingToolDuplicateNormalized,
|
||||
@@ -43,7 +44,7 @@ function collectPendingMediaFromInternalEvents(
|
||||
continue;
|
||||
}
|
||||
for (const mediaUrl of event.mediaUrls) {
|
||||
const normalized = typeof mediaUrl === "string" ? mediaUrl.trim() : "";
|
||||
const normalized = normalizeOptionalString(mediaUrl) ?? "";
|
||||
if (!normalized || seen.has(normalized)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import type { SkillsInstallPreferences } from "./skills/types.js";
|
||||
|
||||
export {
|
||||
@@ -38,8 +41,7 @@ export { buildWorkspaceSkillCommandSpecs } from "./skills/command-specs.js";
|
||||
export function resolveSkillsInstallPreferences(config?: OpenClawConfig): SkillsInstallPreferences {
|
||||
const raw = config?.skills?.install;
|
||||
const preferBrew = raw?.preferBrew ?? true;
|
||||
const managerRaw = typeof raw?.nodeManager === "string" ? raw.nodeManager.trim() : "";
|
||||
const manager = normalizeLowercaseStringOrEmpty(managerRaw);
|
||||
const manager = normalizeLowercaseStringOrEmpty(normalizeOptionalString(raw?.nodeManager));
|
||||
const nodeManager: SkillsInstallPreferences["nodeManager"] =
|
||||
manager === "pnpm" || manager === "yarn" || manager === "bun" || manager === "npm"
|
||||
? manager
|
||||
|
||||
@@ -3,6 +3,7 @@ import path from "node:path";
|
||||
import chokidar, { type FSWatcher } from "chokidar";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import { CONFIG_DIR, resolveUserPath } from "../../utils.js";
|
||||
import { resolvePluginSkillDirs } from "./plugin-skills.js";
|
||||
import {
|
||||
@@ -62,7 +63,7 @@ function resolveWatchPaths(workspaceDir: string, config?: OpenClawConfig): strin
|
||||
paths.push(path.join(os.homedir(), ".agents", "skills"));
|
||||
const extraDirsRaw = config?.skills?.load?.extraDirs ?? [];
|
||||
const extraDirs = extraDirsRaw
|
||||
.map((d) => (typeof d === "string" ? d.trim() : ""))
|
||||
.map((d) => normalizeOptionalString(d) ?? "")
|
||||
.filter(Boolean)
|
||||
.map((dir) => resolveUserPath(dir));
|
||||
paths.push(...extraDirs);
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from "node:path";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { isPathInside } from "../../infra/path-guards.js";
|
||||
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import { CONFIG_DIR, resolveUserPath } from "../../utils.js";
|
||||
import { resolveSandboxPath } from "../sandbox-paths.js";
|
||||
import { resolveEffectiveAgentSkillFilter } from "./agent-filter.js";
|
||||
@@ -410,9 +411,7 @@ function loadSkillEntries(
|
||||
const workspaceSkillsDir = path.resolve(workspaceDir, "skills");
|
||||
const bundledSkillsDir = opts?.bundledSkillsDir ?? resolveBundledSkillsDir();
|
||||
const extraDirsRaw = opts?.config?.skills?.load?.extraDirs ?? [];
|
||||
const extraDirs = extraDirsRaw
|
||||
.map((d) => (typeof d === "string" ? d.trim() : ""))
|
||||
.filter(Boolean);
|
||||
const extraDirs = extraDirsRaw.map((d) => normalizeOptionalString(d) ?? "").filter(Boolean);
|
||||
const pluginSkillDirs = resolvePluginSkillDirs({
|
||||
workspaceDir,
|
||||
config: opts?.config,
|
||||
|
||||
@@ -282,7 +282,7 @@ async function wakeSubagentRunAfterDescendants(params: {
|
||||
timeoutMs: announceTimeoutMs,
|
||||
}),
|
||||
});
|
||||
wakeRunId = typeof wakeResponse?.runId === "string" ? wakeResponse.runId.trim() : "";
|
||||
wakeRunId = normalizeOptionalString(wakeResponse?.runId) ?? "";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import crypto from "node:crypto";
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import { resolveAgentWorkspaceDir } from "./agent-scope.js";
|
||||
|
||||
export function decodeStrictBase64(value: string, maxDecodedBytes: number): Buffer | null {
|
||||
@@ -137,9 +138,9 @@ export async function materializeSubagentAttachments(params: {
|
||||
let totalBytes = 0;
|
||||
|
||||
for (const raw of requestedAttachments) {
|
||||
const name = typeof raw?.name === "string" ? raw.name.trim() : "";
|
||||
const name = normalizeOptionalString(raw?.name) ?? "";
|
||||
const contentVal = typeof raw?.content === "string" ? raw.content : "";
|
||||
const encodingRaw = typeof raw?.encoding === "string" ? raw.encoding.trim() : "utf8";
|
||||
const encodingRaw = normalizeOptionalString(raw?.encoding) ?? "utf8";
|
||||
const encoding = encodingRaw === "base64" ? "base64" : "utf8";
|
||||
|
||||
if (!name) {
|
||||
|
||||
@@ -6,7 +6,10 @@ import {
|
||||
type OperatorScope,
|
||||
} from "../../gateway/method-scopes.js";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "../../shared/string-coerce.js";
|
||||
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../../utils/message-channel.js";
|
||||
import { readStringParam } from "./common.js";
|
||||
|
||||
@@ -74,8 +77,7 @@ function validateGatewayUrlOverrideForAgentTools(params: {
|
||||
]);
|
||||
|
||||
let remoteKey: string | undefined;
|
||||
const remoteUrl =
|
||||
typeof cfg.gateway?.remote?.url === "string" ? cfg.gateway.remote.url.trim() : "";
|
||||
const remoteUrl = normalizeOptionalString(cfg.gateway?.remote?.url) ?? "";
|
||||
if (remoteUrl) {
|
||||
try {
|
||||
const remote = canonicalizeToolGatewayWsUrl(remoteUrl);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { isSubagentSessionKey, resolveAgentIdFromSessionKey } from "../../routing/session-key.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "../../shared/string-coerce.js";
|
||||
import {
|
||||
listSpawnedSessionKeys,
|
||||
resolveInternalSessionKey,
|
||||
@@ -64,14 +67,14 @@ export function resolveSandboxedSessionToolContext(params: {
|
||||
} {
|
||||
const { mainKey, alias } = resolveMainSessionAlias(params.cfg);
|
||||
const visibility = resolveSandboxSessionToolsVisibility(params.cfg);
|
||||
const requesterInternalKey =
|
||||
typeof params.agentSessionKey === "string" && params.agentSessionKey.trim()
|
||||
? resolveInternalSessionKey({
|
||||
key: params.agentSessionKey,
|
||||
alias,
|
||||
mainKey,
|
||||
})
|
||||
: undefined;
|
||||
const requesterSessionKey = normalizeOptionalString(params.agentSessionKey);
|
||||
const requesterInternalKey = requesterSessionKey
|
||||
? resolveInternalSessionKey({
|
||||
key: requesterSessionKey,
|
||||
alias,
|
||||
mainKey,
|
||||
})
|
||||
: undefined;
|
||||
const effectiveRequesterKey = requesterInternalKey ?? alias;
|
||||
const restrictToSpawned =
|
||||
params.sandboxed === true &&
|
||||
@@ -97,7 +100,9 @@ export function createAgentToAgentPolicy(cfg: OpenClawConfig): AgentToAgentPolic
|
||||
return true;
|
||||
}
|
||||
return allowPatterns.some((pattern) => {
|
||||
const raw = String(pattern ?? "").trim();
|
||||
const raw =
|
||||
normalizeOptionalString(typeof pattern === "string" ? pattern : String(pattern ?? "")) ??
|
||||
"";
|
||||
if (!raw) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ export function createSessionsSendTool(opts?: {
|
||||
params: resolveParams,
|
||||
timeoutMs: 10_000,
|
||||
});
|
||||
resolvedKey = typeof resolved?.key === "string" ? resolved.key.trim() : "";
|
||||
resolvedKey = normalizeOptionalString(resolved?.key) ?? "";
|
||||
} catch (err) {
|
||||
const msg = formatErrorMessage(err);
|
||||
if (restrictToSpawned) {
|
||||
|
||||
@@ -155,7 +155,7 @@ export function createSessionsSpawnTool(
|
||||
);
|
||||
}
|
||||
const task = readStringParam(params, "task", { required: true });
|
||||
const label = typeof params.label === "string" ? params.label.trim() : "";
|
||||
const label = readStringParam(params, "label") ?? "";
|
||||
const runtime = params.runtime === "acp" ? "acp" : "subagent";
|
||||
const requestedAgentId = readStringParam(params, "agentId");
|
||||
const resumeSessionId = readStringParam(params, "resumeSessionId");
|
||||
|
||||
Reference in New Issue
Block a user