mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
refactor: reuse shared coercion helpers (#86419)
* refactor: share talk event metric extraction * refactor: reuse shared coercion helpers * refactor: reuse shared primitive guards * refactor: reuse shared record guard * refactor: reuse shared primitive helpers * refactor: reuse shared string guards * refactor: reuse shared non-empty string guard * refactor: share plugin primitive coercion helpers * refactor: reuse plugin coercion helpers * refactor: reuse plugin coercion helpers in more plugins * refactor: reuse channel coercion helpers * refactor: reuse monitor coercion helpers * refactor: reuse provider coercion helpers * refactor: reuse core coercion helpers * refactor: reuse runtime coercion helpers * refactor: reuse helper coercion in codex paths * refactor: reuse helper coercion in runtime paths * refactor: reuse codex app-server coercion helpers * refactor: reuse codex record helpers * refactor: reuse migration and qa record helpers * refactor: reuse feishu and core helper guards * refactor: reuse browser and policy coercion helpers * refactor: reuse memory wiki record helper * refactor: share boolean coercion helpers * refactor: reuse finite number coercion * refactor: reuse trimmed string list helpers * refactor: reuse string list normalization * refactor: reuse remaining string list helpers * refactor: reuse string entry normalizer * refactor: share sorted string helpers * refactor: share string list normalization * test: preserve command registry browser imports * refactor: reuse trimmed list helpers * refactor: reuse string dedupe helpers * refactor: reuse local dedupe helpers * refactor: reuse more string dedupe helpers * refactor: reuse command string dedupe helpers * refactor: dedupe memory path lists with helper * refactor: expose string dedupe helpers to plugins * refactor: reuse core string dedupe helpers * refactor: reuse shared unique value helpers * refactor: reuse unique helpers in agent utilities * refactor: reuse unique helpers in config plumbing * refactor: reuse unique helpers in extensions * refactor: reuse unique helpers in core utilities * refactor: reuse unique helpers in qa plugins * refactor: reuse unique helpers in memory plugins * refactor: reuse unique helpers in channel plugins * refactor: reuse unique helpers in core tails * refactor: reuse unique helper in comfy workflow * refactor: reuse unique helpers in test utilities * refactor: expose unique value helper to plugins * refactor: reuse unique helpers for numeric lists * refactor: replace index dedupe filters * refactor: reuse string entry normalization * refactor: reuse string normalization in plugin helpers * refactor: reuse string normalization in extension helpers * refactor: reuse string normalization in channel parsers * refactor: reuse string normalization in memory search * refactor: reuse string normalization in provider parsers * refactor: reuse string normalization in qa helpers * refactor: reuse string normalization in infra parsers * refactor: reuse string normalization in messaging parsers * refactor: reuse string normalization in core parsers * refactor: reuse string normalization in extension parsers * refactor: reuse string normalization in remaining parsers * refactor: reuse string normalization in final parser spots * refactor: reuse string normalization in qa media helpers * refactor: reuse normalization in provider and media lists * refactor: reuse normalization for remaining set filters * refactor: reuse normalization in policy allowlists * refactor: reuse normalization in session and owner lists * refactor: centralize primitive string lists * refactor: reuse lowercase entry helpers * refactor: reuse sorted string helpers * refactor: reuse unique trimmed helpers * refactor: reuse string normalization helpers * refactor: reuse catalog string helpers * refactor: reuse remaining string helpers * refactor: simplify remaining list normalization * refactor: reuse codex auth order normalization * chore: refresh plugin sdk api baseline * fix: make shared string sorting deterministic * chore: refresh plugin sdk api baseline * fix: align host env security ordering
This commit is contained in:
committed by
GitHub
parent
a98660eebd
commit
77d9ac30bb
@@ -154,6 +154,10 @@ Skills own workflows; root owns hard policy and routing.
|
||||
- Inline simple one-use objects/spreads when clearer. Extract only when it removes duplication or hard logic.
|
||||
- Tests prove behavior/regressions, not every internal branch.
|
||||
- For non-trivial refactors, check `git diff --numstat` before closeout. If LOC grew, trim or explain why.
|
||||
- Prefer existing narrow helpers over repeated casts/guards. Add local helpers when 2+ nearby call sites share real boundary logic.
|
||||
- Prefer ctor parameter properties for injected deps/config. Do not ban them for erasable-syntax purity.
|
||||
- Prefer `satisfies` for registries/config maps; derive types from schemas when a runtime schema already exists.
|
||||
- Table-drive repetitive tests when it reduces code and keeps failure names clear.
|
||||
- Dynamic import: no static+dynamic import for same prod module. Use `*.runtime.ts` lazy boundary. After edits: `pnpm build`; check `[INEFFECTIVE_DYNAMIC_IMPORT]`.
|
||||
- Cycles: keep `pnpm check:import-cycles` + architecture/madge green.
|
||||
- Classes: no prototype mixins/mutations. Prefer inheritance/composition. Tests prefer per-instance stubs.
|
||||
|
||||
@@ -6,7 +6,6 @@ import Foundation
|
||||
|
||||
enum HostEnvSecurityPolicy {
|
||||
static let blockedInheritedKeys: Set<String> = [
|
||||
"_JAVA_OPTIONS",
|
||||
"AMQP_URL",
|
||||
"ANSIBLE_CALLBACK_PLUGINS",
|
||||
"ANSIBLE_COLLECTIONS_PATH",
|
||||
@@ -31,12 +30,11 @@ enum HostEnvSecurityPolicy {
|
||||
"AZURE_CLIENT_SECRET",
|
||||
"BASH_ENV",
|
||||
"BROWSER",
|
||||
"BUN_CONFIG_REGISTRY",
|
||||
"BUNDLE_GEMFILE",
|
||||
"BUN_CONFIG_REGISTRY",
|
||||
"BZR_EDITOR",
|
||||
"BZR_PLUGIN_PATH",
|
||||
"BZR_SSH",
|
||||
"C_INCLUDE_PATH",
|
||||
"CARGO_BUILD_RUSTC",
|
||||
"CARGO_BUILD_RUSTC_WRAPPER",
|
||||
"CARGO_HOME",
|
||||
@@ -46,8 +44,8 @@ enum HostEnvSecurityPolicy {
|
||||
"CGO_CFLAGS",
|
||||
"CGO_LDFLAGS",
|
||||
"CLASSPATH",
|
||||
"CMAKE_C_COMPILER",
|
||||
"CMAKE_CXX_COMPILER",
|
||||
"CMAKE_C_COMPILER",
|
||||
"CMAKE_TOOLCHAIN_FILE",
|
||||
"COMPOSER_HOME",
|
||||
"CONFIG_SHELL",
|
||||
@@ -58,6 +56,7 @@ enum HostEnvSecurityPolicy {
|
||||
"CPLUS_INCLUDE_PATH",
|
||||
"CURL_HOME",
|
||||
"CXX",
|
||||
"C_INCLUDE_PATH",
|
||||
"DATABASE_URL",
|
||||
"DENO_DIR",
|
||||
"DOTNET_ADDITIONAL_DEPS",
|
||||
@@ -75,6 +74,8 @@ enum HostEnvSecurityPolicy {
|
||||
"GEM_HOME",
|
||||
"GEM_PATH",
|
||||
"GH_TOKEN",
|
||||
"GITHUB_TOKEN",
|
||||
"GITLAB_TOKEN",
|
||||
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
||||
"GIT_ASKPASS",
|
||||
"GIT_COMMON_DIR",
|
||||
@@ -95,8 +96,6 @@ enum HostEnvSecurityPolicy {
|
||||
"GIT_SSL_NO_VERIFY",
|
||||
"GIT_TEMPLATE_DIR",
|
||||
"GIT_WORK_TREE",
|
||||
"GITHUB_TOKEN",
|
||||
"GITLAB_TOKEN",
|
||||
"GLIBC_TUNABLES",
|
||||
"GOENV",
|
||||
"GOFLAGS",
|
||||
@@ -145,8 +144,8 @@ enum HostEnvSecurityPolicy {
|
||||
"PERL5DBCMD",
|
||||
"PERL5LIB",
|
||||
"PERL5OPT",
|
||||
"PHP_INI_SCAN_DIR",
|
||||
"PHPRC",
|
||||
"PHP_INI_SCAN_DIR",
|
||||
"PIP_CONFIG_FILE",
|
||||
"PIP_EXTRA_INDEX_URL",
|
||||
"PIP_FIND_LINKS",
|
||||
@@ -160,17 +159,17 @@ enum HostEnvSecurityPolicy {
|
||||
"PYTHONPATH",
|
||||
"PYTHONSTARTUP",
|
||||
"PYTHONUSERBASE",
|
||||
"R_ENVIRON",
|
||||
"R_ENVIRON_USER",
|
||||
"R_LIBS_USER",
|
||||
"R_PROFILE",
|
||||
"R_PROFILE_USER",
|
||||
"REDIS_URL",
|
||||
"RUBYLIB",
|
||||
"RUBYOPT",
|
||||
"RUBYSHELL",
|
||||
"RUSTC_WRAPPER",
|
||||
"RUSTFLAGS",
|
||||
"R_ENVIRON",
|
||||
"R_ENVIRON_USER",
|
||||
"R_LIBS_USER",
|
||||
"R_PROFILE",
|
||||
"R_PROFILE_USER",
|
||||
"SBT_OPTS",
|
||||
"SHELL",
|
||||
"SHELLOPTS",
|
||||
@@ -192,7 +191,8 @@ enum HostEnvSecurityPolicy {
|
||||
"VIRTUAL_ENV",
|
||||
"VISUAL",
|
||||
"WGETRC",
|
||||
"YARN_RC_FILENAME"
|
||||
"YARN_RC_FILENAME",
|
||||
"_JAVA_OPTIONS"
|
||||
]
|
||||
|
||||
static let blockedInheritedPrefixes: [String] = [
|
||||
@@ -202,7 +202,6 @@ enum HostEnvSecurityPolicy {
|
||||
]
|
||||
|
||||
static let blockedKeys: Set<String> = [
|
||||
"_JAVA_OPTIONS",
|
||||
"ANT_OPTS",
|
||||
"BASH_ENV",
|
||||
"BROWSER",
|
||||
@@ -213,8 +212,8 @@ enum HostEnvSecurityPolicy {
|
||||
"CARGO_BUILD_RUSTC_WRAPPER",
|
||||
"CATALINA_OPTS",
|
||||
"CC",
|
||||
"CMAKE_C_COMPILER",
|
||||
"CMAKE_CXX_COMPILER",
|
||||
"CMAKE_C_COMPILER",
|
||||
"CMAKE_TOOLCHAIN_FILE",
|
||||
"CONFIG_SHELL",
|
||||
"CONFIG_SITE",
|
||||
@@ -275,14 +274,14 @@ enum HostEnvSecurityPolicy {
|
||||
"PYTHONBREAKPOINT",
|
||||
"PYTHONHOME",
|
||||
"PYTHONPATH",
|
||||
"R_ENVIRON",
|
||||
"R_ENVIRON_USER",
|
||||
"R_PROFILE",
|
||||
"R_PROFILE_USER",
|
||||
"RUBYLIB",
|
||||
"RUBYOPT",
|
||||
"RUBYSHELL",
|
||||
"RUSTC_WRAPPER",
|
||||
"R_ENVIRON",
|
||||
"R_ENVIRON_USER",
|
||||
"R_PROFILE",
|
||||
"R_PROFILE_USER",
|
||||
"SBT_OPTS",
|
||||
"SHELL",
|
||||
"SHELLOPTS",
|
||||
@@ -291,7 +290,8 @@ enum HostEnvSecurityPolicy {
|
||||
"SVN_EDITOR",
|
||||
"SVN_SSH",
|
||||
"VAGRANT_VAGRANTFILE",
|
||||
"VIMINIT"
|
||||
"VIMINIT",
|
||||
"_JAVA_OPTIONS"
|
||||
]
|
||||
|
||||
static let blockedOverrideKeys: Set<String> = [
|
||||
@@ -321,9 +321,8 @@ enum HostEnvSecurityPolicy {
|
||||
"AZURE_AUTH_LOCATION",
|
||||
"AZURE_CLIENT_ID",
|
||||
"AZURE_CLIENT_SECRET",
|
||||
"BUN_CONFIG_REGISTRY",
|
||||
"BUNDLE_GEMFILE",
|
||||
"C_INCLUDE_PATH",
|
||||
"BUN_CONFIG_REGISTRY",
|
||||
"CARGO_BUILD_RUSTC_WRAPPER",
|
||||
"CARGO_HOME",
|
||||
"CFLAGS",
|
||||
@@ -336,6 +335,7 @@ enum HostEnvSecurityPolicy {
|
||||
"CPLUS_INCLUDE_PATH",
|
||||
"CURL_CA_BUNDLE",
|
||||
"CURL_HOME",
|
||||
"C_INCLUDE_PATH",
|
||||
"DATABASE_URL",
|
||||
"DENO_DIR",
|
||||
"DOCKER_CERT_PATH",
|
||||
@@ -347,6 +347,8 @@ enum HostEnvSecurityPolicy {
|
||||
"GEM_HOME",
|
||||
"GEM_PATH",
|
||||
"GH_TOKEN",
|
||||
"GITHUB_TOKEN",
|
||||
"GITLAB_TOKEN",
|
||||
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
||||
"GIT_ASKPASS",
|
||||
"GIT_COMMON_DIR",
|
||||
@@ -362,8 +364,6 @@ enum HostEnvSecurityPolicy {
|
||||
"GIT_SSL_CAPATH",
|
||||
"GIT_SSL_NO_VERIFY",
|
||||
"GIT_WORK_TREE",
|
||||
"GITHUB_TOKEN",
|
||||
"GITLAB_TOKEN",
|
||||
"GOENV",
|
||||
"GOFLAGS",
|
||||
"GONOPROXY",
|
||||
@@ -378,8 +378,8 @@ enum HostEnvSecurityPolicy {
|
||||
"HGRCPATH",
|
||||
"HISTFILE",
|
||||
"HOME",
|
||||
"HTTP_PROXY",
|
||||
"HTTPS_PROXY",
|
||||
"HTTP_PROXY",
|
||||
"KUBECONFIG",
|
||||
"LDFLAGS",
|
||||
"LESSCLOSE",
|
||||
@@ -391,10 +391,10 @@ enum HostEnvSecurityPolicy {
|
||||
"MANPAGER",
|
||||
"MFLAGS",
|
||||
"MONGODB_URI",
|
||||
"NO_PROXY",
|
||||
"NODE_AUTH_TOKEN",
|
||||
"NODE_EXTRA_CA_CERTS",
|
||||
"NODE_TLS_REJECT_UNAUTHORIZED",
|
||||
"NO_PROXY",
|
||||
"NPM_TOKEN",
|
||||
"OBJC_INCLUDE_PATH",
|
||||
"OPENSSL_CONF",
|
||||
@@ -402,8 +402,8 @@ enum HostEnvSecurityPolicy {
|
||||
"PAGER",
|
||||
"PERL5DB",
|
||||
"PERL5DBCMD",
|
||||
"PHP_INI_SCAN_DIR",
|
||||
"PHPRC",
|
||||
"PHP_INI_SCAN_DIR",
|
||||
"PIP_CONFIG_FILE",
|
||||
"PIP_EXTRA_INDEX_URL",
|
||||
"PIP_FIND_LINKS",
|
||||
@@ -413,11 +413,11 @@ enum HostEnvSecurityPolicy {
|
||||
"PROMPT_COMMAND",
|
||||
"PYTHONSTARTUP",
|
||||
"PYTHONUSERBASE",
|
||||
"R_LIBS_USER",
|
||||
"REDIS_URL",
|
||||
"REQUESTS_CA_BUNDLE",
|
||||
"RUSTC_WRAPPER",
|
||||
"RUSTFLAGS",
|
||||
"R_LIBS_USER",
|
||||
"SSH_ASKPASS",
|
||||
"SSH_AUTH_SOCK",
|
||||
"SSL_CERT_DIR",
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
1d3e6177eeac57fc43736f7d5f76d8f825e1859ca625d268e97dc30b5567ea34 plugin-sdk-api-baseline.json
|
||||
6c093ff7c10bd81ee9d2c4fc5d07b206bc3a1f5acd0bad491cfc9e0df6689f6b plugin-sdk-api-baseline.jsonl
|
||||
374f1fec7d6fa8c00865dcb58b68d89ec10e85e81ef536c5746167a83d10bcc7 plugin-sdk-api-baseline.json
|
||||
ffc6a2faf381d1bb118845e010b2798397c3d41fff400f52ee57b6dc197c8af3 plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -19,7 +19,9 @@ import {
|
||||
type AcpRuntimeTurnResult,
|
||||
} from "acpx/runtime";
|
||||
import { redactSensitiveText } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { AcpRuntimeError, type AcpRuntime, type AcpRuntimeErrorCode } from "../runtime-api.js";
|
||||
import { splitCommandParts } from "./command-line.js";
|
||||
import {
|
||||
createAcpxProcessLeaseId,
|
||||
hashAcpxProcessCommand,
|
||||
@@ -32,7 +34,6 @@ import {
|
||||
isOpenClawLeaseAwareAcpxProcessCommand,
|
||||
type AcpxProcessCleanupDeps,
|
||||
} from "./process-reaper.js";
|
||||
import { splitCommandParts } from "./command-line.js";
|
||||
|
||||
type AcpSessionStore = AcpRuntimeOptions["sessionStore"];
|
||||
type AcpSessionRecord = Parameters<AcpSessionStore["save"]>[0];
|
||||
@@ -189,7 +190,7 @@ function selectCurrentSessionLease(params: {
|
||||
sessionKeys: string[];
|
||||
rootPid?: number;
|
||||
}): AcpxProcessLease | undefined {
|
||||
const sessionKeys = new Set(params.sessionKeys.map((entry) => entry.trim()).filter(Boolean));
|
||||
const sessionKeys = new Set(normalizeStringEntries(params.sessionKeys));
|
||||
const candidates = params.leases.filter((lease) => sessionKeys.has(lease.sessionKey));
|
||||
if (params.rootPid) {
|
||||
return candidates.find((lease) => lease.rootPid === params.rootPid);
|
||||
|
||||
@@ -20,6 +20,12 @@ import {
|
||||
import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { parseAgentSessionKey, parseThreadSessionSuffix } from "openclaw/plugin-sdk/routing";
|
||||
import { isPathInside, replaceFileAtomic } from "openclaw/plugin-sdk/security-runtime";
|
||||
import {
|
||||
asOptionalRecord as asRecord,
|
||||
normalizeOptionalString,
|
||||
normalizeStringEntries,
|
||||
uniqueStrings,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { tempWorkspace, resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
||||
|
||||
const DEFAULT_TIMEOUT_MS = 15_000;
|
||||
@@ -313,11 +319,6 @@ function withToggleStoreLock<T>(statePath: string, task: () => Promise<T>): Prom
|
||||
return withLock(task);
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
type ActiveMemoryThinkingLevel =
|
||||
| "off"
|
||||
| "minimal"
|
||||
@@ -571,10 +572,6 @@ function resolveCanonicalSessionKeyFromSessionId(params: {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function formatRuntimeToolsAllowSource(toolsAllow: readonly string[]): string {
|
||||
return `runtime toolsAllow: ${toolsAllow.join(", ")}`;
|
||||
}
|
||||
@@ -877,9 +874,7 @@ function normalizePluginConfig(
|
||||
: [];
|
||||
return {
|
||||
enabled: raw.enabled !== false,
|
||||
agents: Array.isArray(raw.agents)
|
||||
? raw.agents.map((agentId) => agentId.trim()).filter(Boolean)
|
||||
: [],
|
||||
agents: Array.isArray(raw.agents) ? normalizeStringEntries(raw.agents) : [],
|
||||
model: typeof raw.model === "string" && raw.model.trim() ? raw.model.trim() : undefined,
|
||||
modelFallback:
|
||||
typeof raw.modelFallback === "string" && raw.modelFallback.trim()
|
||||
@@ -1518,11 +1513,11 @@ function buildPluginDebugLine(params: {
|
||||
warning && action && !cleaned
|
||||
? `${warning} ${action}`
|
||||
: [warning, action && !cleaned ? action : ""]
|
||||
.filter((value, index, values) => Boolean(value) && values.indexOf(value) === index)
|
||||
.filter((value): value is string => Boolean(value))
|
||||
.join(" | ");
|
||||
const messages = [warningAction, cleaned]
|
||||
.filter((value, index, values) => Boolean(value) && values.indexOf(value) === index)
|
||||
.join(" | ");
|
||||
const messages = uniqueStrings(
|
||||
[warningAction, cleaned].filter((value): value is string => Boolean(value)),
|
||||
).join(" | ");
|
||||
const trailing = messages;
|
||||
if (prefix && trailing) {
|
||||
return `${ACTIVE_MEMORY_DEBUG_PREFIX} ${prefix} | ${trailing}`;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { dispatchGatewayMethod } from "openclaw/plugin-sdk/gateway-method-runtime";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { isAdminHttpRpcAllowedMethod, listAdminHttpRpcAllowedMethods } from "./methods.js";
|
||||
|
||||
const DEFAULT_RPC_BODY_BYTES = 1024 * 1024;
|
||||
@@ -38,10 +39,6 @@ type ParsedRequest = {
|
||||
params?: unknown;
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function createError(code: string, message: string): RpcError {
|
||||
return { code, message };
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import {
|
||||
type MemoryEmbeddingProvider,
|
||||
type MemoryEmbeddingProviderCreateOptions,
|
||||
} from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
asOptionalRecord as asRecord,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { refreshAwsSharedConfigCacheForBedrock } from "./aws-credential-refresh.js";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -258,12 +261,6 @@ function asNumberArray(value: unknown): number[] {
|
||||
return value;
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function asNumberArrayBatch(value: unknown): number[][] {
|
||||
if (!Array.isArray(value)) {
|
||||
throw malformedBedrockEmbeddingResponse();
|
||||
|
||||
@@ -3,7 +3,10 @@ import {
|
||||
type OpenClawConfig,
|
||||
type ProviderAuthResult,
|
||||
} from "openclaw/plugin-sdk/provider-auth";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
isRecord,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolveClaudeCliAnthropicModelRefs } from "./claude-model-refs.js";
|
||||
import {
|
||||
readClaudeCliCredentialsForSetup,
|
||||
@@ -31,10 +34,6 @@ function toAnthropicSelectedModelRef(raw: string): string | undefined {
|
||||
return resolved?.rewriteRef ?? resolved?.selectedRef;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function rewriteModelSelection(model: AgentDefaultsModel): {
|
||||
value: AgentDefaultsModel;
|
||||
primary?: string;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import {
|
||||
isRecord,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
resolveClaudeCliAnthropicModelRefs,
|
||||
resolveKnownAnthropicModelRef,
|
||||
@@ -8,10 +12,6 @@ import { CLAUDE_CLI_BACKEND_ID, CLAUDE_CLI_DEFAULT_ALLOWLIST_REFS } from "./cli-
|
||||
const ANTHROPIC_PROVIDER_API = "anthropic-messages";
|
||||
const ANTHROPIC_API_KEY_DEFAULT_ALLOWLIST_REFS = ["anthropic/claude-sonnet-4-6"] as const;
|
||||
|
||||
function normalizeLowercaseStringOrEmpty(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
||||
}
|
||||
|
||||
function normalizeProviderId(provider: string): string {
|
||||
const normalized = normalizeLowercaseStringOrEmpty(provider);
|
||||
if (normalized === "bedrock" || normalized === "aws-bedrock") {
|
||||
@@ -20,10 +20,6 @@ function normalizeProviderId(provider: string): string {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function resolveAnthropicDefaultAuthMode(
|
||||
config: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv,
|
||||
|
||||
@@ -13,7 +13,9 @@ import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import {
|
||||
normalizeFastMode,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeStringEntries,
|
||||
readStringValue,
|
||||
uniqueStrings,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
const log = createSubsystemLogger("anthropic-stream");
|
||||
@@ -48,10 +50,7 @@ function parseHeaderList(value: unknown): string[] {
|
||||
if (typeof value !== "string") {
|
||||
return [];
|
||||
}
|
||||
return value
|
||||
.split(",")
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean);
|
||||
return normalizeStringEntries(value.split(","));
|
||||
}
|
||||
|
||||
function mergeAnthropicBetaHeader(
|
||||
@@ -63,7 +62,7 @@ function mergeAnthropicBetaHeader(
|
||||
(key) => normalizeLowercaseStringOrEmpty(key) === "anthropic-beta",
|
||||
);
|
||||
const existing = existingKey ? parseHeaderList(merged[existingKey]) : [];
|
||||
const values = Array.from(new Set([...existing, ...betas]));
|
||||
const values = uniqueStrings([...existing, ...betas]);
|
||||
const key = existingKey ?? "anthropic-beta";
|
||||
merged[key] = values.join(",");
|
||||
return merged;
|
||||
@@ -138,7 +137,7 @@ export function createAnthropicBetaHeadersWrapper(
|
||||
const piAiBetas = isOauth
|
||||
? (PI_AI_OAUTH_ANTHROPIC_BETAS as readonly string[])
|
||||
: (PI_AI_DEFAULT_ANTHROPIC_BETAS as readonly string[]);
|
||||
const allBetas = [...new Set([...piAiBetas, ...effectiveBetas])];
|
||||
const allBetas = uniqueStrings([...piAiBetas, ...effectiveBetas]);
|
||||
return underlying(model, context, {
|
||||
...options,
|
||||
headers: mergeAnthropicBetaHeader(options?.headers, allBetas),
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
WebSearchProviderToolDefinition,
|
||||
} from "openclaw/plugin-sdk/provider-web-search";
|
||||
import { createWebSearchProviderContractFields } from "openclaw/plugin-sdk/provider-web-search-config-contract";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
const BRAVE_CREDENTIAL_PATH = "plugins.entries.brave.config.webSearch.apiKey";
|
||||
|
||||
@@ -61,10 +62,6 @@ const BraveSearchSchema = {
|
||||
},
|
||||
} satisfies Record<string, unknown>;
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function resolveProviderWebSearchPluginConfig(
|
||||
config: unknown,
|
||||
pluginId: string,
|
||||
|
||||
@@ -2,10 +2,7 @@ import {
|
||||
createWebSearchProviderContractFields,
|
||||
type WebSearchProviderPlugin,
|
||||
} from "openclaw/plugin-sdk/provider-web-search-config-contract";
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
function resolveLegacyTopLevelBraveCredential(
|
||||
config: unknown,
|
||||
|
||||
@@ -10,6 +10,8 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
||||
import {
|
||||
normalizeOptionalString,
|
||||
readStringValue,
|
||||
uniqueStrings,
|
||||
uniqueValues,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
|
||||
import { redactToolPayloadText } from "../logging/redact.js";
|
||||
@@ -558,7 +560,7 @@ async function terminateChromeMcpProcessTree(
|
||||
|
||||
const killProcess = deps?.killProcess ?? ((pid, signal) => process.kill(pid, signal));
|
||||
const sleep = deps?.sleep ?? sleepTimeout;
|
||||
const pids = Array.from(new Set([...descendantPids.toReversed(), rootPid])).filter(
|
||||
const pids = uniqueValues([...descendantPids.toReversed(), rootPid]).filter(
|
||||
(pid) => Number.isInteger(pid) && pid > 0 && pid !== process.pid,
|
||||
);
|
||||
const signaled: number[] = [];
|
||||
@@ -1107,7 +1109,7 @@ export async function closeChromeMcpSession(profileName: string): Promise<boolea
|
||||
}
|
||||
|
||||
export async function stopAllChromeMcpSessions(): Promise<void> {
|
||||
const names = [...new Set([...sessions.keys()].map((key) => JSON.parse(key)[0] as string))];
|
||||
const names = uniqueStrings([...sessions.keys()].map((key) => JSON.parse(key)[0] as string));
|
||||
for (const name of names) {
|
||||
await closeChromeMcpSession(name).catch(() => {});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { uniqueValues } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { CDPSession, Page } from "playwright-core";
|
||||
|
||||
type PageCdpSend = (method: string, params?: Record<string, unknown>) => Promise<unknown>;
|
||||
@@ -72,7 +73,7 @@ export async function markBackendDomRefsOnPage(opts: {
|
||||
|
||||
await send("DOM.enable").catch(() => {});
|
||||
|
||||
const backendNodeIds = [...new Set(refs.map((entry) => Math.floor(entry.backendDOMNodeId)))];
|
||||
const backendNodeIds = uniqueValues(refs.map((entry) => Math.floor(entry.backendDOMNodeId)));
|
||||
const pushed = (await send("DOM.pushNodesByBackendIdsToFrontend", {
|
||||
backendNodeIds,
|
||||
}).catch(() => ({}))) as { nodeIds?: number[] };
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { ensurePageState, getPageForTargetId } from "./pw-session.js";
|
||||
import { normalizeTimeoutMs } from "./pw-tools-core.shared.js";
|
||||
import { matchBrowserUrlPattern } from "./url-pattern.js";
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
return typeof value === "string" ? value.trim() || undefined : undefined;
|
||||
}
|
||||
|
||||
export async function responseBodyViaPlaywright(opts: {
|
||||
cdpUrl: string;
|
||||
targetId?: string;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolveBrowserNavigationProxyMode } from "../browser-proxy-mode.js";
|
||||
import { toBrowserErrorResponse } from "../errors.js";
|
||||
import {
|
||||
@@ -10,10 +11,6 @@ import type { BrowserRouteContext, ProfileContext } from "../server-context.js";
|
||||
import type { BrowserRequest, BrowserResponse } from "./types.js";
|
||||
import { getProfileContext, jsonError } from "./utils.js";
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
return typeof value === "string" ? value.trim() || undefined : undefined;
|
||||
}
|
||||
|
||||
export const SELECTOR_UNSUPPORTED_MESSAGE = [
|
||||
"Error: 'selector' is not supported. Use 'ref' from snapshot instead.",
|
||||
"",
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
normalizeOptionalString,
|
||||
readStringValue,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { ResolvedBrowserProfile } from "../config.js";
|
||||
import {
|
||||
DEFAULT_AI_SNAPSHOT_EFFICIENT_DEPTH,
|
||||
@@ -11,14 +15,6 @@ import {
|
||||
} from "../profile-capabilities.js";
|
||||
import { toBoolean, toNumber, toStringOrEmpty } from "./utils.js";
|
||||
|
||||
function readStringValue(value: unknown): string | undefined {
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
return readStringValue(value)?.trim() || undefined;
|
||||
}
|
||||
|
||||
type BrowserSnapshotPlan = {
|
||||
format: "ai" | "aria";
|
||||
mode?: "efficient";
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
normalizeOptionalString,
|
||||
readStringValue,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { BrowserRouteContext } from "../server-context.js";
|
||||
import {
|
||||
readBody,
|
||||
@@ -8,14 +12,6 @@ import {
|
||||
import type { BrowserRequest, BrowserResponse, BrowserRouteRegistrar } from "./types.js";
|
||||
import { asyncBrowserRoute, jsonError, toBoolean, toNumber, toStringOrEmpty } from "./utils.js";
|
||||
|
||||
function readStringValue(value: unknown): string | undefined {
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
return readStringValue(value)?.trim() || undefined;
|
||||
}
|
||||
|
||||
type StorageKind = "local" | "session";
|
||||
|
||||
export function parseStorageKind(raw: string): StorageKind | null {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { SsrFPolicy } from "../../infra/net/ssrf.js";
|
||||
import { withCdpSocket } from "../cdp.helpers.js";
|
||||
import { getChromeWebSocketUrl } from "../chrome.js";
|
||||
@@ -57,7 +58,7 @@ function readPermissions(raw: unknown): string[] | null {
|
||||
if (permissions.length !== raw.length) {
|
||||
return null;
|
||||
}
|
||||
return [...new Set(permissions)];
|
||||
return uniqueStrings(permissions);
|
||||
}
|
||||
|
||||
async function grantPermissions(params: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||
|
||||
export function withAllowedHostname(
|
||||
@@ -6,6 +7,6 @@ export function withAllowedHostname(
|
||||
): SsrFPolicy {
|
||||
return {
|
||||
...ssrfPolicy,
|
||||
allowedHostnames: Array.from(new Set([...(ssrfPolicy?.allowedHostnames ?? []), hostname])),
|
||||
allowedHostnames: uniqueStrings([...(ssrfPolicy?.allowedHostnames ?? []), hostname]),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import fsPromises from "node:fs/promises";
|
||||
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { redactCdpUrl } from "../browser/cdp.helpers.js";
|
||||
import { loadBrowserConfigForRuntimeRefresh } from "../browser/config-refresh-source.js";
|
||||
import { resolveBrowserConfig } from "../browser/config.js";
|
||||
@@ -40,7 +41,7 @@ const DEFAULT_BROWSER_PROXY_TIMEOUT_MS = 20_000;
|
||||
const BROWSER_PROXY_STATUS_TIMEOUT_MS = 750;
|
||||
|
||||
function normalizeProfileAllowlist(raw?: string[]): string[] {
|
||||
return Array.isArray(raw) ? raw.map((entry) => entry.trim()).filter(Boolean) : [];
|
||||
return Array.isArray(raw) ? normalizeStringEntries(raw) : [];
|
||||
}
|
||||
|
||||
function resolveBrowserProxyConfig() {
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
waitProviderOperationPollInterval,
|
||||
type ProviderOperationTimeoutMs,
|
||||
} from "openclaw/plugin-sdk/provider-http";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type {
|
||||
GeneratedVideoAsset,
|
||||
VideoGenerationProvider,
|
||||
@@ -43,10 +43,6 @@ type BytePlusTaskResponse = {
|
||||
|
||||
type BytePlusTaskStatus = "running" | "failed" | "queued" | "succeeded" | "cancelled";
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
async function readBytePlusJsonResponse<T>(
|
||||
response: Pick<Response, "json">,
|
||||
label: string,
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { asOptionalRecord as readRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
type MutableRecord = Record<string, unknown>;
|
||||
|
||||
function readRecord(value: unknown): MutableRecord | undefined {
|
||||
return isRecord(value) ? (value as MutableRecord) : undefined;
|
||||
}
|
||||
|
||||
function mergeHostConfig(params: {
|
||||
legacyHost: MutableRecord;
|
||||
existingHost: MutableRecord | undefined;
|
||||
|
||||
@@ -5,6 +5,11 @@ import {
|
||||
resolvePluginConfigObject,
|
||||
} from "openclaw/plugin-sdk/plugin-config-runtime";
|
||||
import { isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
|
||||
import {
|
||||
asBoolean as readBoolean,
|
||||
isRecord,
|
||||
readStringValue as readString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export type CanvasHostConfig = {
|
||||
enabled?: boolean;
|
||||
@@ -22,18 +27,6 @@ type CanvasPluginConfigSchema = {
|
||||
uiHints: Record<string, { label: string; help?: string; advanced?: boolean }>;
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function readBoolean(value: unknown): boolean | undefined {
|
||||
return typeof value === "boolean" ? value : undefined;
|
||||
}
|
||||
|
||||
function readString(value: unknown): string | undefined {
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
function readPositiveInteger(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { embeddedAgentLog } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { JsonValue, v2 } from "./protocol.js";
|
||||
|
||||
export const CODEX_APP_INVENTORY_CACHE_TTL_MS = 60 * 60 * 1_000;
|
||||
@@ -260,10 +261,6 @@ function fingerprintInventoryCacheKey(key: string): string {
|
||||
return hash.toString(16).padStart(8, "0");
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function redactErrorData(value: unknown, depth = 0): JsonValue | undefined {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
type NativeHookRelayRegistrationHandle,
|
||||
runBeforeToolCallHook,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { normalizeTrimmedStringList } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { formatCodexDisplayText } from "../command-formatters.js";
|
||||
import {
|
||||
approvalRequestExplicitlyUnavailable,
|
||||
@@ -877,10 +878,7 @@ function summarizeNetworkPolicyAmendments(value: JsonValue | undefined): string
|
||||
}
|
||||
|
||||
function readStringArray(record: JsonObject, key: string): string[] {
|
||||
const value = record[key];
|
||||
return Array.isArray(value)
|
||||
? value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean)
|
||||
: [];
|
||||
return normalizeTrimmedStringList(record[key]);
|
||||
}
|
||||
|
||||
function sanitizePermissionHostValue(value: string): string {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
type CompactEmbeddedPiSessionParams,
|
||||
type EmbeddedPiCompactResult,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { asOptionalRecord as readRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
defaultCodexAppServerClientFactory,
|
||||
type CodexAppServerClientFactory,
|
||||
@@ -117,12 +118,6 @@ function readAgentIdFromSessionKey(sessionKey: string | undefined): string | und
|
||||
return parts[1]?.trim() || undefined;
|
||||
}
|
||||
|
||||
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
async function compactCodexNativeThread(
|
||||
params: CompactEmbeddedPiSessionParams,
|
||||
options: { pluginConfig?: unknown; clientFactory?: CodexAppServerClientFactory } = {},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createHmac, randomBytes } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { hostname as readHostName } from "node:os";
|
||||
import { normalizeTrimmedStringList } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { detectWindowsSpawnCommandInlineArgs } from "openclaw/plugin-sdk/windows-spawn";
|
||||
import { z } from "zod";
|
||||
import type { CodexSandboxPolicy, CodexServiceTier } from "./protocol.js";
|
||||
@@ -991,12 +992,7 @@ function normalizeHeaders(value: unknown): Record<string, string> {
|
||||
}
|
||||
|
||||
function normalizeStringList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
return value
|
||||
.map((entry) => readNonEmptyString(entry))
|
||||
.filter((entry): entry is string => entry !== undefined);
|
||||
return normalizeTrimmedStringList(value);
|
||||
}
|
||||
|
||||
function readBooleanEnv(value: string | undefined): boolean | undefined {
|
||||
|
||||
@@ -20,6 +20,10 @@ import {
|
||||
wrapToolWithBeforeToolCallHook,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
||||
import {
|
||||
asOptionalRecord as readRecord,
|
||||
isRecord,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { CodexDynamicToolsLoading } from "./config.js";
|
||||
import { invalidInlineImageText, sanitizeInlineImageDataUrl } from "./image-payload-sanitizer.js";
|
||||
import {
|
||||
@@ -435,14 +439,6 @@ function extractInternalSourceReplyPayload(
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return isRecord(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function readPositiveInteger(value: unknown): number | undefined {
|
||||
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
||||
return undefined;
|
||||
|
||||
@@ -21,6 +21,11 @@ import {
|
||||
type ToolProgressDetailMode,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
|
||||
import {
|
||||
asBoolean,
|
||||
asFiniteNumber,
|
||||
normalizeStringEntries,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolveCodexLocalRuntimeAttribution } from "./local-runtime-attribution.js";
|
||||
import {
|
||||
readCodexNotificationThreadId,
|
||||
@@ -807,9 +812,7 @@ export class CodexAppServerEventProjector {
|
||||
}
|
||||
|
||||
private buildToolMediaUrls(toolTelemetry: CodexAppServerToolTelemetry): string[] | undefined {
|
||||
const mediaUrls = new Set(
|
||||
toolTelemetry.toolMediaUrls?.map((url) => url.trim()).filter(Boolean) ?? [],
|
||||
);
|
||||
const mediaUrls = new Set(normalizeStringEntries(toolTelemetry.toolMediaUrls ?? []));
|
||||
if ((toolTelemetry.messagingToolSentMediaUrls?.length ?? 0) === 0) {
|
||||
for (const mediaUrl of this.nativeGeneratedMediaUrls) {
|
||||
mediaUrls.add(mediaUrl);
|
||||
@@ -1581,13 +1584,11 @@ function readNullableString(record: JsonObject, key: string): string | null | un
|
||||
}
|
||||
|
||||
function readNumber(record: JsonObject, key: string): number | undefined {
|
||||
const value = record[key];
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
||||
return asFiniteNumber(record[key]);
|
||||
}
|
||||
|
||||
function readBoolean(record: JsonObject, key: string): boolean | undefined {
|
||||
const value = record[key];
|
||||
return typeof value === "boolean" ? value : undefined;
|
||||
return asBoolean(record[key]);
|
||||
}
|
||||
|
||||
function readBooleanAlias(record: JsonObject, keys: readonly string[]): boolean | undefined {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
const DATA_URL_PREFIX = "data:";
|
||||
const IMAGE_OMITTED_TEXT = "omitted image payload: invalid inline image data";
|
||||
const IMAGE_SIGNATURES: Array<{
|
||||
@@ -105,8 +107,7 @@ function parseImageDataUrl(value: string):
|
||||
|
||||
function metadataAllowsImageBase64(metadata: string[]): boolean {
|
||||
const [mimeType, ...options] = metadata;
|
||||
const isImageMimeType =
|
||||
mimeType !== undefined && mimeType.toLowerCase().startsWith("image/");
|
||||
const isImageMimeType = mimeType !== undefined && mimeType.toLowerCase().startsWith("image/");
|
||||
return isImageMimeType && options.some((part) => part.toLowerCase() === "base64");
|
||||
}
|
||||
|
||||
@@ -137,10 +138,6 @@ export function invalidInlineImageText(label: string): string {
|
||||
return `[${label}] ${IMAGE_OMITTED_TEXT}`;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function sanitizeImageContentRecord(
|
||||
record: Record<string, unknown>,
|
||||
label: string,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { resolveCodexAppServerAuthProfileIdForAgent } from "./auth-bridge.js";
|
||||
import type { CodexAppServerClient } from "./client.js";
|
||||
import type { CodexAppServerStartOptions } from "./config.js";
|
||||
@@ -156,7 +157,7 @@ function readReasoningEfforts(value: CodexReasoningEffortOption[]): string[] {
|
||||
const efforts = value
|
||||
.map((entry) => readNonEmptyString(entry.reasoningEffort))
|
||||
.filter((entry): entry is string => entry !== undefined);
|
||||
return [...new Set(efforts)];
|
||||
return uniqueStrings(efforts);
|
||||
}
|
||||
|
||||
function readNonEmptyString(value: unknown): string | undefined {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
type AgentHarnessTaskRuntime,
|
||||
type AgentHarnessTaskRecord,
|
||||
} from "openclaw/plugin-sdk/agent-harness-task-runtime";
|
||||
import { asFiniteNumber, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { CodexAppServerClient } from "./client.js";
|
||||
import {
|
||||
extractCodexNativeSubagentCompletions,
|
||||
@@ -842,11 +843,6 @@ function readString(record: JsonObject | undefined, key: string): string | undef
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
function normalizeOptionalString(value: string | undefined): string | undefined {
|
||||
const normalized = value?.trim();
|
||||
return normalized || undefined;
|
||||
}
|
||||
|
||||
function readStringArray(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
@@ -1037,8 +1033,7 @@ function readTranscriptParentThreadId(payload: JsonObject): string | undefined {
|
||||
}
|
||||
|
||||
function readNumber(record: JsonObject, key: string): number | undefined {
|
||||
const value = record[key];
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
||||
return asFiniteNumber(record[key]);
|
||||
}
|
||||
|
||||
function secondsToMillis(value: number | undefined): number | undefined {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asFiniteNumber } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { isJsonObject, type JsonObject, type JsonValue } from "./protocol.js";
|
||||
|
||||
const CODEX_LIMIT_ID = "codex";
|
||||
@@ -603,8 +604,7 @@ function readNullableString(record: JsonObject, key: string): string | undefined
|
||||
}
|
||||
|
||||
function readNumber(record: JsonObject, key: string): number | undefined {
|
||||
const value = record[key];
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
||||
return asFiniteNumber(record[key]);
|
||||
}
|
||||
|
||||
function normalizeText(value: string | null | undefined): string | undefined {
|
||||
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/diagnostic-runtime";
|
||||
import { isToolAllowed } from "openclaw/plugin-sdk/sandbox";
|
||||
import { pathExists } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { asBoolean } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { defaultCodexAppInventoryCache } from "./app-inventory-cache.js";
|
||||
import { handleCodexAppServerApprovalRequest } from "./approval-bridge.js";
|
||||
import {
|
||||
@@ -4990,8 +4991,7 @@ function readString(record: JsonObject, key: string): string | undefined {
|
||||
}
|
||||
|
||||
function readBoolean(record: JsonObject, key: string): boolean | undefined {
|
||||
const value = record[key];
|
||||
return typeof value === "boolean" ? value : undefined;
|
||||
return asBoolean(record[key]);
|
||||
}
|
||||
|
||||
async function readMirroredSessionHistoryMessages(
|
||||
|
||||
@@ -10,16 +10,12 @@ import {
|
||||
type EmbeddedRunAttemptParams,
|
||||
type SessionWriteLockAcquireTimeoutConfig,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
type MirroredAgentMessage = Extract<AgentMessage, { role: "user" | "assistant" | "toolResult" }>;
|
||||
|
||||
const MIRROR_IDENTITY_META_KEY = "mirrorIdentity" as const;
|
||||
|
||||
function normalizeOptionalString(value: string | null | undefined): string | undefined {
|
||||
const normalized = value?.trim();
|
||||
return normalized ? normalized : undefined;
|
||||
}
|
||||
|
||||
function buildSenderLabel(params: {
|
||||
senderId?: string;
|
||||
senderName?: string;
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
type AuthProfileStore,
|
||||
} from "openclaw/plugin-sdk/agent-runtime";
|
||||
import type { PluginCommandContext } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { normalizeUniqueStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { CODEX_CONTROL_METHODS, type CodexControlMethod } from "./app-server/capabilities.js";
|
||||
import { isJsonObject, type JsonObject, type JsonValue } from "./app-server/protocol.js";
|
||||
import { rememberCodexRateLimits } from "./app-server/rate-limit-cache.js";
|
||||
@@ -149,7 +150,7 @@ function resolveDisplayAuthOrder(params: {
|
||||
resolveOrder(params.store.order, OPENAI_CODEX_PROVIDER_ID) ??
|
||||
resolveOrder(params.config?.auth?.order, OPENAI_CODEX_PROVIDER_ID);
|
||||
if (codexOrder && codexOrder.length > 0) {
|
||||
return { order: dedupe(codexOrder), explicit: true };
|
||||
return { order: normalizeUniqueStringEntries(codexOrder), explicit: true };
|
||||
}
|
||||
const order = resolveAuthProfileOrder({
|
||||
cfg: params.config,
|
||||
@@ -573,17 +574,3 @@ function formatRelativeReset(untilMs: number, nowMs: number): string {
|
||||
const days = Math.ceil(durationMs / dayMs);
|
||||
return `in ${days} ${days === 1 ? "day" : "days"}`;
|
||||
}
|
||||
|
||||
function dedupe(values: string[]): string[] {
|
||||
const seen = new Set<string>();
|
||||
const result: string[] = [];
|
||||
for (const value of values) {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed || seen.has(trimmed)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(trimmed);
|
||||
result.push(trimmed);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import crypto from "node:crypto";
|
||||
import { resolveAgentDir, resolveSessionAgentIds } from "openclaw/plugin-sdk/agent-runtime";
|
||||
import type { PluginCommandContext, PluginCommandResult } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { CODEX_CONTROL_METHODS, type CodexControlMethod } from "./app-server/capabilities.js";
|
||||
import {
|
||||
installCodexComputerUse,
|
||||
@@ -2104,8 +2105,3 @@ function normalizeComputerUseStringOverrides(
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function normalizeOptionalString(value: string | undefined): string | undefined {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import process from "node:process";
|
||||
import type { PluginConversationBinding } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { asOptionalRecord as readRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
const BINDING_DATA_VERSION = 1;
|
||||
|
||||
@@ -112,12 +113,6 @@ export function resolveCodexDefaultWorkspaceDir(pluginConfig: unknown): string {
|
||||
return configured ?? process.cwd();
|
||||
}
|
||||
|
||||
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function readString(record: Record<string, unknown> | undefined, key: string) {
|
||||
const value = record?.[key];
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asOptionalRecord as readRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
readCodexNotificationThreadId,
|
||||
readCodexNotificationTurnId,
|
||||
@@ -173,12 +174,6 @@ function readNotificationTurnId(params: JsonObject): string | undefined {
|
||||
return readCodexNotificationTurnId(params);
|
||||
}
|
||||
|
||||
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function readString(record: Record<string, unknown> | JsonObject | undefined, key: string) {
|
||||
const value = record?.[key];
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { PluginHookInboundClaimEvent } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { normalizeSingleOrTrimmedStringList } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { CodexUserInput } from "./app-server/protocol.js";
|
||||
|
||||
type InboundMedia = {
|
||||
@@ -28,10 +29,14 @@ function extractInboundMedia(event: PluginHookInboundClaimEvent): InboundMedia[]
|
||||
// OpenClaw channels expose either local staged files or remote URLs. Keep
|
||||
// them separate so Codex can receive the cheaper localImage input when a file
|
||||
// is already present, while still supporting remote-only transports.
|
||||
const paths = readStringArray(metadata.mediaPaths).concat(readStringArray(metadata.mediaPath));
|
||||
const urls = readStringArray(metadata.mediaUrls).concat(readStringArray(metadata.mediaUrl));
|
||||
const mimeTypes = readStringArray(metadata.mediaTypes).concat(
|
||||
readStringArray(metadata.mediaType),
|
||||
const paths = normalizeSingleOrTrimmedStringList(metadata.mediaPaths).concat(
|
||||
normalizeSingleOrTrimmedStringList(metadata.mediaPath),
|
||||
);
|
||||
const urls = normalizeSingleOrTrimmedStringList(metadata.mediaUrls).concat(
|
||||
normalizeSingleOrTrimmedStringList(metadata.mediaUrl),
|
||||
);
|
||||
const mimeTypes = normalizeSingleOrTrimmedStringList(metadata.mediaTypes).concat(
|
||||
normalizeSingleOrTrimmedStringList(metadata.mediaType),
|
||||
);
|
||||
const count = Math.max(paths.length, urls.length, mimeTypes.length);
|
||||
const media: InboundMedia[] = [];
|
||||
@@ -94,13 +99,3 @@ function readLocalMediaPath(value: string | undefined): string | undefined {
|
||||
}
|
||||
return /^[a-z][a-z0-9+.-]*:/i.test(value) ? undefined : value;
|
||||
}
|
||||
|
||||
function readStringArray(value: unknown): string[] {
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
return [value.trim()];
|
||||
}
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
return value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import type {
|
||||
MigrationPlan,
|
||||
MigrationProviderContext,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { defaultCodexAppInventoryCache } from "../app-server/app-inventory-cache.js";
|
||||
import {
|
||||
resolveCodexAppServerAuthAccountCacheKey,
|
||||
@@ -176,8 +177,8 @@ export async function applyCodexMigrationPlan(params: {
|
||||
reportDir,
|
||||
};
|
||||
if (items.some(isCodexPluginLoadWarningItem)) {
|
||||
result.warnings = [...new Set([...(result.warnings ?? []), CODEX_PLUGIN_LOAD_WARNING])];
|
||||
result.nextSteps = [...new Set([CODEX_PLUGIN_LOAD_WARNING, ...(result.nextSteps ?? [])])];
|
||||
result.warnings = uniqueStrings([...(result.warnings ?? []), CODEX_PLUGIN_LOAD_WARNING]);
|
||||
result.nextSteps = uniqueStrings([CODEX_PLUGIN_LOAD_WARNING, ...(result.nextSteps ?? [])]);
|
||||
}
|
||||
await writeMigrationReport(result, { title: "Codex Migration Report" });
|
||||
return result;
|
||||
|
||||
@@ -17,6 +17,10 @@ import {
|
||||
type OpenClawConfig,
|
||||
type ProviderAuthResult,
|
||||
} from "openclaw/plugin-sdk/provider-auth";
|
||||
import {
|
||||
isRecord,
|
||||
normalizeOptionalString as readString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { readJsonObject } from "./helpers.js";
|
||||
import type { CodexSource } from "./source.js";
|
||||
import type { resolveCodexMigrationTargets } from "./targets.js";
|
||||
@@ -65,14 +69,6 @@ type CodexAuthConfigApplyResult = "configured" | "conflict" | "unavailable";
|
||||
|
||||
class CodexAuthConfigConflict extends Error {}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function readString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function decodeJwtPayload(token: string): Record<string, unknown> | undefined {
|
||||
const payload = token.split(".")[1];
|
||||
if (!payload) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
MigrationPlan,
|
||||
MigrationProviderContext,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { asBoolean, isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { CODEX_PLUGINS_MARKETPLACE_NAME } from "../app-server/config.js";
|
||||
import { buildCodexAuthItems } from "./auth.js";
|
||||
import { exists, sanitizeName } from "./helpers.js";
|
||||
@@ -261,11 +262,7 @@ function readExistingAllowDestructiveActions(
|
||||
...CODEX_PLUGIN_NATIVE_CONFIG_PATH,
|
||||
"allow_destructive_actions",
|
||||
]);
|
||||
return typeof value === "boolean" ? value : undefined;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
return asBoolean(value);
|
||||
}
|
||||
|
||||
export function buildCodexPluginsConfigValue(
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
OpenClawPluginNodeInvokePolicy,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
||||
import {
|
||||
materializeWindowsSpawnProgram,
|
||||
@@ -705,7 +706,3 @@ function readNodeId(node: CodexCliSessionNodeInfo): string {
|
||||
function formatNodeLabel(node: CodexCliSessionNodeInfo): string {
|
||||
return [node.displayName, node.nodeId, node.remoteIp].filter(Boolean).join(" / ") || "node";
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
@@ -25,9 +25,11 @@ import {
|
||||
type SsrFPolicy,
|
||||
} from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import {
|
||||
asBoolean,
|
||||
isRecord,
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
uniqueStrings,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolveUserPath } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
|
||||
@@ -112,8 +114,7 @@ export function setComfyFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | n
|
||||
}
|
||||
|
||||
function readConfigBoolean(config: ComfyProviderConfig, key: string): boolean | undefined {
|
||||
const value = config[key];
|
||||
return typeof value === "boolean" ? value : undefined;
|
||||
return asBoolean(config[key]);
|
||||
}
|
||||
|
||||
function readConfigInteger(config: ComfyProviderConfig, key: string): number | undefined {
|
||||
@@ -822,6 +823,6 @@ export async function runComfyWorkflow(params: {
|
||||
assets,
|
||||
model: providerModel,
|
||||
promptId,
|
||||
outputNodeIds: Array.from(new Set(outputFiles.map((entry) => entry.nodeId))),
|
||||
outputNodeIds: uniqueStrings(outputFiles.map((entry) => entry.nodeId)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeStringEntries, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
definePluginEntry,
|
||||
type ProviderAuthContext,
|
||||
@@ -43,11 +44,8 @@ function validateBaseUrl(value: string): string | undefined {
|
||||
}
|
||||
|
||||
function parseModelIds(input: string): string[] {
|
||||
const parsed = input
|
||||
.split(/[\n,]/)
|
||||
.map((model) => model.trim())
|
||||
.filter(Boolean);
|
||||
return Array.from(new Set(parsed));
|
||||
const parsed = normalizeStringEntries(input.split(/[\n,]/));
|
||||
return uniqueStrings(parsed);
|
||||
}
|
||||
|
||||
function buildModelDefinition(modelId: string) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
resolveProviderHttpRequestConfig,
|
||||
requireTranscriptionText,
|
||||
} from "openclaw/plugin-sdk/provider-http";
|
||||
import { asOptionalRecord as asRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export const DEFAULT_DEEPGRAM_AUDIO_BASE_URL = "https://api.deepgram.com/v1";
|
||||
export const DEFAULT_DEEPGRAM_AUDIO_MODEL = "nova-3";
|
||||
@@ -18,12 +19,6 @@ function resolveModel(model?: string): string {
|
||||
return trimmed || DEFAULT_DEEPGRAM_AUDIO_MODEL;
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function readDeepgramTranscript(payload: Record<string, unknown>): string | undefined {
|
||||
const results = asRecord(payload.results);
|
||||
if (!results) {
|
||||
|
||||
@@ -6,7 +6,12 @@ import {
|
||||
type RealtimeTranscriptionSessionCreateRequest,
|
||||
} from "openclaw/plugin-sdk/realtime-transcription";
|
||||
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
asOptionalRecord as readRecord,
|
||||
normalizeOptionalString,
|
||||
parseBooleanValue as readBoolean,
|
||||
parseFiniteNumber as readFiniteNumber,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { DEFAULT_DEEPGRAM_AUDIO_BASE_URL, DEFAULT_DEEPGRAM_AUDIO_MODEL } from "./audio.js";
|
||||
|
||||
type DeepgramRealtimeTranscriptionEncoding = "linear16" | "mulaw" | "alaw";
|
||||
@@ -55,45 +60,12 @@ const DEEPGRAM_REALTIME_MAX_RECONNECT_ATTEMPTS = 5;
|
||||
const DEEPGRAM_REALTIME_RECONNECT_DELAY_MS = 1000;
|
||||
const DEEPGRAM_REALTIME_MAX_QUEUED_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function readNestedDeepgramConfig(rawConfig: RealtimeTranscriptionProviderConfig) {
|
||||
const raw = readRecord(rawConfig);
|
||||
const providers = readRecord(raw?.providers);
|
||||
return readRecord(providers?.deepgram ?? raw?.deepgram ?? raw) ?? {};
|
||||
}
|
||||
|
||||
function readFiniteNumber(value: unknown): number | undefined {
|
||||
const next =
|
||||
typeof value === "number"
|
||||
? value
|
||||
: typeof value === "string"
|
||||
? Number.parseFloat(value)
|
||||
: undefined;
|
||||
return Number.isFinite(next) ? next : undefined;
|
||||
}
|
||||
|
||||
function readBoolean(value: unknown): boolean | undefined {
|
||||
if (typeof value === "boolean") {
|
||||
return value;
|
||||
}
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (["1", "true", "yes", "on"].includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
if (["0", "false", "no", "off"].includes(normalized)) {
|
||||
return false;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function normalizeDeepgramEncoding(
|
||||
value: unknown,
|
||||
): DeepgramRealtimeTranscriptionEncoding | undefined {
|
||||
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
postJsonRequest,
|
||||
resolveProviderHttpRequestConfig,
|
||||
} from "openclaw/plugin-sdk/provider-http";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
asFiniteNumber as coerceProviderNumber,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type {
|
||||
GeneratedVideoAsset,
|
||||
VideoGenerationProvider,
|
||||
@@ -79,14 +82,6 @@ function parseVideoDataUrl(url: string): GeneratedVideoAsset | undefined {
|
||||
};
|
||||
}
|
||||
|
||||
function coerceProviderNumber(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function coerceProviderString(value: unknown): string | undefined {
|
||||
return normalizeOptionalString(value);
|
||||
}
|
||||
|
||||
function resolveDurationSeconds(value: number | undefined): number | undefined {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) {
|
||||
return undefined;
|
||||
@@ -115,11 +110,12 @@ function buildDeepInfraVideoBody(
|
||||
body.seed = seed;
|
||||
}
|
||||
const negativePrompt =
|
||||
coerceProviderString(options.negative_prompt) ?? coerceProviderString(options.negativePrompt);
|
||||
normalizeOptionalString(options.negative_prompt) ??
|
||||
normalizeOptionalString(options.negativePrompt);
|
||||
if (negativePrompt) {
|
||||
body.negative_prompt = negativePrompt;
|
||||
}
|
||||
const style = coerceProviderString(options.style);
|
||||
const style = normalizeOptionalString(options.style);
|
||||
if (style) {
|
||||
body.style = style;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { resolveGlobalMap } from "openclaw/plugin-sdk/global-singleton";
|
||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { DiscordComponentEntry, DiscordModalEntry } from "./components.js";
|
||||
import { getOptionalDiscordRuntime } from "./runtime.js";
|
||||
|
||||
@@ -275,7 +276,7 @@ function resolveComponentConsumptionIds(entry: DiscordComponentEntry): string[]
|
||||
return [entry.id];
|
||||
}
|
||||
const ids = entry.consumptionGroupEntryIds?.filter((id) => typeof id === "string" && id) ?? [];
|
||||
return ids.length > 0 ? Array.from(new Set(ids)) : [entry.id];
|
||||
return ids.length > 0 ? uniqueStrings(ids) : [entry.id];
|
||||
}
|
||||
|
||||
function deleteComponentConsumptionGroup(entry: DiscordComponentEntry): void {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-payload";
|
||||
import {
|
||||
asOptionalRecord as readRecord,
|
||||
normalizeOptionalString as readString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export type DiscordInboundEventDeliveryEnd = () => void;
|
||||
|
||||
@@ -77,16 +81,6 @@ export function notifyDiscordInboundEventOutboundSuccess(params: {
|
||||
event.markInboundEventDelivered();
|
||||
}
|
||||
|
||||
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function readString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
export function withDiscordInboundEventDeliveryMetadata(
|
||||
payload: ReplyPayload,
|
||||
params: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { APIMessage, APIUser } from "discord-api-types/v10";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { readStringValue as readString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { getChannelMessage, Message as DiscordMessage, type Message } from "../internal/discord.js";
|
||||
import { resolveDiscordMessageText, type DiscordChannelInfo } from "./message-utils.js";
|
||||
|
||||
@@ -93,10 +94,6 @@ function readMessageFallback(message: Message): MessageFallback {
|
||||
};
|
||||
}
|
||||
|
||||
function readString(value: unknown): string | undefined {
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
function normalizeStringArray(value: unknown): string[] {
|
||||
return Array.isArray(value)
|
||||
? value.flatMap((entry) => (typeof entry === "string" ? [entry] : []))
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
uniqueStrings,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { Message } from "../internal/discord.js";
|
||||
import {
|
||||
@@ -81,7 +82,7 @@ function mergeHostnameList(...lists: Array<string[] | undefined>): string[] | un
|
||||
if (merged.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return Array.from(new Set(merged));
|
||||
return uniqueStrings(merged);
|
||||
}
|
||||
|
||||
function resolveDiscordMediaSsrFPolicy(policy?: SsrFPolicy): SsrFPolicy {
|
||||
|
||||
@@ -5,7 +5,10 @@ import {
|
||||
} from "openclaw/plugin-sdk/command-auth-native";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { danger, warn, type RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeStringEntriesLower,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export type GetPluginCommandSpecs =
|
||||
typeof import("openclaw/plugin-sdk/plugin-runtime").getPluginCommandSpecs;
|
||||
@@ -32,9 +35,7 @@ async function appendPluginCommandSpecs(params: {
|
||||
getPluginCommandSpecs?: GetPluginCommandSpecs;
|
||||
}): Promise<NativeCommandSpec[]> {
|
||||
const merged = [...params.commandSpecs];
|
||||
const existingNames = new Set(
|
||||
merged.map((spec) => normalizeLowercaseStringOrEmpty(spec.name)).filter(Boolean),
|
||||
);
|
||||
const existingNames = new Set(normalizeStringEntriesLower(merged.map((spec) => spec.name)));
|
||||
const getPluginCommandSpecs =
|
||||
params.getPluginCommandSpecs ?? (await loadPluginRuntime()).getPluginCommandSpecs;
|
||||
for (const pluginCommand of getPluginCommandSpecs("discord", { config: params.cfg })) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
uniqueStrings,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { parseDiscordTarget } from "../targets.js";
|
||||
import { resolveChannelIdForBinding } from "./thread-bindings.discord-api.js";
|
||||
@@ -349,6 +350,6 @@ export async function reconcileAcpThreadBindingsOnStartup(params: {
|
||||
return {
|
||||
checked: acpBindings.length,
|
||||
removed,
|
||||
staleSessionKeys: [...new Set(staleSessionKeys)],
|
||||
staleSessionKeys: uniqueStrings(staleSessionKeys),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,15 +5,11 @@ import {
|
||||
resolveNativeCommandsEnabled,
|
||||
resolveNativeSkillsEnabled,
|
||||
} from "openclaw/plugin-sdk/native-command-config-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { ResolvedDiscordAccount } from "./accounts.js";
|
||||
import type { OpenClawConfig } from "./runtime-api.js";
|
||||
import { isDiscordMutableAllowEntry } from "./security-doctor.js";
|
||||
|
||||
function normalizeOptionalString(value: string | null | undefined): string | undefined {
|
||||
const normalized = value?.trim();
|
||||
return normalized ? normalized : undefined;
|
||||
}
|
||||
|
||||
function addDiscordNameBasedEntries(params: {
|
||||
target: Set<string>;
|
||||
values: unknown;
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { MarkdownTableMode, OpenClawConfig } from "openclaw/plugin-sdk/conf
|
||||
import type { OutboundMediaAccess } from "openclaw/plugin-sdk/media-runtime";
|
||||
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
||||
import type { ChunkMode } from "openclaw/plugin-sdk/reply-chunking";
|
||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolveDiscordAccount } from "./accounts.js";
|
||||
import { registerDiscordComponentEntries } from "./components-registry.js";
|
||||
import {
|
||||
@@ -218,7 +219,7 @@ async function buildDiscordComponentPayload(params: {
|
||||
}
|
||||
|
||||
const attachmentNames = extractComponentAttachmentNames(spec);
|
||||
const uniqueAttachmentNames = [...new Set(attachmentNames)];
|
||||
const uniqueAttachmentNames = uniqueStrings(attachmentNames);
|
||||
if (uniqueAttachmentNames.length > 1) {
|
||||
throw new Error(
|
||||
"Discord component attachments currently support a single file. Use media-gallery for multiple files.",
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeStringEntries,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { loadWebMediaRaw } from "openclaw/plugin-sdk/web-media";
|
||||
import { createGuildEmoji, createGuildSticker, listGuildEmojis } from "./internal/discord.js";
|
||||
import { normalizeEmojiName, resolveDiscordRest } from "./send.shared.js";
|
||||
@@ -21,7 +24,7 @@ export async function uploadEmojiDiscord(payload: DiscordEmojiUpload, opts: Disc
|
||||
throw new Error("Discord emoji uploads require a PNG, JPG, or GIF image");
|
||||
}
|
||||
const image = `data:${contentType};base64,${media.buffer.toString("base64")}`;
|
||||
const roleIds = (payload.roleIds ?? []).map((id) => id.trim()).filter(Boolean);
|
||||
const roleIds = normalizeStringEntries(payload.roleIds ?? []);
|
||||
return await createGuildEmoji(rest, payload.guildId, {
|
||||
body: {
|
||||
name: normalizeEmojiName(payload.name, "Emoji name"),
|
||||
|
||||
@@ -14,6 +14,7 @@ import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime"
|
||||
import type { ChunkMode } from "openclaw/plugin-sdk/reply-chunking";
|
||||
import { resolveTextChunksWithFallback } from "openclaw/plugin-sdk/reply-payload";
|
||||
import type { RetryRunner } from "openclaw/plugin-sdk/retry-runtime";
|
||||
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { loadWebMedia } from "openclaw/plugin-sdk/web-media";
|
||||
import { chunkDiscordTextWithMode } from "./chunk.js";
|
||||
import { createDiscordClient, resolveDiscordRest, type DiscordClientOpts } from "./client.js";
|
||||
@@ -80,7 +81,7 @@ function normalizeReactionEmoji(raw: string) {
|
||||
}
|
||||
|
||||
function normalizeStickerIds(raw: string[]) {
|
||||
const ids = raw.map((entry) => entry.trim()).filter(Boolean);
|
||||
const ids = normalizeStringEntries(raw);
|
||||
if (ids.length === 0) {
|
||||
throw new Error("At least one sticker id is required");
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/realtime-voice";
|
||||
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { asBoolean, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { maybeControlDiscordVoiceAgentRun } from "./agent-control.js";
|
||||
import {
|
||||
convertDiscordPcm48kStereoToRealtimePcm24kMono,
|
||||
@@ -197,8 +198,7 @@ function readProviderConfigBoolean(
|
||||
config: RealtimeVoiceProviderConfig | undefined,
|
||||
key: string,
|
||||
): boolean | undefined {
|
||||
const value = config?.[key];
|
||||
return typeof value === "boolean" ? value : undefined;
|
||||
return asBoolean(config?.[key]);
|
||||
}
|
||||
|
||||
export function resolveDiscordVoiceMode(voice: DiscordAccountConfig["voice"]): DiscordVoiceMode {
|
||||
@@ -324,7 +324,7 @@ function resolveDiscordRealtimeWakeNames(params: {
|
||||
const configured = rawConfigured
|
||||
.map((name) => normalizeSupportedRealtimeVoiceActivationName(name))
|
||||
.filter((name): name is string => Boolean(name));
|
||||
return sortRealtimeVoiceActivationNames(Array.from(new Set(configured)));
|
||||
return sortRealtimeVoiceActivationNames(uniqueStrings(configured));
|
||||
}
|
||||
const agent = params.cfg.agents?.list?.find((candidate) => candidate.id === params.agentId);
|
||||
const configuredAgentNames = [agent?.name, agent?.identity?.name]
|
||||
@@ -339,7 +339,7 @@ function resolveDiscordRealtimeWakeNames(params: {
|
||||
: [normalizeSupportedRealtimeVoiceActivationName(params.agentId), ...productWakeNames].filter(
|
||||
(name): name is string => Boolean(name),
|
||||
);
|
||||
return sortRealtimeVoiceActivationNames(Array.from(new Set(defaults)));
|
||||
return sortRealtimeVoiceActivationNames(uniqueStrings(defaults));
|
||||
}
|
||||
|
||||
function matchesPendingAgentProxyQuestion(consultMessage: string, question: string): boolean {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/agent-runtime";
|
||||
import type { OpenClawConfig, TtsConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { parseTtsDirectives } from "openclaw/plugin-sdk/speech";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { getDiscordRuntime } from "../runtime.js";
|
||||
import { sanitizeVoiceReplyTextForSpeech } from "./sanitize.js";
|
||||
|
||||
@@ -40,7 +40,7 @@ function mergeTtsConfig(base: TtsConfig, override?: TtsConfig): TtsConfig {
|
||||
const baseProviders = base.providers ?? {};
|
||||
const overrideProviders = override.providers ?? {};
|
||||
const mergedProviders = Object.fromEntries(
|
||||
[...new Set([...Object.keys(baseProviders), ...Object.keys(overrideProviders)])].map(
|
||||
uniqueStrings([...Object.keys(baseProviders), ...Object.keys(overrideProviders)]).map(
|
||||
(providerId) => {
|
||||
const baseProvider = baseProviders[providerId] ?? {};
|
||||
const overrideProvider = overrideProviders[providerId] ?? {};
|
||||
|
||||
@@ -7,7 +7,11 @@ import {
|
||||
type RealtimeTranscriptionWebSocketTransport,
|
||||
} from "openclaw/plugin-sdk/realtime-transcription";
|
||||
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
asOptionalRecord as readRecord,
|
||||
normalizeOptionalString,
|
||||
parseFiniteNumber as readFiniteNumber,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolveElevenLabsApiKeyWithProfileFallback } from "./config-api.js";
|
||||
import { normalizeElevenLabsBaseUrl } from "./shared.js";
|
||||
|
||||
@@ -57,28 +61,12 @@ const ELEVENLABS_REALTIME_MAX_RECONNECT_ATTEMPTS = 5;
|
||||
const ELEVENLABS_REALTIME_RECONNECT_DELAY_MS = 1000;
|
||||
const ELEVENLABS_REALTIME_MAX_QUEUED_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function readNestedElevenLabsConfig(rawConfig: RealtimeTranscriptionProviderConfig) {
|
||||
const raw = readRecord(rawConfig);
|
||||
const providers = readRecord(raw?.providers);
|
||||
return readRecord(providers?.elevenlabs ?? raw?.elevenlabs ?? raw) ?? {};
|
||||
}
|
||||
|
||||
function readFiniteNumber(value: unknown): number | undefined {
|
||||
const next =
|
||||
typeof value === "number"
|
||||
? value
|
||||
: typeof value === "string"
|
||||
? Number.parseFloat(value)
|
||||
: undefined;
|
||||
return Number.isFinite(next) ? next : undefined;
|
||||
}
|
||||
|
||||
function normalizeCommitStrategy(value: unknown): "manual" | "vad" | undefined {
|
||||
const normalized = normalizeOptionalString(value)?.toLowerCase();
|
||||
if (!normalized) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
|
||||
} from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import {
|
||||
isRecord,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
@@ -62,10 +63,6 @@ function matchesTrustedHostSuffix(hostname: string, trustedSuffix: string): bool
|
||||
return normalizedHost === normalizedSuffix || normalizedHost.endsWith(`.${normalizedSuffix}`);
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function parseFalImageGenerationResponse(payload: unknown): {
|
||||
images: Record<string, unknown>[];
|
||||
prompt?: string;
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
|
||||
} from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import {
|
||||
isRecord,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
@@ -97,10 +98,6 @@ export function setFalVideoFetchGuardForTesting(impl: typeof fetchWithSsrFGuard
|
||||
falFetchGuard = impl ?? fetchWithSsrFGuard;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function normalizeFalVideoUrl(value: unknown): string | undefined {
|
||||
const normalized = normalizeOptionalString(value);
|
||||
if (!normalized && value !== undefined && value !== null) {
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
warnMissingProviderGroupPolicyFallbackOnce,
|
||||
} from "openclaw/plugin-sdk/runtime-group-policy";
|
||||
import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolveFeishuRuntimeAccount } from "./accounts.js";
|
||||
import {
|
||||
checkBotMentioned,
|
||||
@@ -595,7 +595,7 @@ export async function handleFeishuMessage(params: {
|
||||
const configAllowFrom = feishuCfg?.allowFrom ?? [];
|
||||
const rawBroadcastAgents = isGroup ? resolveBroadcastAgents(cfg, ctx.chatId) : null;
|
||||
const broadcastAgents = rawBroadcastAgents
|
||||
? [...new Set(rawBroadcastAgents.map((id) => normalizeAgentId(id)))]
|
||||
? uniqueStrings(rawBroadcastAgents.map((id) => normalizeAgentId(id)))
|
||||
: null;
|
||||
|
||||
// Parse message create_time early so every downstream consumer (pending
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
isRecord as sharedIsRecord,
|
||||
normalizeOptionalString,
|
||||
normalizeStringEntries,
|
||||
readStringValue,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { FEISHU_COMMENT_FILE_TYPES, type CommentFileType } from "./comment-target.js";
|
||||
@@ -237,10 +238,7 @@ function parseCommentLinkedDocumentPath(pathname: string): {
|
||||
urlKind: ParsedCommentResolvedDocumentType | "wiki";
|
||||
token: string;
|
||||
} | null {
|
||||
const segments = pathname
|
||||
.split("/")
|
||||
.map((segment) => segment.trim())
|
||||
.filter(Boolean);
|
||||
const segments = normalizeStringEntries(pathname.split("/"));
|
||||
const offset = segments[0]?.toLowerCase() === "space" ? 1 : 0;
|
||||
const kind = COMMENT_LINK_KIND_ALIASES.get(segments[offset]?.toLowerCase() ?? "");
|
||||
const token = normalizeString(segments[offset + 1]);
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { asNullableRecord as readRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { FeishuMessageEvent } from "./event-types.js";
|
||||
import { normalizeFeishuExternalKey } from "./external-keys.js";
|
||||
import { parsePostContent } from "./post.js";
|
||||
|
||||
type FeishuMessageDedupeInput = Pick<FeishuMessageEvent, "message">;
|
||||
|
||||
function readRecord(value: unknown): Record<string, unknown> | null {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: null;
|
||||
}
|
||||
|
||||
function readExternalKey(value: unknown): string | undefined {
|
||||
return normalizeFeishuExternalKey(typeof value === "string" ? value : "");
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { basename } from "node:path";
|
||||
import type * as Lark from "@larksuiteoapi/node-sdk";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { extensionForMime } from "openclaw/plugin-sdk/media-mime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { Type } from "typebox";
|
||||
import type { OpenClawPluginApi } from "../runtime-api.js";
|
||||
import { listEnabledFeishuAccounts } from "./accounts.js";
|
||||
@@ -235,7 +235,8 @@ function normalizeConvertedBlockTree(
|
||||
|
||||
const rootIds = (
|
||||
firstLevelIds && firstLevelIds.length > 0 ? firstLevelIds : inferredTopLevelIds
|
||||
).filter((id, index, arr) => typeof id === "string" && byId.has(id) && arr.indexOf(id) === index);
|
||||
).filter((id): id is string => typeof id === "string" && byId.has(id));
|
||||
const uniqueRootIds = uniqueStrings(rootIds);
|
||||
|
||||
const orderedBlocks: FeishuDocxBlock[] = [];
|
||||
const visited = new Set<string>();
|
||||
@@ -255,7 +256,7 @@ function normalizeConvertedBlockTree(
|
||||
}
|
||||
};
|
||||
|
||||
for (const rootId of rootIds) {
|
||||
for (const rootId of uniqueRootIds) {
|
||||
visit(rootId);
|
||||
}
|
||||
|
||||
@@ -268,7 +269,7 @@ function normalizeConvertedBlockTree(
|
||||
}
|
||||
}
|
||||
|
||||
return { orderedBlocks, rootIds: rootIds.filter((id): id is string => typeof id === "string") };
|
||||
return { orderedBlocks, rootIds: uniqueRootIds };
|
||||
}
|
||||
|
||||
async function insertBlocks(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isRecord, readStringValue as readString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { ClawdbotConfig, HistoryEntry, RuntimeEnv } from "../runtime-api.js";
|
||||
import { handleFeishuMessage, type FeishuMessageEvent } from "./bot.js";
|
||||
import { maybeHandleFeishuQuickActionMenu } from "./card-ux-launcher.js";
|
||||
@@ -18,18 +19,10 @@ type FeishuBotMenuEvent = {
|
||||
};
|
||||
};
|
||||
|
||||
function readString(value: unknown): string | undefined {
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
function readStringOrNumber(value: unknown): string | number | undefined {
|
||||
return typeof value === "string" || typeof value === "number" ? value : undefined;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function parseFeishuBotMenuEvent(value: unknown): FeishuBotMenuEvent | null {
|
||||
if (!isRecord(value)) {
|
||||
return null;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { asBoolean as readBoolean } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { ClawdbotConfig } from "../runtime-api.js";
|
||||
import { raceWithTimeoutAndAbort } from "./async.js";
|
||||
import { createFeishuClient } from "./client.js";
|
||||
@@ -163,10 +164,6 @@ type ResolvedWholeCommentTimelineEntry = {
|
||||
content: ParsedCommentContent;
|
||||
};
|
||||
|
||||
function readBoolean(value: unknown): boolean | undefined {
|
||||
return typeof value === "boolean" ? value : undefined;
|
||||
}
|
||||
|
||||
function safeJsonStringify(value: unknown): string {
|
||||
try {
|
||||
return JSON.stringify(value);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isRecord, readStringValue as readString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { ClawdbotConfig, HistoryEntry, PluginRuntime, RuntimeEnv } from "../runtime-api.js";
|
||||
import { resolveFeishuMessageDedupeKey } from "./dedupe-key.js";
|
||||
import type { FeishuMessageEvent } from "./event-types.js";
|
||||
@@ -9,14 +10,6 @@ import {
|
||||
import { createSequentialQueue } from "./sequential-queue.js";
|
||||
import type { FeishuChatType } from "./types.js";
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function readString(value: unknown): string | undefined {
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
type FeishuMessageReceiveHandlerContext = {
|
||||
cfg: ClawdbotConfig;
|
||||
core: PluginRuntime;
|
||||
|
||||
@@ -16,7 +16,11 @@ import {
|
||||
sendTextMediaPayload,
|
||||
} from "openclaw/plugin-sdk/reply-payload";
|
||||
import { statRegularFileSync } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
isRecord,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeStringEntries,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolveFeishuAccount } from "./accounts.js";
|
||||
import { createFeishuClient } from "./client.js";
|
||||
import { cleanupAmbientCommentTypingReaction } from "./comment-reaction.js";
|
||||
@@ -80,10 +84,6 @@ function shouldUseCard(text: string): boolean {
|
||||
return /```[\s\S]*?```/.test(text) || /\|.+\|[\r\n]+\|[-:| ]+\|/.test(text);
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function markRenderedFeishuCard(card: Record<string, unknown>): Record<string, unknown> {
|
||||
Object.defineProperty(card, RENDERED_FEISHU_CARD, {
|
||||
value: true,
|
||||
@@ -92,6 +92,21 @@ function markRenderedFeishuCard(card: Record<string, unknown>): Record<string, u
|
||||
return card;
|
||||
}
|
||||
|
||||
function escapeFeishuCardMarkdownText(text: string): string {
|
||||
return text.replace(/[&<>]/g, (char) => {
|
||||
switch (char) {
|
||||
case "&":
|
||||
return "&";
|
||||
case "<":
|
||||
return "<";
|
||||
case ">":
|
||||
return ">";
|
||||
default:
|
||||
return char;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resolveSafeFeishuButtonUrl(url: unknown): string | undefined {
|
||||
const trimmed = typeof url === "string" ? url.trim() : "";
|
||||
if (!trimmed) {
|
||||
@@ -179,18 +194,7 @@ function sanitizeNativeFeishuCardElements(element: unknown): Record<string, unkn
|
||||
return [
|
||||
{
|
||||
tag: "markdown",
|
||||
content: element.content.replace(/[&<>]/g, (char) => {
|
||||
switch (char) {
|
||||
case "&":
|
||||
return "&";
|
||||
case "<":
|
||||
return "<";
|
||||
case ">":
|
||||
return ">";
|
||||
default:
|
||||
return char;
|
||||
}
|
||||
}),
|
||||
content: escapeFeishuCardMarkdownText(element.content),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -527,9 +531,7 @@ export const feishuOutbound: ChannelOutboundAdapter = {
|
||||
});
|
||||
}
|
||||
|
||||
const mediaUrls = resolvePayloadMediaUrls(ctx.payload)
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean);
|
||||
const mediaUrls = normalizeStringEntries(resolvePayloadMediaUrls(ctx.payload));
|
||||
return attachChannelToResult(
|
||||
"feishu",
|
||||
await sendPayloadMediaSequenceAndFinalize({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
||||
import {
|
||||
isRecord,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalLowercaseString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
@@ -67,10 +68,6 @@ function isWithdrawnReplyError(err: unknown): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
type FeishuCreateMessageClient = {
|
||||
im: {
|
||||
message: {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
type OpenClawConfig,
|
||||
type SecretInput,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import { normalizeOptionalString as normalizeString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolveDefaultFeishuAccountId, resolveFeishuAccount } from "./accounts.js";
|
||||
import type { AppRegistrationResult } from "./app-registration.js";
|
||||
import type { FeishuConfig, FeishuDomain } from "./types.js";
|
||||
@@ -27,14 +28,6 @@ const FEISHU_SETUP_FLOW_KEY = "_flow";
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function normalizeString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
function isFeishuConfigured(cfg: OpenClawConfig): boolean {
|
||||
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import {
|
||||
asNullableRecord as asRecord,
|
||||
normalizeOptionalLowercaseString as normalizeProviderId,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
type LegacyConfigRule = {
|
||||
path: Array<string | number>;
|
||||
@@ -6,16 +10,6 @@ type LegacyConfigRule = {
|
||||
match: (value: unknown) => boolean;
|
||||
};
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: null;
|
||||
}
|
||||
|
||||
function normalizeProviderId(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim() ? value.trim().toLowerCase() : undefined;
|
||||
}
|
||||
|
||||
function hasOwn(record: Record<string, unknown>, key: string): boolean {
|
||||
return Object.prototype.hasOwnProperty.call(record, key);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import {
|
||||
type RealtimeVoiceAgentConsultToolPolicy,
|
||||
} from "openclaw/plugin-sdk/realtime-voice";
|
||||
import {
|
||||
asRecord,
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
normalizeOptionalTrimmedStringList,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export type GoogleMeetTransport = "chrome" | "chrome-node" | "twilio";
|
||||
@@ -260,12 +262,6 @@ const GOOGLE_MEET_PREVIEW_ACK_KEYS = [
|
||||
"GOOGLE_MEET_PREVIEW_ACK",
|
||||
] as const;
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: {};
|
||||
}
|
||||
|
||||
function resolveBoolean(value: unknown, fallback: boolean): boolean {
|
||||
return typeof value === "boolean" ? value : fallback;
|
||||
}
|
||||
@@ -318,13 +314,7 @@ function readEnvNumber(env: NodeJS.ProcessEnv, keys: readonly string[]): number
|
||||
}
|
||||
|
||||
function resolveStringArray(value: unknown): string[] | undefined {
|
||||
if (!Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = value
|
||||
.map((entry) => normalizeOptionalString(entry))
|
||||
.filter((entry): entry is string => Boolean(entry));
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
return normalizeOptionalTrimmedStringList(value);
|
||||
}
|
||||
|
||||
function resolveProvidersConfig(value: unknown): Record<string, Record<string, unknown>> {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { exportGoogleDriveDocumentText, extractGoogleDriveDocumentId } from "./drive.js";
|
||||
import { googleApiError } from "./google-api-errors.js";
|
||||
|
||||
@@ -797,9 +798,10 @@ function mergeAttendanceRows(
|
||||
grouped.set(key, { ...row, participants: [row.participant] });
|
||||
continue;
|
||||
}
|
||||
existing.participants = [
|
||||
...new Set([...(existing.participants ?? [existing.participant]), row.participant]),
|
||||
];
|
||||
existing.participants = uniqueStrings([
|
||||
...(existing.participants ?? [existing.participant]),
|
||||
row.participant,
|
||||
]);
|
||||
existing.sessions.push(...row.sessions);
|
||||
existing.displayName ??= row.displayName;
|
||||
existing.user ??= row.user;
|
||||
|
||||
@@ -2,6 +2,10 @@ import { spawn, spawnSync, type ChildProcess } from "node:child_process";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { setTimeout as sleep } from "node:timers/promises";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import {
|
||||
asRecord,
|
||||
normalizeOptionalString as readString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND,
|
||||
DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND,
|
||||
@@ -33,16 +37,6 @@ type NodeBridgeSession = {
|
||||
|
||||
const sessions = new Map<string, NodeBridgeSession>();
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: {};
|
||||
}
|
||||
|
||||
function readString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function readStringArray(value: unknown): string[] | undefined {
|
||||
if (!Array.isArray(value)) {
|
||||
return undefined;
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import type { PluginRuntime, RuntimeLogger } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import { sleep } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type {
|
||||
GoogleMeetConfig,
|
||||
GoogleMeetMode,
|
||||
@@ -207,7 +207,7 @@ function collectChromeAudioCommands(config: GoogleMeetConfig): string[] {
|
||||
config.chrome.audioOutputCommand?.[0],
|
||||
config.chrome.bargeInInputCommand?.[0],
|
||||
];
|
||||
return [...new Set(commands.filter((value): value is string => Boolean(value?.trim())))];
|
||||
return uniqueStrings(commands.filter((value): value is string => Boolean(value?.trim())));
|
||||
}
|
||||
|
||||
async function commandExists(runtime: PluginRuntime, command: string): Promise<boolean> {
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { asRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { GoogleMeetConfig, GoogleMeetMode, GoogleMeetTransport } from "./config.js";
|
||||
|
||||
type SetupCheck = {
|
||||
@@ -273,13 +274,3 @@ export function addGoogleMeetSetupCheck(
|
||||
checks,
|
||||
};
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: {};
|
||||
}
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { callGatewayFromCli } from "openclaw/plugin-sdk/gateway-runtime";
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import type { RuntimeLogger } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { GoogleMeetConfig, GoogleMeetMode } from "../config.js";
|
||||
import {
|
||||
startNodeAgentAudioBridge,
|
||||
@@ -313,7 +314,7 @@ function mergeBrowserNotes(
|
||||
}
|
||||
return {
|
||||
...browser,
|
||||
notes: [...new Set([...(browser.notes ?? []), ...notes])],
|
||||
notes: uniqueStrings([...(browser.notes ?? []), ...notes]),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
withRemoteHttpResponse,
|
||||
} from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
|
||||
import { createProviderHttpError } from "openclaw/plugin-sdk/provider-http";
|
||||
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { GeminiEmbeddingClient, GeminiTextEmbeddingRequest } from "./embedding-provider.js";
|
||||
|
||||
type EmbeddingBatchExecutionParams = {
|
||||
@@ -221,11 +222,9 @@ function parseGeminiBatchOutput(text: string): GeminiBatchOutputLine[] {
|
||||
if (!text.trim()) {
|
||||
return [];
|
||||
}
|
||||
return text
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
.map((line) => JSON.parse(line) as GeminiBatchOutputLine);
|
||||
return normalizeStringEntries(text.split("\n")).map(
|
||||
(line) => JSON.parse(line) as GeminiBatchOutputLine,
|
||||
);
|
||||
}
|
||||
|
||||
async function waitForGeminiBatch(params: {
|
||||
|
||||
@@ -20,7 +20,10 @@ import {
|
||||
readProviderJsonObjectResponse,
|
||||
} from "openclaw/plugin-sdk/provider-http";
|
||||
import type { SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
asOptionalRecord as asRecord,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export type GeminiEmbeddingClient = {
|
||||
baseUrl: string;
|
||||
@@ -91,12 +94,6 @@ type GeminiEmbeddingRequest = {
|
||||
};
|
||||
export type GeminiTextEmbeddingRequest = GeminiEmbeddingRequest;
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function malformedGeminiEmbeddingResponse(): Error {
|
||||
return new Error("gemini embeddings failed: malformed JSON response");
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
sanitizeConfiguredModelProviderRequest,
|
||||
} from "openclaw/plugin-sdk/provider-http";
|
||||
import {
|
||||
isRecord,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
@@ -41,10 +42,6 @@ const GOOGLE_SUPPORTED_ASPECT_RATIOS = [
|
||||
|
||||
const GOOGLE_IMAGE_MALFORMED_RESPONSE = "Google image generation response malformed";
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function normalizeGoogleImageModel(model: string | undefined): string {
|
||||
const trimmed = model?.trim();
|
||||
return normalizeGoogleModelId(trimmed || DEFAULT_GOOGLE_IMAGE_MODEL);
|
||||
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
ProviderThinkingProfile,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-types";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { normalizeAntigravityModelId, normalizeGoogleModelId } from "./model-id.js";
|
||||
import { isGoogleGemini3ProModel, isGoogleGemini3ThinkingLevelModel } from "./thinking-api.js";
|
||||
|
||||
@@ -17,10 +18,6 @@ type GoogleProviderConfigLike = GoogleApiCarrier & {
|
||||
export const DEFAULT_GOOGLE_API_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
||||
const GOOGLE_MODEL_ID_PROVIDERS = new Set(["google", "google-gemini-cli", "google-vertex"]);
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function trimTrailingSlashes(value: string): string {
|
||||
return value.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
@@ -37,7 +37,11 @@ import {
|
||||
resamplePcm,
|
||||
} from "openclaw/plugin-sdk/realtime-voice";
|
||||
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
asBoolean,
|
||||
asFiniteNumber,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { createGoogleGenAI } from "./google-genai-runtime.js";
|
||||
|
||||
const GOOGLE_REALTIME_DEFAULT_MODEL = "gemini-2.5-flash-native-audio-preview-12-2025";
|
||||
@@ -126,14 +130,6 @@ function trimToUndefined(value: unknown): string | undefined {
|
||||
return normalizeOptionalString(value);
|
||||
}
|
||||
|
||||
function asFiniteNumber(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function asBoolean(value: unknown): boolean | undefined {
|
||||
return typeof value === "boolean" ? value : undefined;
|
||||
}
|
||||
|
||||
function asSensitivity(value: unknown): GoogleRealtimeSensitivity | undefined {
|
||||
const normalized = normalizeOptionalString(value)?.toLowerCase();
|
||||
return normalized === "low" || normalized === "high" ? normalized : undefined;
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
wrapWebContent,
|
||||
writeCachedSearchPayload,
|
||||
} from "openclaw/plugin-sdk/provider-web-search";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
resolveGeminiConfig,
|
||||
resolveGeminiBaseUrl,
|
||||
@@ -60,10 +61,6 @@ type GeminiGroundingResponse = {
|
||||
};
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function throwMalformedGeminiResponse(): never {
|
||||
throw new Error("Gemini API error: malformed JSON response");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
isRecord,
|
||||
normalizeOptionalString as trimToUndefined,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { normalizeGoogleApiBaseUrl } from "../provider-policy.js";
|
||||
|
||||
const DEFAULT_GEMINI_WEB_SEARCH_MODEL = "gemini-2.5-flash";
|
||||
@@ -10,14 +14,6 @@ export type GeminiConfig = {
|
||||
providerBaseUrl?: unknown;
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function trimToUndefined(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
export function resolveGeminiConfig(searchConfig?: Record<string, unknown>): GeminiConfig {
|
||||
const gemini = searchConfig?.gemini;
|
||||
return isRecord(gemini) ? gemini : {};
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
type WebSearchProviderPlugin,
|
||||
type WebSearchProviderToolDefinition,
|
||||
} from "openclaw/plugin-sdk/provider-web-search-config-contract";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
resolveGeminiApiKey,
|
||||
resolveGeminiBaseUrl,
|
||||
@@ -66,10 +67,6 @@ function createGeminiToolDefinition(
|
||||
};
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function resolveGoogleModelProviderConfig(
|
||||
config?: OpenClawConfig,
|
||||
): Record<string, unknown> | undefined {
|
||||
|
||||
@@ -21,7 +21,10 @@ import {
|
||||
transformTransportMessages,
|
||||
type WritableTransportStream,
|
||||
} from "openclaw/plugin-sdk/provider-transport-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { parseGeminiAuth } from "./gemini-auth.js";
|
||||
import { normalizeGoogleApiBaseUrl } from "./provider-policy.js";
|
||||
import {
|
||||
@@ -140,10 +143,6 @@ type GoogleSseChunk = {
|
||||
let toolCallCounter = 0;
|
||||
const GEMINI_THOUGHT_SIGNATURE_VALIDATOR_SKIP = "skip_thought_signature_validator";
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function requiresToolCallId(modelId: string): boolean {
|
||||
return modelId.startsWith("claude-") || modelId.startsWith("gpt-oss-");
|
||||
}
|
||||
@@ -202,9 +201,7 @@ function hasGeminiThoughtSignatureTruncationFootprint(value: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function sanitizeGeminiThoughtSignature(
|
||||
thoughtSignature: string | undefined,
|
||||
): string | undefined {
|
||||
function sanitizeGeminiThoughtSignature(thoughtSignature: string | undefined): string | undefined {
|
||||
if (typeof thoughtSignature !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
@@ -552,9 +549,7 @@ function convertGoogleMessages(model: GoogleTransportModel, context: Context) {
|
||||
: undefined;
|
||||
parts.push({
|
||||
text: sanitizeTransportPayloadText(block.text),
|
||||
...(sanitizedTextSignature
|
||||
? { thoughtSignature: sanitizedTextSignature }
|
||||
: {}),
|
||||
...(sanitizedTextSignature ? { thoughtSignature: sanitizedTextSignature } : {}),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
type GoogleAuthorizedUserCredentials = {
|
||||
type: "authorized_user";
|
||||
@@ -46,10 +47,6 @@ export function resetGoogleVertexAuthorizedUserTokenCacheForTest(): void {
|
||||
cachedGoogleVertexAdcToken = undefined;
|
||||
}
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
export function isGoogleVertexCredentialsMarker(
|
||||
apiKey: string | undefined,
|
||||
): apiKey is undefined | typeof GCP_VERTEX_CREDENTIALS_MARKER {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { extname, join } from "node:path";
|
||||
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
||||
import { createIMessageRpcClient } from "./client.js";
|
||||
import { extractMarkdownFormatRuns } from "./markdown-format.js";
|
||||
@@ -222,10 +223,7 @@ async function runIMessageCliJson(
|
||||
if (killEscalation) {
|
||||
clearTimeout(killEscalation);
|
||||
}
|
||||
const lines = stdout
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
const lines = normalizeStringEntries(stdout.split(/\r?\n/));
|
||||
const last = lines.at(-1);
|
||||
let parsed: Record<string, unknown> | null = null;
|
||||
if (last) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import { createChannelHistoryWindow, type HistoryEntry } from "openclaw/plugin-s
|
||||
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
|
||||
import { evaluateSupplementalContextVisibility } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { sanitizeTerminalText } from "openclaw/plugin-sdk/text-chunking";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import { resolveIMessageConversationRoute } from "../conversation-route.js";
|
||||
@@ -94,7 +95,7 @@ function mergeIMessageGroupAllowFromWithLegacyChatTargets(params: {
|
||||
if (legacyChatTargets.length === 0) {
|
||||
return params.groupAllowFrom;
|
||||
}
|
||||
return Array.from(new Set([...params.groupAllowFrom, ...legacyChatTargets]));
|
||||
return uniqueStrings([...params.groupAllowFrom, ...legacyChatTargets]);
|
||||
}
|
||||
|
||||
const imessageIngressIdentity = defineStableChannelIngressIdentity({
|
||||
|
||||
@@ -4,7 +4,10 @@ import { runCommandWithTimeout } from "openclaw/plugin-sdk/process-runtime";
|
||||
import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { detectBinary } from "openclaw/plugin-sdk/setup";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeStringEntries,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { createIMessageRpcClient } from "./client.js";
|
||||
import { DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS } from "./constants.js";
|
||||
import {
|
||||
@@ -107,10 +110,7 @@ function parseStatusPayload(stdout: string): {
|
||||
payload: Record<string, unknown> | null;
|
||||
firstLineSnippet?: string;
|
||||
} {
|
||||
const lines = stdout
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
const lines = normalizeStringEntries(stdout.split(/\r?\n/));
|
||||
for (const line of lines.toReversed()) {
|
||||
try {
|
||||
const value = JSON.parse(line);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeStringEntriesLower,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { hasIrcControlChars } from "./control-chars.js";
|
||||
import type { IrcInboundMessage } from "./types.js";
|
||||
@@ -93,7 +94,7 @@ export function resolveIrcAllowlistMatch(params: {
|
||||
message: IrcInboundMessage;
|
||||
allowNameMatching?: boolean;
|
||||
}): { allowed: boolean; source?: string } {
|
||||
const allowFrom = new Set(params.allowFrom.map(normalizeLowercaseStringOrEmpty).filter(Boolean));
|
||||
const allowFrom = new Set(normalizeStringEntriesLower(params.allowFrom));
|
||||
if (allowFrom.has("*")) {
|
||||
return { allowed: true, source: "wildcard" };
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ export function setIrcGroupAccess(
|
||||
return updateIrcAccountConfig(cfg, accountId, { enabled: true, groupPolicy: policy });
|
||||
}
|
||||
const normalizedEntries = [
|
||||
...new Set(entries.map((entry) => normalizeGroupEntry(entry)).filter(Boolean)),
|
||||
...new Set(entries.flatMap((entry) => normalizeGroupEntry(entry) ?? [])),
|
||||
];
|
||||
const groups = Object.fromEntries(normalizedEntries.map((entry) => [entry, {}]));
|
||||
return updateIrcAccountConfig(cfg, accountId, {
|
||||
|
||||
@@ -14,7 +14,9 @@ import {
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import {
|
||||
normalizeOptionalString,
|
||||
normalizeStringEntries,
|
||||
normalizeStringifiedOptionalString,
|
||||
uniqueStrings,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolveDefaultIrcAccountId, resolveIrcAccount } from "./accounts.js";
|
||||
import {
|
||||
@@ -40,10 +42,7 @@ const USE_ENV_FLAG = "__ircUseEnv";
|
||||
const TLS_FLAG = "__ircTls";
|
||||
|
||||
function parseListInput(raw: string): string[] {
|
||||
return raw
|
||||
.split(/[\n,;]+/g)
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean);
|
||||
return normalizeStringEntries(raw.split(/[\n,;]+/g));
|
||||
}
|
||||
|
||||
function normalizeGroupEntry(raw: string): string | null {
|
||||
@@ -74,10 +73,9 @@ const promptIrcAllowFrom = createPromptParsedAllowFromForAccount<CoreConfig>({
|
||||
message: t("wizard.irc.allowFromPrompt"),
|
||||
placeholder: "alice, bob!ident@example.org",
|
||||
parseEntries: (raw) => ({
|
||||
entries: parseListInput(raw)
|
||||
.map((entry) => normalizeIrcAllowEntry(entry))
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean),
|
||||
entries: normalizeStringEntries(
|
||||
parseListInput(raw).map((entry) => normalizeIrcAllowEntry(entry)),
|
||||
),
|
||||
}),
|
||||
getExistingAllowFrom: ({ cfg }) => cfg.channels?.irc?.allowFrom ?? [],
|
||||
applyAllowFrom: ({ cfg, allowFrom }) => setIrcAllowFrom(cfg, allowFrom),
|
||||
@@ -372,7 +370,11 @@ export const ircSetupWizard: ChannelSetupWizard = {
|
||||
setPolicy: ({ cfg, accountId, policy }) =>
|
||||
setIrcGroupAccess(cfg as CoreConfig, accountId, policy, [], normalizeGroupEntry),
|
||||
resolveAllowlist: async ({ entries }) =>
|
||||
[...new Set(entries.map((entry) => normalizeGroupEntry(entry)).filter(Boolean))] as string[],
|
||||
uniqueStrings(
|
||||
entries
|
||||
.map((entry) => normalizeGroupEntry(entry))
|
||||
.filter((entry): entry is string => Boolean(entry)),
|
||||
),
|
||||
applyAllowlist: ({ cfg, accountId, resolved }) =>
|
||||
setIrcGroupAccess(
|
||||
cfg as CoreConfig,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
listLineAccountIds,
|
||||
resolveDefaultLineAccountId,
|
||||
@@ -21,9 +22,5 @@ export const lineConfigAdapter = createScopedChannelConfigAdapter<
|
||||
defaultAccountId: resolveDefaultLineAccountId,
|
||||
clearBaseFields: ["channelSecret", "tokenFile", "secretFile"],
|
||||
resolveAllowFrom: (account) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean)
|
||||
.map(normalizeLineAllowFrom),
|
||||
formatAllowFrom: (allowFrom) => normalizeStringEntries(allowFrom).map(normalizeLineAllowFrom),
|
||||
});
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export function buildLineQuickReplyFallbackText(labels: readonly string[] | undefined): string {
|
||||
const normalized = (labels ?? [])
|
||||
.map((label) => label.trim())
|
||||
.filter(Boolean)
|
||||
.slice(0, 13);
|
||||
const normalized = normalizeStringEntries(labels ?? []).slice(0, 13);
|
||||
if (normalized.length === 0) {
|
||||
return "Choose an option.";
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user