mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor: consolidate coercion ownership (#122299)
* refactor: consolidate coercion ownership Centralize four canonical coercion helpers, migrate exact core and plugin duplicates through narrow Plugin SDK facades, and enforce declaration and plugin-normalization ownership boundaries. The sweep adds eight focused SDK exports while deleting more production and tooling code than it adds. User-visible behavior is unchanged except for safer equivalent object and UI parsing at existing boundaries. * fix: guard integer option ownership Register resolveIntegerOption with the canonical function owner and extend the declaration-guard fixture so future local duplicates fail validation. * fix: keep integer helpers on numeric facade Remove the unshipped duplicate string-coerce exports and route every affected plugin consumer through the existing number-runtime contract. * fix: point numeric coercion to number runtime Make boundary and declaration diagnostics recommend the canonical numeric facade, with failing-before coverage for both guidance paths.
This commit is contained in:
committed by
GitHub
parent
88dba70ec5
commit
964c8c84c1
@@ -1 +1 @@
|
||||
{"contentHash":"ca54298c1a0adf9550f58fc13881d7e7f37e4c755a5f917a595bc4f0177d9e20","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"}
|
||||
{"contentHash":"b017f177c844e59f8c9771773fd9fe13bd49777097859ecec3e90057d6cc3709","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"00743220201e85945df77cc8e276a4842f93602c971e06459ea00dd029f063ff","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"}
|
||||
{"contentHash":"9695a1112df503dd92e36a5502aa3930289d6828a562917ea5b8d6b7c25a0755","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"b0d043c2fc4d36023fa7bd6472c9b9cc5f0ee17ff4375d598e59fecb45cbe85c","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"}
|
||||
{"contentHash":"96449168bf95c5c22ff75ba7d50d21cb2d9c73666c5ceef95450d76273cd36ca","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"555310d087ce5a7a801f6291ee837283eb182e9bf86f294e11dc0d4559c9c268","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"}
|
||||
{"contentHash":"f1e6d92d125cdaca72317819b5ba7619cbfcb67ee72752bb009af78a8f753b38","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"08111748e2944e6bf1571eda856a46a00c813e2d90fd3da04a9c473b033bc38c","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"}
|
||||
{"contentHash":"1b3770f22431dbbce8ffcbffd4496a95d2afaac82172aa48b73c9c8bceac7ca5","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"4561d105811249b490dcc691888e082fbb5365b0652fee8e97d272c55f7c1eba","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"}
|
||||
{"contentHash":"fdea77b1259c9799177f9c2b327a6d759058d0df0ef339834b6c0a6a9d04b130","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"8bef6c7c70abc4745029421fef29125646effa27bb8947541e0ca9bea4e2da9b","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"}
|
||||
{"contentHash":"ddc4dd3f139c5858912bad00560051731ef386eaec43957343b01f76224ecca3","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"140e69b3a97558ca94edd1fc83fc528e33565d0b3b7b16a2fb7dbf9bd9deb0be","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"}
|
||||
{"contentHash":"8f806aa43ddd9298176cacb5971b8ddac7f2c1d25101deaa92b3c9e1f788e09a","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"af02ae51d5c0941991928b998ecac5584639e50912532082fa0d16fe0a40e43f","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"}
|
||||
{"contentHash":"11138710259d030a81c55a9e506254428328931945b6c6b79fc969e832d56901","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"e0523b4936d7c7ec043265de9e2a8f6289d32dacba0a28c3999ba3a0975dfc21","entrypoint":"error-runtime","importSpecifier":"openclaw/plugin-sdk/error-runtime"}
|
||||
{"contentHash":"1f61b7d845d0172c62a5413cd96559796dbe338b38866f91782ff74485aef96e","entrypoint":"error-runtime","importSpecifier":"openclaw/plugin-sdk/error-runtime"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"de31ab324cc57e8f525cac7559813e83a434c3cb114124388558c0b35e094adc","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"}
|
||||
{"contentHash":"180d24e66df9e1242f1ba78f4aea182c79340280d022db048b227071892fbb59","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"f559df4c86dd30b151db7e159944df99f5870a8564b4ef07adfbff4826737aa4","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"}
|
||||
{"contentHash":"5a99888e906478be43c13f67f99fb2f74c7396eb834202e7d872f0e2ee60f144","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"9f9e1c49a46f04964615be21556834f4d7d6042aedef30812973efbc2af9871c","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"}
|
||||
{"contentHash":"1896a2342b27ee5f055bf77f9a826f76ddc6520c9dce44789d6d612cfcdd17dc","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"b1883cb0c123488b0d8f93934cc27dca32e349d17bab1119e0d273655b368c4a","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"}
|
||||
{"contentHash":"fcdd433db990480373e6caea357c69cbbc754bcbf492fe3371cd21491d9bd04f","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"7923737351da1ca1c0c0b3c3ce3d105a61739f633d27da4cc8b92c957ccf397f","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"}
|
||||
{"contentHash":"9459eb7103be5db943a12ebbf69b62f9a06b60cf5a24df9a8b72fa2bd4242dce","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"14d56ffc88afba3e8c83ace0894f0187860a45b739d562b96e290d2235c45294","entrypoint":"string-coerce-runtime","importSpecifier":"openclaw/plugin-sdk/string-coerce-runtime"}
|
||||
{"contentHash":"347f68c5497a100307cffbb43c2f88e653bed7b7451066b7eb19266dd3c16660","entrypoint":"string-coerce-runtime","importSpecifier":"openclaw/plugin-sdk/string-coerce-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"b2281fac79519417397a7670e25392c7e7e592a84e3ed00459b947b6bef2955b","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"}
|
||||
{"contentHash":"1d4374297f908a74d78873c746fc0624240182a96c28a064a47f91f6260049bb","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"14ae7f9d841c6c4e2702239ed2aeb126f89039df8434bf12773a495486a04ff4","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"}
|
||||
{"contentHash":"f5bf0f3889cd0e4a5b1de4e110098400573d46c508c962c386db3334246bc9fe","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { createRequire } from "node:module";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { readJsonFileWithFallback } from "openclaw/plugin-sdk/json-store";
|
||||
import { isRecord as isConfigRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
parse as parseToml,
|
||||
stringify as stringifyToml,
|
||||
@@ -802,10 +803,6 @@ function extractConfiguredAdapterArgs(params: {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isConfigRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function mergeConfigRecords(
|
||||
base: Record<string, unknown>,
|
||||
override: Record<string, unknown>,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import path from "node:path";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
||||
import {
|
||||
parseStrictPositiveInteger,
|
||||
resolveIntegerOption,
|
||||
} from "openclaw/plugin-sdk/number-runtime";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { isPathInside } from "openclaw/plugin-sdk/security-runtime";
|
||||
import {
|
||||
@@ -52,10 +55,7 @@ function parseOptionalPositiveInt(value: unknown, fallback: number): number {
|
||||
}
|
||||
|
||||
function clampInt(value: number | undefined, fallback: number, min: number, max: number): number {
|
||||
if (!Number.isFinite(value)) {
|
||||
return fallback;
|
||||
}
|
||||
return Math.max(min, Math.min(max, Math.floor(value as number)));
|
||||
return resolveIntegerOption(value, fallback, { min, max });
|
||||
}
|
||||
|
||||
function normalizeTranscriptDir(value: unknown): string {
|
||||
|
||||
@@ -7,7 +7,10 @@ import { join } from "node:path";
|
||||
import type { GoogleAuthOptions } from "google-auth-library";
|
||||
import { resolveProviderEndpoint } from "openclaw/plugin-sdk/provider-http";
|
||||
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString as normalizeOptionalSecretInput,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
const ANTHROPIC_VERTEX_DEFAULT_REGION = "global";
|
||||
const ANTHROPIC_VERTEX_REGION_RE = /^[a-z0-9-]+$/;
|
||||
@@ -19,14 +22,6 @@ type AnthropicVertexAdcCredentials = NonNullable<GoogleAuthOptions["credentials"
|
||||
quota_project_id?: unknown;
|
||||
};
|
||||
|
||||
function normalizeOptionalSecretInput(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
/** Resolve the configured Vertex region, defaulting to global. */
|
||||
export function resolveAnthropicVertexRegion(env: NodeJS.ProcessEnv = process.env): string {
|
||||
const region =
|
||||
|
||||
@@ -144,12 +144,6 @@ function readPerTokenPrice(value: unknown): number | undefined {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function readStringArray(value: unknown): string[] {
|
||||
return Array.isArray(value)
|
||||
? value.filter((item): item is string => typeof item === "string")
|
||||
: [];
|
||||
}
|
||||
|
||||
function applyLiveReasoningEffortCompat(
|
||||
fallbackCompat: ModelCompatConfig,
|
||||
supportsReasoningEffort: boolean,
|
||||
@@ -177,7 +171,7 @@ function projectLiveModel(
|
||||
}
|
||||
|
||||
const hasLiveFeatures = Array.isArray(row.supported_features);
|
||||
const features = new Set(readStringArray(row.supported_features));
|
||||
const features = new Set(filterStringEntries(row.supported_features));
|
||||
const pricing = asNonArrayRecord(row.pricing);
|
||||
const inputPrice = readPerTokenPrice(pricing.prompt);
|
||||
const outputPrice = readPerTokenPrice(pricing.completion);
|
||||
@@ -256,4 +250,4 @@ export function resolveBasetenDynamicModel(modelId: string) {
|
||||
compat: buildBasetenModelCompat(id),
|
||||
};
|
||||
}
|
||||
import { asNonArrayRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { asNonArrayRecord, filterStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { resolveOptionalIntegerOption } from "openclaw/plugin-sdk/number-runtime";
|
||||
/**
|
||||
* Runtime dependency barrel for the Browser agent tool.
|
||||
*
|
||||
@@ -9,11 +10,14 @@ import { getRuntimeConfig } from "./sdk-config.js";
|
||||
export { getRuntimeConfig };
|
||||
/** Resolve global image downscaling for screenshots returned to agent tools. */
|
||||
export function resolveRuntimeImageSanitization(): { maxDimensionPx: number } | undefined {
|
||||
const configured = getRuntimeConfig().agents?.defaults?.imageMaxDimensionPx;
|
||||
if (typeof configured !== "number" || !Number.isFinite(configured)) {
|
||||
const maxDimensionPx = resolveOptionalIntegerOption(
|
||||
getRuntimeConfig().agents?.defaults?.imageMaxDimensionPx,
|
||||
{ min: 1 },
|
||||
);
|
||||
if (maxDimensionPx === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return { maxDimensionPx: Math.max(1, Math.floor(configured)) };
|
||||
return { maxDimensionPx };
|
||||
}
|
||||
export {
|
||||
callGatewayTool,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* Chrome DevTools Protocol URL, fetch, and socket helpers.
|
||||
*
|
||||
* Handles CDP URL normalization, SSRF-guarded HTTP discovery, credential
|
||||
* redaction/headers, and request/response correlation over WebSocket.
|
||||
*/
|
||||
import { createHash } from "node:crypto";
|
||||
import { parseBrowserHttpUrl, redactCdpUrl } from "openclaw/plugin-sdk/browser-config";
|
||||
import { toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
|
||||
import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
@@ -35,8 +35,7 @@ import { normalizeBrowserTimerDelayMs } from "./timer-delay.js";
|
||||
|
||||
const CDP_URL_IN_TEXT_RE = /\b(?:https?|wss?):\/\/[^\s"'<>`]+/gi;
|
||||
|
||||
export { isLoopbackHost };
|
||||
export { parseBrowserHttpUrl, redactCdpUrl };
|
||||
export { isLoopbackHost, parseBrowserHttpUrl, redactCdpUrl };
|
||||
|
||||
/**
|
||||
* Returns true when the URL uses a WebSocket protocol (ws: or wss:).
|
||||
@@ -529,7 +528,7 @@ function createCdpSender(ws: WebSocket, opts?: { commandTimeoutMs?: number }) {
|
||||
} catch (err) {
|
||||
pending.delete(id);
|
||||
clearPendingTimer(entry);
|
||||
reject(err instanceof Error ? err : new Error(String(err)));
|
||||
reject(toStringifiedError(err));
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -549,7 +548,7 @@ function createCdpSender(ws: WebSocket, opts?: { commandTimeoutMs?: number }) {
|
||||
// non-Error branch would require synthetically emitting on the socket,
|
||||
// which the library treats as an unhandled error and hangs the test.
|
||||
/* c8 ignore next */
|
||||
closeWithError(err instanceof Error ? err : new Error(String(err)));
|
||||
closeWithError(toStringifiedError(err));
|
||||
});
|
||||
|
||||
ws.on("message", (data) => {
|
||||
@@ -798,7 +797,7 @@ export async function withCdpSocket<T>(
|
||||
// Error from Node's `ws` library, the latter is already an Error. The
|
||||
// non-Error wrap is defensive and structurally unreachable.
|
||||
/* c8 ignore next */
|
||||
closeWithError(err instanceof Error ? err : new Error(String(err)));
|
||||
closeWithError(toStringifiedError(err));
|
||||
// Cancellation on the final attempt must not become a handshake error.
|
||||
opts?.signal?.throwIfAborted();
|
||||
if (attempt >= maxHandshakeRetries || !shouldRetryCdpHandshakeError(err)) {
|
||||
@@ -821,7 +820,7 @@ export async function withCdpSocket<T>(
|
||||
try {
|
||||
return await fn(send);
|
||||
} catch (err) {
|
||||
closeWithError(err instanceof Error ? err : new Error(String(err)));
|
||||
closeWithError(toStringifiedError(err));
|
||||
throw err;
|
||||
} finally {
|
||||
ws.close();
|
||||
|
||||
@@ -12,11 +12,6 @@ import {
|
||||
type NormalizedChromeMcpProfileOptions,
|
||||
} from "./chrome-mcp-contracts.js";
|
||||
|
||||
function normalizeChromeMcpUserDataDir(userDataDir?: string): string | undefined {
|
||||
const trimmed = userDataDir?.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function normalizeChromeMcpStringList(values?: string[]): string[] {
|
||||
return Array.isArray(values)
|
||||
? values.filter(
|
||||
@@ -35,7 +30,7 @@ export function normalizeChromeMcpOptions(
|
||||
const command = normalizeOptionalString(options.mcpCommand) ?? DEFAULT_CHROME_MCP_COMMAND;
|
||||
return {
|
||||
command,
|
||||
userDataDir: normalizeChromeMcpUserDataDir(options.userDataDir),
|
||||
userDataDir: normalizeOptionalString(options.userDataDir),
|
||||
browserUrl: normalizeOptionalString(options.cdpUrl),
|
||||
extraArgs: normalizeChromeMcpStringList(options.mcpArgs),
|
||||
};
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { asNullableRecord, isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
asNullableRecord,
|
||||
asFiniteNumber,
|
||||
filterStringEntries,
|
||||
isRecord,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
/**
|
||||
* Managed Chrome graphics diagnostics.
|
||||
*
|
||||
@@ -24,11 +30,11 @@ type ChromeGraphicsProbeOptions = {
|
||||
};
|
||||
|
||||
function readChromeString(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
return normalizeOptionalString(value) ?? "";
|
||||
}
|
||||
|
||||
function readChromeNumber(value: unknown): number {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
||||
return asFiniteNumber(value) ?? 0;
|
||||
}
|
||||
|
||||
function readStringRecord(value: unknown): Record<string, string> {
|
||||
@@ -42,12 +48,6 @@ function readStringRecord(value: unknown): Record<string, string> {
|
||||
return Object.fromEntries(entries);
|
||||
}
|
||||
|
||||
function readStringArray(value: unknown): string[] {
|
||||
return Array.isArray(value)
|
||||
? value.filter((item): item is string => typeof item === "string")
|
||||
: [];
|
||||
}
|
||||
|
||||
function readSize(value: unknown): { width: number; height: number } {
|
||||
const size = asNullableRecord(value);
|
||||
return {
|
||||
@@ -162,7 +162,7 @@ function normalizeChromeGraphicsInfo(
|
||||
devices,
|
||||
featureStatus,
|
||||
disabledFeatures,
|
||||
driverBugWorkarounds: readStringArray(gpu.driverBugWorkarounds),
|
||||
driverBugWorkarounds: filterStringEntries(gpu.driverBugWorkarounds),
|
||||
videoDecoding: readVideoDecoding(gpu.videoDecoding),
|
||||
videoEncoding: readVideoEncoding(gpu.videoEncoding),
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
ProviderToolSchemaDiagnostic,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { findUnsupportedSchemaKeywords } from "openclaw/plugin-sdk/provider-tools";
|
||||
import { asOptionalRecord as readRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
const PERPLEXITY_UNSUPPORTED_SCHEMA_KEYWORDS = new Set([
|
||||
"patternProperties",
|
||||
@@ -36,12 +37,6 @@ const SCHEMA_VALUE_KEYS = new Set([
|
||||
"contentSchema",
|
||||
]);
|
||||
|
||||
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
// JSON Schema allows `type` to be an array; a union containing "object" still
|
||||
// admits objects, so it needs `properties` for Perplexity too.
|
||||
function isObjectType(type: unknown): boolean {
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { ChannelGatewayContext } from "openclaw/plugin-sdk/channel-contract
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { channelReadyPatch, channelStoppedPatch } from "openclaw/plugin-sdk/gateway-runtime";
|
||||
import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { rawDataToString } from "openclaw/plugin-sdk/webhook-ingress";
|
||||
import type { RawData } from "ws";
|
||||
import { resolveClickClackInboundAccess } from "./access.js";
|
||||
@@ -28,8 +29,7 @@ import type {
|
||||
const CLICKCLACK_EVENT_PAGE_LIMIT = 500;
|
||||
|
||||
function payloadString(event: ClickClackEvent, key: string): string {
|
||||
const value = event.payload?.[key];
|
||||
return typeof value === "string" ? value : "";
|
||||
return readStringField(event.payload, key) ?? "";
|
||||
}
|
||||
|
||||
function eventCorrelationId(event: ClickClackEvent): string | undefined {
|
||||
|
||||
@@ -24,7 +24,7 @@ const PROVIDER_ID = "cloudflare-ai-gateway";
|
||||
const PROVIDER_ENV_VAR = "CLOUDFLARE_AI_GATEWAY_API_KEY";
|
||||
const PROFILE_ID = "cloudflare-ai-gateway:default";
|
||||
function readRequiredTextInput(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
return normalizeOptionalString(value) ?? "";
|
||||
}
|
||||
|
||||
async function resolveCloudflareGatewayMetadataInteractive(ctx: {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
type NativeHookRelayProcessResponse,
|
||||
type NativeHookRelayRegistrationHandle,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { coerceErrorMessage as formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import {
|
||||
normalizeTrimmedStringList,
|
||||
readStringField as readString,
|
||||
@@ -1332,7 +1333,4 @@ function joinDescriptionLinesWithinLimit(lines: string[], maxLength: number): st
|
||||
return description;
|
||||
}
|
||||
|
||||
function formatErrorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* or whether OpenClaw must keep exec/process on a configured node host.
|
||||
*/
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
||||
import { resolveSandboxRuntimeStatus } from "openclaw/plugin-sdk/sandbox";
|
||||
import { getSessionEntry, type SessionEntry } from "openclaw/plugin-sdk/session-store-runtime";
|
||||
|
||||
@@ -16,12 +17,6 @@ type ExecHostOverride = {
|
||||
|
||||
type AgentEntry = NonNullable<NonNullable<OpenClawConfig["agents"]>["list"]>[number];
|
||||
|
||||
const DEFAULT_AGENT_ID = "main";
|
||||
const VALID_AGENT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
|
||||
const INVALID_AGENT_ID_CHARS_PATTERN = /[^a-z0-9_-]+/g;
|
||||
const LEADING_DASH_PATTERN = /^-+/;
|
||||
const TRAILING_DASH_PATTERN = /-+$/;
|
||||
|
||||
/** Effective execution-host policy for the Codex app-server native tool surface. */
|
||||
export type CodexNativeExecutionPolicy = {
|
||||
nativeToolSurfaceAllowed: boolean;
|
||||
@@ -197,25 +192,7 @@ function resolveDefaultPolicyAgentId(agents: AgentEntry[]): string {
|
||||
|
||||
function normalizeAgentIdOrDefault(value?: string | null): string | undefined {
|
||||
const normalized = normalizeAgentId(value);
|
||||
return normalized === DEFAULT_AGENT_ID && !(value ?? "").trim() ? undefined : normalized;
|
||||
}
|
||||
|
||||
function normalizeAgentId(value?: string | null): string {
|
||||
const trimmed = (value ?? "").trim();
|
||||
if (!trimmed) {
|
||||
return DEFAULT_AGENT_ID;
|
||||
}
|
||||
const normalized = trimmed.toLowerCase();
|
||||
if (VALID_AGENT_ID_PATTERN.test(trimmed)) {
|
||||
return normalized;
|
||||
}
|
||||
return (
|
||||
normalized
|
||||
.replace(INVALID_AGENT_ID_CHARS_PATTERN, "-")
|
||||
.replace(LEADING_DASH_PATTERN, "")
|
||||
.replace(TRAILING_DASH_PATTERN, "")
|
||||
.slice(0, 64) || DEFAULT_AGENT_ID
|
||||
);
|
||||
return normalized === "main" && !(value ?? "").trim() ? undefined : normalized;
|
||||
}
|
||||
|
||||
function normalizeExecTarget(value?: string | null): ExecTarget | undefined {
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
PluginHookInboundClaimEvent,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-payload";
|
||||
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
||||
import {
|
||||
getSessionEntry,
|
||||
resolveStorePath,
|
||||
@@ -122,11 +123,6 @@ import { isIncognitoSessionKey } from "./incognito-session.js";
|
||||
import { resumeCodexCliSessionOnNode } from "./node-cli-sessions.js";
|
||||
|
||||
const DEFAULT_BOUND_TURN_TIMEOUT_MS = 20 * 60_000;
|
||||
const DEFAULT_AGENT_ID = "main";
|
||||
const VALID_AGENT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
|
||||
const INVALID_AGENT_ID_CHARS_PATTERN = /[^a-z0-9_-]+/g;
|
||||
const LEADING_DASH_PATTERN = /^-+/;
|
||||
const TRAILING_DASH_PATTERN = /-+$/;
|
||||
const NATIVE_CONVERSATION_INTERACTIVE_APPROVALS_UNAVAILABLE =
|
||||
"OpenClaw native Codex conversation binding cannot route interactive approvals yet; use the Codex harness or explicit /acp spawn codex for that workflow.";
|
||||
|
||||
@@ -1484,25 +1480,7 @@ function resolveDefaultPolicyAgentId(config: ResolvedCodexConversationConfig): s
|
||||
|
||||
function normalizeAgentIdOrDefault(value?: string | null): string | undefined {
|
||||
const normalized = normalizeAgentId(value);
|
||||
return normalized === DEFAULT_AGENT_ID && !(value ?? "").trim() ? undefined : normalized;
|
||||
}
|
||||
|
||||
function normalizeAgentId(value?: string | null): string {
|
||||
const trimmed = (value ?? "").trim();
|
||||
if (!trimmed) {
|
||||
return DEFAULT_AGENT_ID;
|
||||
}
|
||||
const normalized = trimmed.toLowerCase();
|
||||
if (VALID_AGENT_ID_PATTERN.test(trimmed)) {
|
||||
return normalized;
|
||||
}
|
||||
return (
|
||||
normalized
|
||||
.replace(INVALID_AGENT_ID_CHARS_PATTERN, "-")
|
||||
.replace(LEADING_DASH_PATTERN, "")
|
||||
.replace(TRAILING_DASH_PATTERN, "")
|
||||
.slice(0, 64) || DEFAULT_AGENT_ID
|
||||
);
|
||||
return normalized === "main" && !(value ?? "").trim() ? undefined : normalized;
|
||||
}
|
||||
|
||||
function isCodexThreadNotFoundError(error: unknown): boolean {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Codex plugin module implements apply behavior.
|
||||
import path from "node:path";
|
||||
import { coerceErrorMessage as formatCodexMigrationError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import {
|
||||
applyMigrationManualItem,
|
||||
markMigrationItemConflict,
|
||||
@@ -293,10 +294,6 @@ function isCodexPluginInventoryLoadError(error: unknown): boolean {
|
||||
return message.includes("codex app-server plugin/list timed out");
|
||||
}
|
||||
|
||||
function formatCodexMigrationError(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
function resolveTargetCodexAppServer(ctx: MigrationProviderContext) {
|
||||
return resolveCodexAppServerRuntimeOptions({
|
||||
pluginConfig: readCodexPluginConfig(ctx.config),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { asFiniteNumber, isRecord } 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 type { CodexThread, CodexThreadTurnsListResponse } from "./app-server/protocol.js";
|
||||
@@ -305,10 +305,6 @@ export function parseJsonParams(paramsJSON?: string | null): unknown {
|
||||
}
|
||||
}
|
||||
|
||||
function readFiniteNumber(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function parseOptionalCatalogString(
|
||||
value: unknown,
|
||||
field: string,
|
||||
@@ -385,9 +381,9 @@ function parseCatalogSession(
|
||||
const sessionKey = options.allowSessionKey
|
||||
? parseOptionalCatalogString(value.sessionKey, "OpenClaw session key", MAX_SESSION_KEY_LENGTH)
|
||||
: undefined;
|
||||
const createdAt = readFiniteNumber(value.createdAt);
|
||||
const updatedAt = readFiniteNumber(value.updatedAt);
|
||||
const recencyAt = value.recencyAt === null ? null : readFiniteNumber(value.recencyAt);
|
||||
const createdAt = asFiniteNumber(value.createdAt);
|
||||
const updatedAt = asFiniteNumber(value.updatedAt);
|
||||
const recencyAt = value.recencyAt === null ? null : asFiniteNumber(value.recencyAt);
|
||||
return {
|
||||
threadId: value.threadId,
|
||||
status,
|
||||
|
||||
@@ -124,10 +124,6 @@ if (process.env.VITEST === "true") {
|
||||
});
|
||||
}
|
||||
|
||||
function readConfigBoolean(config: ComfyProviderConfig, key: string): boolean | undefined {
|
||||
return asBoolean(config[key]);
|
||||
}
|
||||
|
||||
function readConfigInteger(config: ComfyProviderConfig, key: string): number | undefined {
|
||||
const value = config[key];
|
||||
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
|
||||
@@ -710,8 +706,7 @@ export async function runComfyWorkflow(params: {
|
||||
throw new Error("Comfy Cloud API key missing");
|
||||
}
|
||||
|
||||
const explicitAllowPrivateNetwork =
|
||||
readConfigBoolean(capabilityConfig, "allowPrivateNetwork") === true;
|
||||
const explicitAllowPrivateNetwork = asBoolean(capabilityConfig.allowPrivateNetwork) === true;
|
||||
const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } =
|
||||
resolveProviderHttpRequestConfig({
|
||||
baseUrl: normalizeOptionalString(capabilityConfig.baseUrl),
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
type AgentHarnessResetParams,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import type { PluginStateSyncKeyedStore } from "openclaw/plugin-sdk/plugin-state-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { CopilotSessionConfig } from "./src/attempt.js";
|
||||
import { createCopilotByokAuth, resolveCopilotAuth, tokenFingerprint } from "./src/auth-bridge.js";
|
||||
import { createCopilotByokProxy } from "./src/byok-proxy.js";
|
||||
@@ -454,10 +455,10 @@ function computeSessionKey(
|
||||
(typeof p.model === "string" ? p.model : "");
|
||||
const requestTransport =
|
||||
p.model && typeof p.model === "object" ? getModelProviderRequestTransport(p.model) : undefined;
|
||||
const requestAuthMode = readSessionString(
|
||||
const requestAuthMode = normalizeOptionalString(
|
||||
requestTransport?.auth?.mode ?? modelObj.request?.auth?.mode,
|
||||
);
|
||||
const azureApiVersion = readSessionString(
|
||||
const azureApiVersion = normalizeOptionalString(
|
||||
modelObj.azureApiVersion ?? modelObj.params?.azureApiVersion,
|
||||
);
|
||||
// resolveCopilotAuth can throw when an explicit `auth.gitHubToken`
|
||||
@@ -559,10 +560,6 @@ function computeSessionKey(
|
||||
return parts.join("|");
|
||||
}
|
||||
|
||||
function readSessionString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function fingerprintSessionValue(value: unknown): string {
|
||||
return typeof value === "string" && value ? tokenFingerprint(value) : "";
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { MessageOptions, SessionConfig, Tool as SdkTool } from "@github/copilot-sdk";
|
||||
import type { AgentMessage, SandboxContext } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { toStringifiedError as toCopilotError } from "openclaw/plugin-sdk/error-runtime";
|
||||
|
||||
export { toCopilotError };
|
||||
import {
|
||||
detectAndLoadAgentHarnessPromptImages,
|
||||
getModelProviderRequestTransport,
|
||||
@@ -439,9 +442,6 @@ export function resolvePoolAcquire(params: AttemptParamsLike): {
|
||||
provider,
|
||||
};
|
||||
}
|
||||
export function toCopilotError(error: unknown): Error {
|
||||
return error instanceof Error ? error : new Error(String(error));
|
||||
}
|
||||
export function isSdkSendAndWaitTimeoutError(error: unknown): boolean {
|
||||
if (error === null || typeof error !== "object") {
|
||||
return false;
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
import type { ProviderConfig } from "@github/copilot-sdk";
|
||||
import { isNonSecretApiKeyMarker } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
filterStringRecord,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { tokenFingerprint } from "./auth-bridge.js";
|
||||
|
||||
const COPILOT_BYOK_PROVIDER_ERROR =
|
||||
@@ -96,7 +99,7 @@ export function resolveCopilotProvider(params: {
|
||||
const api = normalizeOptionalString(params.model.api)?.toLowerCase() ?? "openai-responses";
|
||||
const provider = resolveProviderType(api, baseUrl, params.model.azureApiVersion);
|
||||
const resolvedApiKey = resolveProviderCredential(params.resolvedApiKey);
|
||||
const headers = resolveProviderHeaders(params.model.headers);
|
||||
const headers = filterStringRecord(params.model.headers);
|
||||
const requestAuthMode = normalizeOptionalString(params.model.requestAuthMode)?.toLowerCase();
|
||||
const usePreparedRequestAuth =
|
||||
requestAuthMode !== undefined && requestAuthMode !== "provider-default";
|
||||
@@ -322,15 +325,3 @@ function resolveProviderCredential(value: string | undefined): string | undefine
|
||||
const credential = normalizeOptionalString(value);
|
||||
return credential && !isNonSecretApiKeyMarker(credential) ? credential : undefined;
|
||||
}
|
||||
|
||||
function resolveProviderHeaders(
|
||||
headers: Record<string, string | null | undefined> | undefined,
|
||||
): Record<string, string> | undefined {
|
||||
if (!headers) {
|
||||
return undefined;
|
||||
}
|
||||
const resolved = Object.fromEntries(
|
||||
Object.entries(headers).filter(([, value]) => typeof value === "string"),
|
||||
) as Record<string, string>;
|
||||
return Object.keys(resolved).length > 0 ? resolved : undefined;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copilot plugin module implements runtime behavior.
|
||||
import { normalize, resolve, sep } from "node:path";
|
||||
import type { CopilotClient, CopilotClientOptions } from "@github/copilot-sdk";
|
||||
import { toStringifiedError as toCopilotRuntimeError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { loadCopilotSdk } from "./sdk-loader.js";
|
||||
|
||||
// SAFETY: The pool reuses CopilotClient instances per normalized PoolKey and does not
|
||||
@@ -389,10 +390,3 @@ function normalizeCopilotHome(copilotHome: string): string {
|
||||
}
|
||||
return normalizedHome;
|
||||
}
|
||||
|
||||
function toCopilotRuntimeError(error: unknown): Error {
|
||||
if (error instanceof Error) {
|
||||
return error;
|
||||
}
|
||||
return new Error(String(error));
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
sanitizeToolResult,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { createAgentHarnessToolSurfaceRuntime } from "openclaw/plugin-sdk/agent-harness-tool-runtime";
|
||||
import { toStringifiedError as toCopilotToolError } from "openclaw/plugin-sdk/error-runtime";
|
||||
|
||||
type CreateOpenClawCodingTools =
|
||||
(typeof import("openclaw/plugin-sdk/agent-harness"))["createOpenClawCodingTools"];
|
||||
@@ -911,7 +912,3 @@ function findDuplicateToolNames(sourceTools: AnyAgentTool[]): string[] {
|
||||
.map(([name]) => name)
|
||||
.toSorted();
|
||||
}
|
||||
|
||||
function toCopilotToolError(error: unknown): Error {
|
||||
return error instanceof Error ? error : new Error(String(error));
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
resolveBootstrapContextForRun,
|
||||
resolveUserPath,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { hasNonEmptyString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { readNonBlankString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
// Filenames the Copilot SDK already loads natively from the working
|
||||
// directory / instructionDirectories (per
|
||||
@@ -88,10 +88,10 @@ export async function resolveCopilotWorkspaceBootstrapContext(params: {
|
||||
const bootstrapContext = await resolveBootstrapContextForRun({
|
||||
workspaceDir,
|
||||
config: attempt.config,
|
||||
sessionKey: readNonEmptyString((attempt as { sessionKey?: unknown }).sessionKey),
|
||||
sessionId: readNonEmptyString(attempt.sessionId),
|
||||
sessionKey: readNonBlankString((attempt as { sessionKey?: unknown }).sessionKey),
|
||||
sessionId: readNonBlankString(attempt.sessionId),
|
||||
chatType: attempt.chatType,
|
||||
agentId: readNonEmptyString(attempt.agentId),
|
||||
agentId: readNonBlankString(attempt.agentId),
|
||||
warn: params.warn,
|
||||
contextMode: attempt.bootstrapContextMode,
|
||||
runKind: attempt.bootstrapContextRunKind,
|
||||
@@ -235,12 +235,8 @@ function getCopilotContextFileBasename(filePath: string): string {
|
||||
return normalizeCopilotContextFilePath(filePath).split("/").pop() ?? "";
|
||||
}
|
||||
|
||||
function readNonEmptyString(value: unknown): string | undefined {
|
||||
return hasNonEmptyString(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function readResolvedWorkspacePath(value: unknown): string | undefined {
|
||||
const raw = readNonEmptyString(value);
|
||||
const raw = readNonBlankString(value);
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ import { createHash } from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { WorkerProviderError, type WorkerProfile } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { normalizeOptionalString as nonEmptyString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export { nonEmptyString };
|
||||
|
||||
const PROFILE_KEYS = new Set([
|
||||
"binary",
|
||||
@@ -39,14 +42,6 @@ type CrabboxProfile = {
|
||||
|
||||
type IsExecutable = (candidate: string) => boolean;
|
||||
|
||||
export function nonEmptyString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
function requirePositiveDuration(value: unknown, key: string): string {
|
||||
const duration = nonEmptyString(value);
|
||||
if (!duration || !isPositiveGoDuration(duration)) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { SpanKind } from "@opentelemetry/api";
|
||||
import { GEN_AI_OPERATION_NAME_VALUE_INVOKE_AGENT } from "@opentelemetry/semantic-conventions/incubating";
|
||||
import { normalizeDiagnosticValue } from "openclaw/plugin-sdk/diagnostic-runtime";
|
||||
import { asFiniteNumber, asFiniteNumberInRange } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { DiagnosticEventPayload } from "../api.js";
|
||||
import { redactSensitiveText } from "../api.js";
|
||||
import {
|
||||
@@ -48,11 +49,11 @@ export function genAiOperationName(
|
||||
}
|
||||
|
||||
export function positiveFiniteNumber(value: number | undefined): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
|
||||
return asFiniteNumberInRange(value, { min: 0, minExclusive: true });
|
||||
}
|
||||
|
||||
function nonNegativeFiniteNumber(value: number | undefined): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
||||
return asFiniteNumberInRange(value, { min: 0 });
|
||||
}
|
||||
|
||||
export function assignPositiveNumberAttr(
|
||||
@@ -88,8 +89,9 @@ function assignNumberAttr(
|
||||
key: string,
|
||||
value: number | undefined,
|
||||
): void {
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
attrs[key] = value;
|
||||
const normalized = asFiniteNumber(value);
|
||||
if (normalized !== undefined) {
|
||||
attrs[key] = normalized;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asBoolean } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
// Discord plugin module implements runtime.shared behavior.
|
||||
import {
|
||||
parseAvailableTags,
|
||||
@@ -45,13 +46,6 @@ export function readDiscordAutoArchiveDurationParam(
|
||||
return value;
|
||||
}
|
||||
|
||||
function readDiscordBooleanParam(
|
||||
params: Record<string, unknown>,
|
||||
key: string,
|
||||
): boolean | undefined {
|
||||
return typeof params[key] === "boolean" ? params[key] : undefined;
|
||||
}
|
||||
|
||||
export function createDiscordActionOptions<
|
||||
T extends Record<string, unknown> = Record<string, never>,
|
||||
>(params: {
|
||||
@@ -80,7 +74,7 @@ export function readDiscordChannelCreateParams(
|
||||
parentId: parentId ?? undefined,
|
||||
topic: readStringParam(params, "topic") ?? undefined,
|
||||
position: readNonNegativeIntegerParam(params, "position") ?? undefined,
|
||||
nsfw: readDiscordBooleanParam(params, "nsfw"),
|
||||
nsfw: asBoolean(params.nsfw),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -92,10 +86,10 @@ export function readDiscordChannelEditParams(params: Record<string, unknown>): D
|
||||
topic: readStringParam(params, "topic") ?? undefined,
|
||||
position: readNonNegativeIntegerParam(params, "position") ?? undefined,
|
||||
parentId: parentId === undefined ? undefined : parentId,
|
||||
nsfw: readDiscordBooleanParam(params, "nsfw"),
|
||||
nsfw: asBoolean(params.nsfw),
|
||||
rateLimitPerUser: readNonNegativeIntegerParam(params, "rateLimitPerUser") ?? undefined,
|
||||
archived: readDiscordBooleanParam(params, "archived"),
|
||||
locked: readDiscordBooleanParam(params, "locked"),
|
||||
archived: asBoolean(params.archived),
|
||||
locked: asBoolean(params.locked),
|
||||
autoArchiveDuration: readDiscordAutoArchiveDurationParam(params, "autoArchiveDuration"),
|
||||
availableTags: parseAvailableTags(params.availableTags),
|
||||
};
|
||||
|
||||
@@ -10,11 +10,6 @@ type ActiveDiscordTurnThreadRoute = {
|
||||
|
||||
const activeRoutes = new Map<string, Set<ActiveDiscordTurnThreadRoute>>();
|
||||
|
||||
function normalizeId(value: string | undefined): string | undefined {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
export function beginDiscordActiveTurnThreadRoute(
|
||||
sessionKey: string | undefined,
|
||||
route: ActiveDiscordTurnThreadRoute,
|
||||
@@ -96,3 +91,4 @@ function findDiscordActiveTurnThreadReplyRoute(params: {
|
||||
(!route.accountId || !params.accountId || route.accountId === params.accountId),
|
||||
);
|
||||
}
|
||||
import { normalizeOptionalString as normalizeId } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { normalizeOptionalString as readNonEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
type DiscordActivitiesConfigResolution =
|
||||
| {
|
||||
@@ -11,11 +12,6 @@ type DiscordActivitiesConfigResolution =
|
||||
reason: "not-configured" | "missing-client-secret";
|
||||
};
|
||||
|
||||
function readNonEmpty(value: string | undefined): string | undefined {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
export function resolveDiscordActivitiesConfig(
|
||||
account: DiscordAccountConfig,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { inspect } from "node:util";
|
||||
import { gunzipSync } from "node:zlib";
|
||||
import {
|
||||
clampTimerTimeoutMs,
|
||||
parseFiniteNumber,
|
||||
resolveIntegerOption as normalizeIntegerOption,
|
||||
resolveTimerTimeoutMs,
|
||||
} from "openclaw/plugin-sdk/number-runtime";
|
||||
import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
|
||||
@@ -307,15 +307,6 @@ export class RequestClient {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeIntegerOption(
|
||||
value: number | undefined,
|
||||
fallback: number,
|
||||
params: { min: number },
|
||||
): number {
|
||||
const candidate = parseFiniteNumber(value) ?? fallback;
|
||||
return Math.max(params.min, Math.floor(candidate));
|
||||
}
|
||||
|
||||
function normalizeRequestClientOptions(
|
||||
options?: RequestClientOptions,
|
||||
): NormalizedRequestClientOptions {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Discord provider module implements model/runtime integration.
|
||||
import { inspect } from "node:util";
|
||||
import {
|
||||
parseStrictFiniteNumber,
|
||||
parseStrictNonNegativeInteger,
|
||||
} from "openclaw/plugin-sdk/number-runtime";
|
||||
import { formatDurationSeconds } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import {
|
||||
parseFiniteNumber as readFiniteNumber,
|
||||
parseStrictNonNegativeInteger as readNonNegativeInteger,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import { RateLimitError } from "../internal/discord.js";
|
||||
|
||||
@@ -102,20 +102,6 @@ function readDiscordDeployObjectField(value: unknown, field: string): unknown {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function readFiniteNumber(value: unknown): number | undefined {
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string" && value.trim().length > 0) {
|
||||
return parseStrictFiniteNumber(value);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function readNonNegativeInteger(value: unknown): number | undefined {
|
||||
return parseStrictNonNegativeInteger(value);
|
||||
}
|
||||
|
||||
function isAbortLikeError(err: unknown): boolean {
|
||||
if (!err || typeof err !== "object") {
|
||||
return false;
|
||||
|
||||
@@ -2,22 +2,18 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { makeProxyFetch } from "openclaw/plugin-sdk/fetch-runtime";
|
||||
import { danger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { ResolvedDiscordAccount } from "./accounts.js";
|
||||
|
||||
function resolveDiscordProxyUrl(
|
||||
account: Pick<ResolvedDiscordAccount, "config">,
|
||||
cfg: OpenClawConfig,
|
||||
): string | undefined {
|
||||
const accountProxy = account.config.proxy?.trim();
|
||||
const accountProxy = normalizeOptionalString(account.config.proxy);
|
||||
if (accountProxy) {
|
||||
return accountProxy;
|
||||
}
|
||||
const channelProxy = cfg?.channels?.discord?.proxy;
|
||||
if (typeof channelProxy !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = channelProxy.trim();
|
||||
return trimmed || undefined;
|
||||
return normalizeOptionalString(cfg?.channels?.discord?.proxy);
|
||||
}
|
||||
|
||||
function resolveDiscordProxyFetchByUrl(
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Duckduckgo helper module supports config behavior.
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
const DEFAULT_DDG_SAFE_SEARCH = "moderate";
|
||||
|
||||
@@ -25,12 +28,7 @@ function resolveDdgWebSearchConfig(
|
||||
}
|
||||
|
||||
export function resolveDdgRegion(config?: OpenClawConfig): string | undefined {
|
||||
const region = resolveDdgWebSearchConfig(config)?.region;
|
||||
if (typeof region !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = region.trim();
|
||||
return trimmed || undefined;
|
||||
return normalizeOptionalString(resolveDdgWebSearchConfig(config)?.region);
|
||||
}
|
||||
|
||||
export function resolveDdgSafeSearch(config?: OpenClawConfig): DdgSafeSearch {
|
||||
|
||||
@@ -9,7 +9,9 @@ import {
|
||||
} from "openclaw/plugin-sdk/realtime-transcription";
|
||||
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
||||
import {
|
||||
asFiniteNumberInRange,
|
||||
asOptionalRecord as readRecord,
|
||||
asSafeIntegerInRange,
|
||||
normalizeOptionalString,
|
||||
parseFiniteNumber as readFiniteNumber,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
@@ -81,19 +83,17 @@ function normalizeCommitStrategy(value: unknown): "manual" | "vad" | undefined {
|
||||
|
||||
function normalizePositiveSafeInteger(value: unknown): number | undefined {
|
||||
const parsed = readFiniteNumber(value);
|
||||
return parsed !== undefined && Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
|
||||
return asSafeIntegerInRange(parsed, { min: 1 });
|
||||
}
|
||||
|
||||
function normalizeFiniteRange(value: unknown, min: number, max: number): number | undefined {
|
||||
const parsed = readFiniteNumber(value);
|
||||
return parsed !== undefined && parsed >= min && parsed <= max ? parsed : undefined;
|
||||
return asFiniteNumberInRange(parsed, { min, max });
|
||||
}
|
||||
|
||||
function normalizeIntegerRange(value: unknown, min: number, max: number): number | undefined {
|
||||
const parsed = readFiniteNumber(value);
|
||||
return parsed !== undefined && Number.isSafeInteger(parsed) && parsed >= min && parsed <= max
|
||||
? parsed
|
||||
: undefined;
|
||||
return asSafeIntegerInRange(parsed, { min, max });
|
||||
}
|
||||
|
||||
function normalizeProviderConfig(
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
resolveChannelPreviewStreamMode,
|
||||
resolveChannelStreamingBlockEnabled,
|
||||
} from "openclaw/plugin-sdk/channel-outbound";
|
||||
import { toStringifiedError as toFeishuError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import {
|
||||
getReplyPayloadTtsSupplement,
|
||||
@@ -77,10 +78,6 @@ function mergeStreamingFinalText(
|
||||
return `${previousText}\n\n${nextText}`;
|
||||
}
|
||||
|
||||
function toFeishuError(error: unknown): Error {
|
||||
return error instanceof Error ? error : new Error(String(error));
|
||||
}
|
||||
|
||||
/** Maximum age (ms) for a message to receive a typing indicator reaction.
|
||||
* Messages older than this are likely replays after context compaction (#30418). */
|
||||
const TYPING_INDICATOR_MAX_AGE_MS = 2 * 60_000;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import { formatByteSize } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { readPositiveIntegerParam } from "openclaw/plugin-sdk/param-readers";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
type GatewayCallOptions = {
|
||||
gatewayUrl?: string;
|
||||
@@ -23,8 +24,7 @@ export function readGatewayCallOptions(params: Record<string, unknown>): Gateway
|
||||
}
|
||||
|
||||
export function readTrimmedString(params: Record<string, unknown>, key: string): string {
|
||||
const value = params[key];
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
return normalizeOptionalString(params[key]) ?? "";
|
||||
}
|
||||
|
||||
export function readClampedInt(params: {
|
||||
|
||||
@@ -11,12 +11,11 @@ import type {
|
||||
} from "openclaw/plugin-sdk/speech";
|
||||
import {
|
||||
asBoolean,
|
||||
asFiniteNumber,
|
||||
parseSpeechDirectiveNumberOverride,
|
||||
resolveSpeechProviderApiKey,
|
||||
trimToUndefined,
|
||||
} from "openclaw/plugin-sdk/speech-core";
|
||||
import { asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { asFiniteNumberInRange, asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
FISH_AUDIO_STREAM_MAX_BYTES,
|
||||
type FishAudioFormat,
|
||||
@@ -71,8 +70,7 @@ function normalizeLatency(value: unknown): FishAudioLatency {
|
||||
}
|
||||
|
||||
function normalizeNumber(value: unknown, min: number, max: number): number | undefined {
|
||||
const number = asFiniteNumber(value);
|
||||
return number != null && number >= min && number <= max ? number : undefined;
|
||||
return asFiniteNumberInRange(value, { min, max });
|
||||
}
|
||||
|
||||
function resolveReferenceId(raw: Record<string, unknown> | undefined): string | undefined {
|
||||
|
||||
@@ -9,10 +9,12 @@ import {
|
||||
type RealtimeVoiceAgentConsultToolPolicy,
|
||||
} from "openclaw/plugin-sdk/realtime-voice";
|
||||
import {
|
||||
asBoolean,
|
||||
asRecord,
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
normalizeOptionalTrimmedStringList,
|
||||
parseBooleanValue,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export type GoogleMeetTransport = "chrome" | "chrome-node" | "twilio";
|
||||
@@ -251,7 +253,7 @@ const GOOGLE_MEET_PREVIEW_ACK_KEYS = [
|
||||
] as const;
|
||||
|
||||
function resolveBoolean(value: unknown, fallback: boolean): boolean {
|
||||
return typeof value === "boolean" ? value : fallback;
|
||||
return asBoolean(value) ?? fallback;
|
||||
}
|
||||
|
||||
function resolveNumber(value: unknown, fallback: number): number {
|
||||
@@ -289,17 +291,7 @@ function normalizeStringAllowEmpty(value: unknown): string | undefined {
|
||||
}
|
||||
|
||||
function readEnvBoolean(env: NodeJS.ProcessEnv, keys: readonly string[]): boolean | undefined {
|
||||
const normalized = normalizeOptionalLowercaseString(readEnvString(env, keys));
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
if (["1", "true", "yes", "on"].includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
if (["0", "false", "no", "off"].includes(normalized)) {
|
||||
return false;
|
||||
}
|
||||
return undefined;
|
||||
return parseBooleanValue(readEnvString(env, keys));
|
||||
}
|
||||
|
||||
function readEnvNumber(env: NodeJS.ProcessEnv, keys: readonly string[]): number | undefined {
|
||||
|
||||
@@ -5,7 +5,12 @@ import type {
|
||||
OpenClawPluginApi,
|
||||
OpenClawPluginNodeInvokePolicy,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
asNonArrayRecord as asParamRecord,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export { asParamRecord };
|
||||
import { isGoogleMeetBrowserManualActionError } from "./browser-manual-action-error.js";
|
||||
import {
|
||||
resolveGoogleMeetGatewayOperationTimeoutMs,
|
||||
@@ -44,10 +49,6 @@ type LoadGoogleMeetNodeInvokePolicy = (
|
||||
const loadGoogleMeetNodeInvokePolicy: LoadGoogleMeetNodeInvokePolicy = async (config) =>
|
||||
(await loadGoogleMeetNodeInvokePolicyModule()).createGoogleMeetChromeNodeInvokePolicy(config);
|
||||
|
||||
export function asParamRecord(params: unknown): Record<string, unknown> {
|
||||
return isRecord(params) ? params : {};
|
||||
}
|
||||
|
||||
export function normalizeTransport(value: unknown): GoogleMeetTransport | undefined {
|
||||
return value === "chrome" || value === "chrome-node" || value === "twilio" ? value : undefined;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-i
|
||||
import {
|
||||
asBoolean,
|
||||
asFiniteNumber,
|
||||
asSafeIntegerInRange,
|
||||
isRecord,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
@@ -209,8 +210,7 @@ function asTurnCoverage(value: unknown): GoogleRealtimeTurnCoverage | undefined
|
||||
}
|
||||
|
||||
function asNonNegativeInteger(value: unknown): number | undefined {
|
||||
const number = asFiniteNumber(value);
|
||||
return number !== undefined && Number.isSafeInteger(number) && number >= 0 ? number : undefined;
|
||||
return asSafeIntegerInRange(value, { min: 0 });
|
||||
}
|
||||
|
||||
function asGoogleRealtimeThinkingBudget(value: unknown): number | undefined {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
buildHostnameAllowlistPolicyFromSuffixAllowlist,
|
||||
fetchWithSsrFGuard,
|
||||
} from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { asNullableObjectRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolveUserPath } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import type { ResolvedGoogleChatAccount } from "./accounts.js";
|
||||
import { MAX_GOOGLE_CHAT_SERVICE_ACCOUNT_FILE_BYTES } from "./google-auth-limits.js";
|
||||
@@ -78,10 +79,6 @@ function installGoogleAuthHeaderCompatibilityInterceptor(
|
||||
return transport;
|
||||
}
|
||||
|
||||
function asNullableObjectRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value !== null && typeof value === "object" ? (value as Record<string, unknown>) : null;
|
||||
}
|
||||
|
||||
function hasProxyAgentShape(value: unknown): value is ProxyAgentLike {
|
||||
const record = asNullableObjectRecord(value);
|
||||
return record !== null && record.proxy instanceof URL;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Imessage plugin module implements catchup behavior.
|
||||
import { createHash } from "node:crypto";
|
||||
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
||||
import { resolveIntegerOption } from "openclaw/plugin-sdk/number-runtime";
|
||||
import type { PluginStateSyncKeyedStore } from "openclaw/plugin-sdk/plugin-state-runtime";
|
||||
import { getIMessageRuntime } from "../runtime.js";
|
||||
|
||||
@@ -246,10 +247,7 @@ export type ResolvedCatchupConfig = {
|
||||
};
|
||||
|
||||
function clampInt(value: number | undefined, min: number, max: number, fallback: number): number {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) {
|
||||
return fallback;
|
||||
}
|
||||
return Math.min(max, Math.max(min, Math.floor(value)));
|
||||
return resolveIntegerOption(value, fallback, { min, max });
|
||||
}
|
||||
|
||||
export function resolveCatchupConfig(
|
||||
|
||||
@@ -10,6 +10,7 @@ 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 {
|
||||
filterStringEntries,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeStringEntries,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
@@ -187,14 +188,6 @@ function selectorsFromPayload(payload: Record<string, unknown>): Record<string,
|
||||
return selectors;
|
||||
}
|
||||
|
||||
function rpcMethodsFromPayload(payload: Record<string, unknown>): string[] {
|
||||
const raw = payload.rpc_methods;
|
||||
if (!Array.isArray(raw)) {
|
||||
return [];
|
||||
}
|
||||
return raw.filter((entry): entry is string => typeof entry === "string");
|
||||
}
|
||||
|
||||
// Probe whether the installed imsg CLI accepts `--file` on the `send-rich`
|
||||
// subcommand (added by openclaw/imsg#114, which lets a single bridge call
|
||||
// combine `--reply-to` and an attachment). We grep the help output rather
|
||||
@@ -278,7 +271,7 @@ export async function probeIMessagePrivateApi(
|
||||
}
|
||||
const { payload, firstLineSnippet } = parseStatusPayload(result.stdout);
|
||||
const selectors = payload ? selectorsFromPayload(payload) : {};
|
||||
const rpcMethods = payload ? rpcMethodsFromPayload(payload) : [];
|
||||
const rpcMethods = filterStringEntries(payload?.rpc_methods);
|
||||
const advancedFeatures = payload?.advanced_features === true;
|
||||
const v2Ready = payload?.v2_ready === true;
|
||||
// imsg explains an unavailable bridge here (SIP, library validation, macOS
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { OpenClawPluginNodeHostCommandAvailabilityContext } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import type { CommandOptions, SpawnResult } from "openclaw/plugin-sdk/process-runtime";
|
||||
import { asNonArrayRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { asFiniteNumber, asNonArrayRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import {
|
||||
resolveLinuxNodePluginConfigFromHost,
|
||||
@@ -21,9 +21,7 @@ export function parseParams(paramsJSON: string | null | undefined): Record<strin
|
||||
}
|
||||
}
|
||||
|
||||
export function readFiniteNumber(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
||||
}
|
||||
export { asFiniteNumber as readFiniteNumber };
|
||||
|
||||
export function clamp(value: number, minimum: number, maximum: number): number {
|
||||
return Math.min(maximum, Math.max(minimum, value));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Matrix plugin module implements env auth behavior.
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { getMatrixScopedEnvVarNames } from "../../env-vars.js";
|
||||
|
||||
type MatrixEnvConfig = {
|
||||
@@ -12,7 +13,7 @@ type MatrixEnvConfig = {
|
||||
};
|
||||
|
||||
function cleanEnv(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
return normalizeOptionalString(value) ?? "";
|
||||
}
|
||||
|
||||
export function resolveGlobalMatrixEnvConfig(env: NodeJS.ProcessEnv): MatrixEnvConfig {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Matrix plugin module implements shared behavior.
|
||||
import { normalizeOptionalAccountId } from "openclaw/plugin-sdk/account-id";
|
||||
import { toStringifiedError as toRetirementError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
||||
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
||||
import type { CoreConfig } from "../../types.js";
|
||||
@@ -274,10 +275,6 @@ async function retireMonitorLeases(
|
||||
}
|
||||
}
|
||||
|
||||
function toRetirementError(error: unknown): Error {
|
||||
return error instanceof Error ? error : new Error(String(error));
|
||||
}
|
||||
|
||||
function mergeReleaseMode(
|
||||
current: MatrixClientReleaseMode,
|
||||
requested: MatrixClientReleaseMode,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Matrix plugin module implements crypto bootstrap behavior.
|
||||
import { setTimeout as sleep } from "node:timers/promises";
|
||||
import { CryptoEvent } from "matrix-js-sdk/lib/crypto-api/CryptoEvent.js";
|
||||
import { toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import type { MatrixDecryptBridge } from "./decrypt-bridge.js";
|
||||
import { LogService } from "./logger.js";
|
||||
import type { MatrixRecoveryKeyStore } from "./recovery-key-store.js";
|
||||
@@ -236,7 +237,7 @@ export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
} catch (repairErr) {
|
||||
LogService.warn("MatrixClientLite", "Forced cross-signing reset failed:", repairErr);
|
||||
if (options.strict) {
|
||||
throw repairErr instanceof Error ? repairErr : new Error(String(repairErr));
|
||||
throw toStringifiedError(repairErr);
|
||||
}
|
||||
return { ready: false, published: false };
|
||||
}
|
||||
@@ -250,7 +251,7 @@ export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
{ cause: err },
|
||||
);
|
||||
}
|
||||
throw err instanceof Error ? err : new Error(String(err));
|
||||
throw toStringifiedError(err);
|
||||
}
|
||||
return { ready: false, published: false };
|
||||
}
|
||||
@@ -300,7 +301,7 @@ export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
} catch (resetErr) {
|
||||
LogService.warn("MatrixClientLite", "Failed to bootstrap cross-signing:", resetErr);
|
||||
if (options.strict) {
|
||||
throw resetErr instanceof Error ? resetErr : new Error(String(resetErr));
|
||||
throw toStringifiedError(resetErr);
|
||||
}
|
||||
return { ready: false, published: false };
|
||||
}
|
||||
@@ -328,7 +329,7 @@ export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
} catch (err) {
|
||||
LogService.warn("MatrixClientLite", "Fallback cross-signing bootstrap failed:", err);
|
||||
if (options.strict) {
|
||||
throw err instanceof Error ? err : new Error(String(err));
|
||||
throw toStringifiedError(err);
|
||||
}
|
||||
return { ready: false, published: false };
|
||||
}
|
||||
@@ -371,7 +372,7 @@ export class MatrixCryptoBootstrapper<TRawEvent extends MatrixRawEvent> {
|
||||
} catch (err) {
|
||||
LogService.warn("MatrixClientLite", "Failed to bootstrap secret storage:", err);
|
||||
if (options.strict) {
|
||||
throw err instanceof Error ? err : new Error(String(err));
|
||||
throw toStringifiedError(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ import { parseStrictInteger } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import {
|
||||
asFiniteNumber,
|
||||
normalizeOptionalString,
|
||||
normalizeStringifiedOptionalString,
|
||||
readStringField,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { MattermostInteractiveButtonInput } from "./interactions.js";
|
||||
|
||||
@@ -59,19 +61,12 @@ function splitModelRef(modelRef?: string | null): { provider: string; model: str
|
||||
}
|
||||
|
||||
function readContextString(context: Record<string, unknown>, key: string, fallback = ""): string {
|
||||
const value = context[key];
|
||||
return typeof value === "string" ? value : fallback;
|
||||
return readStringField(context, key) ?? fallback;
|
||||
}
|
||||
|
||||
function readContextNumber(context: Record<string, unknown>, key: string): number | undefined {
|
||||
const value = context[key];
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return parseStrictInteger(value);
|
||||
}
|
||||
return undefined;
|
||||
return asFiniteNumber(value) ?? parseStrictInteger(value);
|
||||
}
|
||||
|
||||
function normalizePage(value: number | undefined): number {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { resolveNonNegativeIntegerOption } from "openclaw/plugin-sdk/number-runtime";
|
||||
import type {
|
||||
OpenKeyedStoreOptions,
|
||||
PluginStateKeyedStore,
|
||||
@@ -149,10 +150,6 @@ function normalizeImportRunEntries(value: unknown): ChatGptImportRunEntry[] {
|
||||
});
|
||||
}
|
||||
|
||||
function asNonNegativeInteger(value: unknown): number {
|
||||
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
||||
}
|
||||
|
||||
function normalizeMemoryWikiImportRunRecord(raw: unknown): ChatGptImportRunRecord | null {
|
||||
const record = asNullableRecord(raw);
|
||||
if (!record) {
|
||||
@@ -182,10 +179,10 @@ function normalizeMemoryWikiImportRunRecord(raw: unknown): ChatGptImportRunRecor
|
||||
exportPath,
|
||||
sourcePath,
|
||||
appliedAt,
|
||||
conversationCount: asNonNegativeInteger(record.conversationCount),
|
||||
createdCount: asNonNegativeInteger(record.createdCount),
|
||||
updatedCount: asNonNegativeInteger(record.updatedCount),
|
||||
skippedCount: asNonNegativeInteger(record.skippedCount),
|
||||
conversationCount: resolveNonNegativeIntegerOption(record.conversationCount, 0),
|
||||
createdCount: resolveNonNegativeIntegerOption(record.createdCount, 0),
|
||||
updatedCount: resolveNonNegativeIntegerOption(record.updatedCount, 0),
|
||||
skippedCount: resolveNonNegativeIntegerOption(record.skippedCount, 0),
|
||||
createdPaths: normalizeImportRunEntries(record.createdPaths),
|
||||
updatedPaths: normalizeImportRunEntries(record.updatedPaths),
|
||||
...(rollbackStartedAt ? { rollbackStartedAt } : {}),
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
import type { StreamFn } from "openclaw/plugin-sdk/agent-core";
|
||||
import type { ProviderWrapStreamFnContext } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { createPayloadPatchStreamWrapper } from "openclaw/plugin-sdk/provider-stream-shared";
|
||||
import { filterStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
const META_REASONING_ENCRYPTED_CONTENT_INCLUDE = "reasoning.encrypted_content";
|
||||
|
||||
function ensureMetaResponsesReplayFields(payloadObj: Record<string, unknown>): void {
|
||||
const existing = payloadObj.include;
|
||||
const include = Array.isArray(existing)
|
||||
? existing.filter((entry): entry is string => typeof entry === "string")
|
||||
: [];
|
||||
const include = filterStringEntries(existing);
|
||||
if (!include.includes(META_REASONING_ENCRYPTED_CONTENT_INCLUDE)) {
|
||||
include.push(META_REASONING_ENCRYPTED_CONTENT_INCLUDE);
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ function readOptionalTrimmedString(
|
||||
params: Record<string, unknown>,
|
||||
key: string,
|
||||
): string | undefined {
|
||||
return typeof params[key] === "string" ? params[key].trim() || undefined : undefined;
|
||||
return normalizeOptionalString(params[key]);
|
||||
}
|
||||
|
||||
function resolveActionUploadFilePath(params: Record<string, unknown>): string | undefined {
|
||||
|
||||
@@ -31,13 +31,6 @@ function cacheRoomInfo(
|
||||
pruneMapToMaxSize(roomCache, ROOM_CACHE_MAX_ENTRIES);
|
||||
}
|
||||
|
||||
function coerceRoomType(value: unknown): number | undefined {
|
||||
if (typeof value === "number" && Number.isSafeInteger(value) && value > 0) {
|
||||
return value;
|
||||
}
|
||||
return parseStrictPositiveInteger(value);
|
||||
}
|
||||
|
||||
function resolveRoomKindFromType(type: number | undefined): "direct" | "group" | undefined {
|
||||
if (!type) {
|
||||
return undefined;
|
||||
@@ -117,7 +110,7 @@ export async function resolveNextcloudTalkRoomKind(params: {
|
||||
const payload = await readProviderJsonResponse<{
|
||||
ocs?: { data?: { type?: number | string } };
|
||||
}>(response, "Nextcloud Talk room info failed");
|
||||
const type = coerceRoomType(payload.ocs?.data?.type);
|
||||
const type = parseStrictPositiveInteger(payload.ocs?.data?.type);
|
||||
const kind = resolveRoomKindFromType(type);
|
||||
cacheRoomInfo(key, { fetchedAt: Date.now(), kind });
|
||||
return kind;
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
readStringValue,
|
||||
readNonEmptyStringPreservingWhitespace as readNonEmptyUntrimmedString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolveDefaultNextcloudTalkAccountId, resolveNextcloudTalkAccount } from "./accounts.js";
|
||||
import type { CoreConfig } from "./types.js";
|
||||
@@ -39,11 +39,6 @@ type NextcloudSetupInput = ChannelSetupInput & {
|
||||
password?: string;
|
||||
};
|
||||
|
||||
function readNonEmptyUntrimmedString(value: unknown): string | undefined {
|
||||
const text = readStringValue(value);
|
||||
return text ? text : undefined;
|
||||
}
|
||||
|
||||
export function normalizeNextcloudTalkBaseUrl(value: string | undefined): string {
|
||||
return value?.trim().replace(/\/+$/, "") ?? "";
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// Nostr plugin module owns durable ingress identity and legacy-state migration.
|
||||
import type { ChannelIngressQueue } from "openclaw/plugin-sdk/channel-outbound";
|
||||
import { isRecord as isNostrIngressRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export { isNostrIngressRecord };
|
||||
|
||||
export const NOSTR_INGRESS_PAYLOAD_VERSION = 1;
|
||||
|
||||
@@ -20,10 +23,6 @@ export class NostrIngressPermanentError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export function isNostrIngressRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function requiredString(value: unknown, field: string): string {
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
return value;
|
||||
|
||||
@@ -5,6 +5,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { DEFAULT_SECRET_FILE_MAX_BYTES, tryReadSecretFileSync } from "@openclaw/fs-safe/secret";
|
||||
import { execa } from "execa";
|
||||
import { coerceErrorMessage as errorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { resolveTrustedOnePasswordCli } from "./onepassword-op-path.js";
|
||||
import { resolveOnePasswordSecretReference } from "./onepassword-secret-id.js";
|
||||
|
||||
@@ -65,10 +66,6 @@ function opMissingMessage(command) {
|
||||
return `1Password CLI "${command}" is not installed or cannot be executed. Install the official 1Password CLI v2, and set CLAW_1PASSWORD_OP to its absolute path.`;
|
||||
}
|
||||
|
||||
function errorMessage(error) {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
function resolveOsHome() {
|
||||
const home =
|
||||
process.platform === "win32"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { coerceErrorMessage as formatOpenAiBatchError } from "openclaw/plugin-sdk/error-runtime";
|
||||
// Openai plugin module implements embedding batch behavior.
|
||||
import {
|
||||
applyEmbeddingBatchOutputLine,
|
||||
@@ -158,10 +159,6 @@ async function fetchOpenAiBatchResource<T>(params: {
|
||||
});
|
||||
}
|
||||
|
||||
function formatOpenAiBatchError(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
function formatOpenAiBatchDiagnostic(error: unknown): string {
|
||||
return formatBatchErrorDetail(formatOpenAiBatchError(error)) ?? "unknown error";
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
sanitizeConfiguredModelProviderRequest,
|
||||
} from "openclaw/plugin-sdk/provider-http";
|
||||
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { filterStringRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import {
|
||||
canonicalizeCodexResponsesBaseUrl,
|
||||
@@ -447,16 +448,7 @@ function hasChatGPTImageRouteConfig(cfg: OpenClawConfig | undefined): boolean {
|
||||
function resolveConfiguredOpenAIImageHeaders(
|
||||
cfg: OpenClawConfig | undefined,
|
||||
): Record<string, string> | undefined {
|
||||
const headers = cfg?.models?.providers?.openai?.headers;
|
||||
if (!headers) {
|
||||
return undefined;
|
||||
}
|
||||
const stringHeaders = Object.fromEntries(
|
||||
Object.entries(headers).filter(
|
||||
(entry): entry is [string, string] => typeof entry[1] === "string",
|
||||
),
|
||||
);
|
||||
return Object.keys(stringHeaders).length > 0 ? stringHeaders : undefined;
|
||||
return filterStringRecord(cfg?.models?.providers?.openai?.headers);
|
||||
}
|
||||
|
||||
function forceOpenAIImageApiKeyAuth(cfg: OpenClawConfig | undefined): OpenClawConfig | undefined {
|
||||
|
||||
@@ -14,7 +14,11 @@ import {
|
||||
type RealtimeTranscriptionWebSocketTransport,
|
||||
} from "openclaw/plugin-sdk/realtime-transcription";
|
||||
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
||||
import { asFiniteNumber, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
asFiniteNumberInRange,
|
||||
asSafeIntegerInRange,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
createOpenAIRealtimeTranscriptionClientSecret,
|
||||
readRealtimeErrorDetail,
|
||||
@@ -133,19 +137,11 @@ function normalizeProviderConfig(
|
||||
}
|
||||
|
||||
function normalizeNonNegativeInteger(value: unknown): number | undefined {
|
||||
const number = asFiniteNumber(value);
|
||||
if (number === undefined || !Number.isSafeInteger(number) || number < 0) {
|
||||
return undefined;
|
||||
}
|
||||
return number;
|
||||
return asSafeIntegerInRange(value, { min: 0 });
|
||||
}
|
||||
|
||||
function normalizeVadThreshold(value: unknown): number | undefined {
|
||||
const number = asFiniteNumber(value);
|
||||
if (number === undefined || number < 0 || number > 1) {
|
||||
return undefined;
|
||||
}
|
||||
return number;
|
||||
return asFiniteNumberInRange(value, { min: 0, max: 1 });
|
||||
}
|
||||
|
||||
function buildOpenAIRealtimeTranscriptionSessionPayload(
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { resolveAgentDir } from "openclaw/plugin-sdk/agent-runtime";
|
||||
import { toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { canonicalizeBase64 } from "openclaw/plugin-sdk/media-runtime";
|
||||
import type { PluginLogger } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import {
|
||||
@@ -41,6 +42,8 @@ import {
|
||||
} from "openclaw/plugin-sdk/secret-input";
|
||||
import {
|
||||
asFiniteNumber,
|
||||
asFiniteNumberInRange,
|
||||
asSafeIntegerInRange,
|
||||
isRecord,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
@@ -294,13 +297,11 @@ function normalizeProviderConfig(
|
||||
}
|
||||
|
||||
function asNonNegativeInteger(value: unknown): number | undefined {
|
||||
const number = asFiniteNumber(value);
|
||||
return number !== undefined && Number.isSafeInteger(number) && number >= 0 ? number : undefined;
|
||||
return asSafeIntegerInRange(value, { min: 0 });
|
||||
}
|
||||
|
||||
function asUnitInterval(value: unknown): number | undefined {
|
||||
const number = asFiniteNumber(value);
|
||||
return number !== undefined && number >= 0 && number <= 1 ? number : undefined;
|
||||
return asFiniteNumberInRange(value, { min: 0, max: 1 });
|
||||
}
|
||||
|
||||
type OpenAIRealtimeApiKeyResolution =
|
||||
@@ -995,7 +996,7 @@ class OpenAIRealtimeVoiceBridge implements RealtimeVoiceBridge {
|
||||
try {
|
||||
this.handleEvent(event, lifecycleConnection);
|
||||
} catch (error) {
|
||||
const readyError = error instanceof Error ? error : new Error(String(error));
|
||||
const readyError = toStringifiedError(error);
|
||||
attempt.reject(readyError);
|
||||
this.failConnection(readyError, ws, lifecycleConnection, {
|
||||
code: 1011,
|
||||
@@ -1036,7 +1037,7 @@ class OpenAIRealtimeVoiceBridge implements RealtimeVoiceBridge {
|
||||
},
|
||||
});
|
||||
if (!attempt.ready) {
|
||||
const startupError = error instanceof Error ? error : new Error(String(error));
|
||||
const startupError = toStringifiedError(error);
|
||||
rejectStartup(
|
||||
isDirectOpenAIRealtimeWebSocketUrl(url) &&
|
||||
isOpenAIRealtimeStartupAuthFailure(startupError)
|
||||
@@ -1045,7 +1046,7 @@ class OpenAIRealtimeVoiceBridge implements RealtimeVoiceBridge {
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.config.onError?.(error instanceof Error ? error : new Error(String(error)));
|
||||
this.config.onError?.(toStringifiedError(error));
|
||||
});
|
||||
|
||||
ws.on("close", (code, reasonBuffer) => {
|
||||
@@ -1091,7 +1092,7 @@ class OpenAIRealtimeVoiceBridge implements RealtimeVoiceBridge {
|
||||
try {
|
||||
connectionOrPromise = this.resolveConnectionParams();
|
||||
} catch (error) {
|
||||
attempt.reject(error instanceof Error ? error : new Error(String(error)));
|
||||
attempt.reject(toStringifiedError(error));
|
||||
return attempt.promise;
|
||||
}
|
||||
if (connectionOrPromise instanceof Promise) {
|
||||
@@ -1103,13 +1104,13 @@ class OpenAIRealtimeVoiceBridge implements RealtimeVoiceBridge {
|
||||
attempt.resolve();
|
||||
return;
|
||||
}
|
||||
attempt.reject(error instanceof Error ? error : new Error(String(error)));
|
||||
attempt.reject(toStringifiedError(error));
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
openWebSocket(connectionOrPromise);
|
||||
} catch (error) {
|
||||
attempt.reject(error instanceof Error ? error : new Error(String(error)));
|
||||
attempt.reject(toStringifiedError(error));
|
||||
}
|
||||
}
|
||||
await attempt.promise;
|
||||
@@ -1277,7 +1278,7 @@ class OpenAIRealtimeVoiceBridge implements RealtimeVoiceBridge {
|
||||
if (!this.lifecycle.acceptsEvents(nextConnection)) {
|
||||
return;
|
||||
}
|
||||
this.config.onError?.(error instanceof Error ? error : new Error(String(error)));
|
||||
this.config.onError?.(toStringifiedError(error));
|
||||
await this.attemptReconnect(reason, nextConnection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
toImageDataUrl,
|
||||
} from "openclaw/plugin-sdk/image-generation";
|
||||
import { resolveGeneratedMediaMaxBytes } from "openclaw/plugin-sdk/media-generation-runtime";
|
||||
import { resolveIntegerOption } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime";
|
||||
import {
|
||||
@@ -171,10 +172,7 @@ function extractOpenRouterImagesFromResponse(body: unknown): GeneratedImageAsset
|
||||
}
|
||||
|
||||
function resolveImageCount(count: number | undefined): number {
|
||||
if (typeof count !== "number" || !Number.isFinite(count)) {
|
||||
return 1;
|
||||
}
|
||||
return Math.max(1, Math.min(MAX_IMAGE_RESULTS, Math.trunc(count)));
|
||||
return resolveIntegerOption(count, 1, { min: 1, max: MAX_IMAGE_RESULTS });
|
||||
}
|
||||
|
||||
function isGeminiImageModel(model: string): boolean {
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import {
|
||||
asOptionalRecord,
|
||||
asPositiveSafeInteger,
|
||||
filterStringEntries,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
@@ -140,10 +141,7 @@ export function buildOpenrouterProvider(): ModelProviderConfig {
|
||||
}
|
||||
|
||||
function readStringArray(record: Record<string, unknown> | undefined, key: string): string[] {
|
||||
const value = record?.[key];
|
||||
return Array.isArray(value)
|
||||
? value.filter((entry): entry is string => typeof entry === "string")
|
||||
: [];
|
||||
return filterStringEntries(record?.[key]);
|
||||
}
|
||||
|
||||
function readTokenPrice(record: Record<string, unknown> | undefined, key: string): number {
|
||||
|
||||
@@ -57,10 +57,6 @@ type OpenRouterVideoRequestPolicyCacheKey = ReturnType<
|
||||
|
||||
type OpenRouterVideoRequestConfig = Parameters<typeof sanitizeConfiguredModelProviderRequest>[0];
|
||||
|
||||
function normalizeStringArray(value: unknown): string[] {
|
||||
return normalizeTrimmedStringList(value);
|
||||
}
|
||||
|
||||
function normalizeNumberArray(value: unknown): number[] {
|
||||
return Array.isArray(value)
|
||||
? value.filter((entry): entry is number => typeof entry === "number" && Number.isFinite(entry))
|
||||
@@ -68,14 +64,14 @@ function normalizeNumberArray(value: unknown): number[] {
|
||||
}
|
||||
|
||||
function normalizeResolutionArray(value: unknown): VideoGenerationResolution[] {
|
||||
return normalizeStringArray(value).map(
|
||||
return normalizeTrimmedStringList(value).map(
|
||||
(entry) => entry.toUpperCase() as VideoGenerationResolution,
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeFrameImageRoles(value: unknown): Array<"first_frame" | "last_frame"> {
|
||||
const seen = new Set<"first_frame" | "last_frame">();
|
||||
for (const entry of normalizeStringArray(value)) {
|
||||
for (const entry of normalizeTrimmedStringList(value)) {
|
||||
if (entry === "first_frame" || entry === "last_frame") {
|
||||
seen.add(entry);
|
||||
}
|
||||
@@ -136,12 +132,14 @@ function buildOpenRouterVideoModeCapabilities(params: {
|
||||
function buildOpenRouterVideoModelCapabilities(
|
||||
model: OpenRouterVideoModel,
|
||||
): OpenRouterVideoModelCatalogCapabilities {
|
||||
const aspectRatios = normalizeStringArray(model.supported_aspect_ratios);
|
||||
const aspectRatios = normalizeTrimmedStringList(model.supported_aspect_ratios);
|
||||
const durations = normalizeNumberArray(model.supported_durations);
|
||||
const frameImages = normalizeFrameImageRoles(model.supported_frame_images);
|
||||
const resolutions = normalizeResolutionArray(model.supported_resolutions);
|
||||
const sizes = normalizeStringArray(model.supported_sizes);
|
||||
const allowedPassthroughParameters = normalizeStringArray(model.allowed_passthrough_parameters);
|
||||
const sizes = normalizeTrimmedStringList(model.supported_sizes);
|
||||
const allowedPassthroughParameters = normalizeTrimmedStringList(
|
||||
model.allowed_passthrough_parameters,
|
||||
);
|
||||
const supportsAudio =
|
||||
typeof model.generate_audio === "boolean" ? model.generate_audio : undefined;
|
||||
const modeCapabilities = buildOpenRouterVideoModeCapabilities({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { resolveIntegerOption } from "openclaw/plugin-sdk/number-runtime";
|
||||
// Transport-agnostic Parallel search normalization shared by the paid REST
|
||||
// provider (`parallel`) and the free Search MCP provider (`parallel-free`).
|
||||
// Both transports return the same v1 result shape, so query/result handling
|
||||
@@ -53,7 +54,10 @@ export function resolveParallelSearchCount(
|
||||
const value =
|
||||
requestedCount ??
|
||||
(typeof configuredCount === "number" ? configuredCount : DEFAULT_SEARCH_COUNT);
|
||||
return Math.max(1, Math.min(PARALLEL_MAX_SEARCH_COUNT, Math.floor(value)));
|
||||
return resolveIntegerOption(value, DEFAULT_SEARCH_COUNT, {
|
||||
min: 1,
|
||||
max: PARALLEL_MAX_SEARCH_COUNT,
|
||||
});
|
||||
}
|
||||
|
||||
export function normalizeParallelSessionId(
|
||||
|
||||
@@ -5,7 +5,10 @@ import path from "node:path";
|
||||
import { StringDecoder } from "node:string_decoder";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { readStringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
asNullableRecord as readRecord,
|
||||
readStringValue,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import pLimit from "p-limit";
|
||||
import type {
|
||||
QaEvidenceArtifactView,
|
||||
@@ -565,12 +568,6 @@ async function buildArtifactView(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function readRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: null;
|
||||
}
|
||||
|
||||
function readCountRecord(value: unknown): Record<string, number> {
|
||||
const record = readRecord(value);
|
||||
if (!record) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import type { QaRunnerCliRegistration } from "openclaw/plugin-sdk/qa-runner-runtime";
|
||||
import {
|
||||
acquireQaCredentialLease,
|
||||
@@ -96,7 +97,7 @@ export async function createDiscordQaTransportAdapter(
|
||||
}
|
||||
})().catch((error: unknown) => {
|
||||
if (!stopped) {
|
||||
pollingError = error instanceof Error ? error : new Error(String(error));
|
||||
pollingError = toStringifiedError(error);
|
||||
}
|
||||
});
|
||||
const scenarioEnvironment = createDiscordQaScenarioEnvironment({
|
||||
|
||||
@@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto";
|
||||
import path from "node:path";
|
||||
import { setTimeout as sleep } from "node:timers/promises";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { buildQaTarget } from "openclaw/plugin-sdk/qa-channel-protocol";
|
||||
import type { QaRunnerCliRegistration } from "openclaw/plugin-sdk/qa-runner-runtime";
|
||||
import { readQaScenarioExecutionConfig } from "../../scenario-catalog.js";
|
||||
@@ -324,7 +325,7 @@ export async function createMatrixQaTransportAdapter(
|
||||
}),
|
||||
).catch((error: unknown) => {
|
||||
if (!stopped) {
|
||||
pollingError = error instanceof Error ? error : new Error(String(error));
|
||||
pollingError = toStringifiedError(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "@openclaw/slack/api.js";
|
||||
import type { FetchFunction } from "@slack/web-api";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { acquireDebugProxyCaptureStore } from "openclaw/plugin-sdk/proxy-capture";
|
||||
import type { QaRunnerCliRegistration } from "openclaw/plugin-sdk/qa-runner-runtime";
|
||||
import {
|
||||
@@ -222,7 +223,7 @@ export async function createSlackQaTransportAdapter(
|
||||
}
|
||||
})().catch((error: unknown) => {
|
||||
if (!stopped) {
|
||||
pollingError = error instanceof Error ? error : new Error(String(error));
|
||||
pollingError = toStringifiedError(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { WhatsAppQaDriverSession } from "@openclaw/whatsapp/api.js";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { buildQaTarget } from "openclaw/plugin-sdk/qa-channel-protocol";
|
||||
import type { QaRunnerCliRegistration } from "openclaw/plugin-sdk/qa-runner-runtime";
|
||||
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
||||
@@ -130,7 +131,7 @@ export async function createWhatsAppQaTransportAdapter(
|
||||
}
|
||||
})().catch((error: unknown) => {
|
||||
if (!stopped) {
|
||||
pollingError = error instanceof Error ? error : new Error(String(error));
|
||||
pollingError = toStringifiedError(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Qa Lab plugin module implements process tree cpu behavior.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { parseStrictFiniteNumber, parseStrictInteger } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { isRecord as isPlainObject } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolveQaWindowsPowerShellExePath } from "./windows-system-tools.js";
|
||||
|
||||
type ProcessTreeSnapshot = {
|
||||
@@ -11,10 +12,6 @@ type ProcessTreeSnapshot = {
|
||||
|
||||
const PROCESS_TREE_SNAPSHOT_TIMEOUT_MS = 5_000;
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function parsePositiveInteger(value: unknown): number | null {
|
||||
const parsed = parseStrictInteger(value);
|
||||
if (parsed === undefined || parsed <= 0) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { isRecord as isJsonRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import {
|
||||
appendQaChildOutput,
|
||||
@@ -83,10 +84,6 @@ function parseBalancedJsonPayloadStart(text: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function isJsonRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function isStructuredDiagnosticJson(value: unknown) {
|
||||
if (!isJsonRecord(value)) {
|
||||
return false;
|
||||
|
||||
@@ -394,12 +394,8 @@ async function formatRuntimePatchMutationDiagnostics(params: {
|
||||
].join("; ");
|
||||
}
|
||||
|
||||
function readNonEmptyString(value: unknown) {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function normalizeToolCallId(value: unknown) {
|
||||
return readNonEmptyString(value);
|
||||
return normalizeOptionalString(value);
|
||||
}
|
||||
|
||||
function stringifyTranscriptToolResult(value: unknown): string {
|
||||
@@ -436,10 +432,10 @@ function extractTranscriptText(value: unknown): string {
|
||||
continue;
|
||||
}
|
||||
const text =
|
||||
readNonEmptyString(block.text) ??
|
||||
readNonEmptyString(block.content) ??
|
||||
readNonEmptyString(block.message) ??
|
||||
readNonEmptyString(block.error);
|
||||
normalizeOptionalString(block.text) ??
|
||||
normalizeOptionalString(block.content) ??
|
||||
normalizeOptionalString(block.message) ??
|
||||
normalizeOptionalString(block.error);
|
||||
if (text) {
|
||||
parts.push(text);
|
||||
}
|
||||
@@ -457,11 +453,11 @@ function extractTranscriptToolCalls(
|
||||
if (!isRecord(block)) {
|
||||
continue;
|
||||
}
|
||||
const type = readNonEmptyString(block.type)?.toLowerCase();
|
||||
const type = normalizeOptionalString(block.type)?.toLowerCase();
|
||||
if (type !== "tool_use" && type !== "toolcall" && type !== "tool_call") {
|
||||
continue;
|
||||
}
|
||||
const tool = readNonEmptyString(block.name);
|
||||
const tool = normalizeOptionalString(block.name);
|
||||
if (!tool) {
|
||||
continue;
|
||||
}
|
||||
@@ -486,7 +482,8 @@ function extractTranscriptToolCalls(
|
||||
continue;
|
||||
}
|
||||
const functionRecord = isRecord(call.function) ? call.function : undefined;
|
||||
const tool = readNonEmptyString(call.name) ?? readNonEmptyString(functionRecord?.name);
|
||||
const tool =
|
||||
normalizeOptionalString(call.name) ?? normalizeOptionalString(functionRecord?.name);
|
||||
if (!tool) {
|
||||
continue;
|
||||
}
|
||||
@@ -558,10 +555,10 @@ function extractTranscriptToolResults(
|
||||
): QaRuntimeToolFixtureTranscriptToolResult[] {
|
||||
const results: QaRuntimeToolFixtureTranscriptToolResult[] = [];
|
||||
const tool =
|
||||
readNonEmptyString(message.toolName) ??
|
||||
readNonEmptyString(message.tool_name) ??
|
||||
readNonEmptyString(message.name) ??
|
||||
readNonEmptyString(message.tool);
|
||||
normalizeOptionalString(message.toolName) ??
|
||||
normalizeOptionalString(message.tool_name) ??
|
||||
normalizeOptionalString(message.name) ??
|
||||
normalizeOptionalString(message.tool);
|
||||
if ((message.role === "tool" || message.role === "toolResult") && message.content !== undefined) {
|
||||
const text = extractTranscriptText(message.content);
|
||||
const structuredFailure = isStructuredFailureToolResult({
|
||||
@@ -598,7 +595,7 @@ function extractTranscriptToolResults(
|
||||
if (!isRecord(block)) {
|
||||
continue;
|
||||
}
|
||||
const type = readNonEmptyString(block.type)?.toLowerCase();
|
||||
const type = normalizeOptionalString(block.type)?.toLowerCase();
|
||||
if (type !== "tool_result" && type !== "toolresult" && type !== "tool_result_error") {
|
||||
continue;
|
||||
}
|
||||
@@ -611,10 +608,10 @@ function extractTranscriptToolResults(
|
||||
is_error: block.is_error,
|
||||
});
|
||||
const blockTool =
|
||||
readNonEmptyString(block.toolName) ??
|
||||
readNonEmptyString(block.tool_name) ??
|
||||
readNonEmptyString(block.name) ??
|
||||
readNonEmptyString(block.tool);
|
||||
normalizeOptionalString(block.toolName) ??
|
||||
normalizeOptionalString(block.tool_name) ??
|
||||
normalizeOptionalString(block.name) ??
|
||||
normalizeOptionalString(block.tool);
|
||||
results.push({
|
||||
id:
|
||||
normalizeToolCallId(block.tool_use_id) ??
|
||||
@@ -703,7 +700,7 @@ async function readSessionTranscriptBytes(
|
||||
) {
|
||||
const store = await readRawQaSessionStore(env);
|
||||
const entry = store[sessionKey];
|
||||
const sessionId = readNonEmptyString(entry?.sessionId);
|
||||
const sessionId = normalizeOptionalString(entry?.sessionId);
|
||||
if (!sessionId) {
|
||||
throw new Error(`session transcript entry not found for ${sessionKey}`);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeOptionalString as normalizeQaConfigString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { QaCliBackendAuthMode } from "./gateway-child.js";
|
||||
import { splitQaModelRef, type QaProviderMode } from "./model-selection.js";
|
||||
import {
|
||||
@@ -14,10 +15,6 @@ export type QaScenarioExecutionCell = {
|
||||
channel: string | null;
|
||||
};
|
||||
|
||||
function normalizeQaConfigString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function resolveQaScenarioLaneChannels(params: {
|
||||
scenario: QaSeedScenario;
|
||||
channelDriver: QaScorecardChannelDriver;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { toStringifiedError as asError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { buildTimeoutAbortSignal } from "openclaw/plugin-sdk/extension-shared";
|
||||
import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
|
||||
import WebSocket from "ws";
|
||||
@@ -590,10 +591,6 @@ export class ReefInboxConnection {
|
||||
}
|
||||
}
|
||||
|
||||
function asError(error: unknown): Error {
|
||||
return error instanceof Error ? error : new Error(String(error));
|
||||
}
|
||||
|
||||
function reefInboxCloseError(event: { code?: number; reason?: string }): Error {
|
||||
const code = Number.isInteger(event.code) ? ` code=${event.code}` : "";
|
||||
const reason = event.reason?.trim() ? ` reason=${event.reason.trim()}` : "";
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
normalizeResolvedSecretInputString,
|
||||
normalizeSecretInput,
|
||||
} from "openclaw/plugin-sdk/secret-input";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
type SearxngPluginConfig = {
|
||||
webSearch?: {
|
||||
@@ -37,14 +38,6 @@ function readInlineEnvSecretRefValue(value: unknown, env: NodeJS.ProcessEnv): st
|
||||
return normalizeSecretInput(env[record.id]);
|
||||
}
|
||||
|
||||
function normalizeTrimmedString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
function normalizeBaseUrl(value: string | undefined): string | undefined {
|
||||
return value?.replace(/\/+$/u, "") || undefined;
|
||||
}
|
||||
@@ -78,9 +71,9 @@ export function resolveSearxngBaseUrl(
|
||||
}
|
||||
|
||||
export function resolveSearxngCategories(config?: OpenClawConfig): string | undefined {
|
||||
return normalizeTrimmedString(resolveSearxngWebSearchConfig(config)?.categories);
|
||||
return normalizeOptionalString(resolveSearxngWebSearchConfig(config)?.categories);
|
||||
}
|
||||
|
||||
export function resolveSearxngLanguage(config?: OpenClawConfig): string | undefined {
|
||||
return normalizeTrimmedString(resolveSearxngWebSearchConfig(config)?.language);
|
||||
return normalizeOptionalString(resolveSearxngWebSearchConfig(config)?.language);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import {
|
||||
renderMessagePresentationTableFallbackText,
|
||||
type MessagePresentationTableBlock,
|
||||
} from "openclaw/plugin-sdk/interactive-runtime";
|
||||
import { asOptionalRecord, hasNonEmptyString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
asOptionalRecord,
|
||||
readNonBlankString as readNonEmptyString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { escapeSlackMrkdwn } from "./monitor/mrkdwn.js";
|
||||
import { renderSlackMessagePresentationTableFallbackText } from "./presentation-fallback.js";
|
||||
|
||||
@@ -44,10 +47,6 @@ type ParsedSlackDataTable = {
|
||||
cellCharacterCount: number;
|
||||
};
|
||||
|
||||
function readNonEmptyString(value: unknown): string | undefined {
|
||||
return hasNonEmptyString(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function countCharacters(value: string): number {
|
||||
return Array.from(value).length;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/number-runtime";
|
||||
import { classifyTransientNetworkErrorCode } from "openclaw/plugin-sdk/retry-runtime";
|
||||
import { logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { normalizeOptionalString as normalizeThreadTs } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { formatSlackError } from "../errors.js";
|
||||
import type { SlackMessageEvent } from "../types.js";
|
||||
import type { SlackIngressTurnLifecycle } from "./ingress.js";
|
||||
@@ -30,11 +31,6 @@ type ThreadTsCacheEntry = {
|
||||
const DEFAULT_THREAD_TS_CACHE_TTL_MS = 60_000;
|
||||
const DEFAULT_THREAD_TS_CACHE_MAX = 500;
|
||||
|
||||
const normalizeThreadTs = (threadTs?: string | null) => {
|
||||
const trimmed = threadTs?.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
};
|
||||
|
||||
const markAmbiguousThreadReply = (message: SlackMessageEvent): SlackMessageEvent => ({
|
||||
...message,
|
||||
_ambiguousThreadReply: true,
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { SpeechVoiceOption } from "openclaw/plugin-sdk/speech";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolveActiveTalkProviderConfig } from "openclaw/plugin-sdk/talk-config-runtime";
|
||||
import { definePluginEntry, type OpenClawPluginApi } from "./api.js";
|
||||
@@ -95,11 +96,7 @@ function findVoice(voices: SpeechVoiceOption[], query: string): SpeechVoiceOptio
|
||||
}
|
||||
|
||||
function asTrimmedString(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
function parsePositiveIntegerToken(value: unknown): number | undefined {
|
||||
return parseStrictPositiveInteger(value);
|
||||
return normalizeOptionalString(value) ?? "";
|
||||
}
|
||||
|
||||
function resolveCommandLabel(channel: string): string {
|
||||
@@ -171,7 +168,7 @@ export default definePluginEntry({
|
||||
}
|
||||
|
||||
if (action === "list") {
|
||||
const limit = parsePositiveIntegerToken(tokens[1]) ?? 12;
|
||||
const limit = parseStrictPositiveInteger(tokens[1]) ?? 12;
|
||||
try {
|
||||
const voices = await api.runtime.tts.listVoices({
|
||||
provider: providerId,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
* without requiring a gateway restart.
|
||||
*/
|
||||
|
||||
import { filterStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { UrbitSSEClient } from "./urbit/sse-client.js";
|
||||
|
||||
/** Pending approval request stored for persistence */
|
||||
@@ -112,10 +113,10 @@ function parseSettingsResponse(raw: unknown): TlonSettingsStore {
|
||||
|
||||
return {
|
||||
groupChannels: Array.isArray(settings.groupChannels)
|
||||
? settings.groupChannels.filter((x): x is string => typeof x === "string")
|
||||
? filterStringEntries(settings.groupChannels)
|
||||
: undefined,
|
||||
dmAllowlist: Array.isArray(settings.dmAllowlist)
|
||||
? settings.dmAllowlist.filter((x): x is string => typeof x === "string")
|
||||
? filterStringEntries(settings.dmAllowlist)
|
||||
: undefined,
|
||||
autoDiscoverChannels:
|
||||
typeof settings.autoDiscoverChannels === "boolean"
|
||||
@@ -129,11 +130,11 @@ function parseSettingsResponse(raw: unknown): TlonSettingsStore {
|
||||
? settings.autoAcceptGroupInvites
|
||||
: undefined,
|
||||
groupInviteAllowlist: Array.isArray(settings.groupInviteAllowlist)
|
||||
? settings.groupInviteAllowlist.filter((x): x is string => typeof x === "string")
|
||||
? filterStringEntries(settings.groupInviteAllowlist)
|
||||
: undefined,
|
||||
channelRules: parseChannelRules(settings.channelRules),
|
||||
defaultAuthorizedShips: Array.isArray(settings.defaultAuthorizedShips)
|
||||
? settings.defaultAuthorizedShips.filter((x): x is string => typeof x === "string")
|
||||
? filterStringEntries(settings.defaultAuthorizedShips)
|
||||
: undefined,
|
||||
ownerShip: typeof settings.ownerShip === "string" ? settings.ownerShip : undefined,
|
||||
pendingApprovals: parsePendingApprovals(settings.pendingApprovals),
|
||||
@@ -242,14 +243,10 @@ function applySettingsUpdate(
|
||||
|
||||
switch (key) {
|
||||
case "groupChannels":
|
||||
next.groupChannels = Array.isArray(value)
|
||||
? value.filter((x): x is string => typeof x === "string")
|
||||
: undefined;
|
||||
next.groupChannels = Array.isArray(value) ? filterStringEntries(value) : undefined;
|
||||
break;
|
||||
case "dmAllowlist":
|
||||
next.dmAllowlist = Array.isArray(value)
|
||||
? value.filter((x): x is string => typeof x === "string")
|
||||
: undefined;
|
||||
next.dmAllowlist = Array.isArray(value) ? filterStringEntries(value) : undefined;
|
||||
break;
|
||||
case "autoDiscoverChannels":
|
||||
next.autoDiscoverChannels = typeof value === "boolean" ? value : undefined;
|
||||
@@ -264,17 +261,13 @@ function applySettingsUpdate(
|
||||
next.autoAcceptGroupInvites = typeof value === "boolean" ? value : undefined;
|
||||
break;
|
||||
case "groupInviteAllowlist":
|
||||
next.groupInviteAllowlist = Array.isArray(value)
|
||||
? value.filter((x): x is string => typeof x === "string")
|
||||
: undefined;
|
||||
next.groupInviteAllowlist = Array.isArray(value) ? filterStringEntries(value) : undefined;
|
||||
break;
|
||||
case "channelRules":
|
||||
next.channelRules = parseChannelRules(value);
|
||||
break;
|
||||
case "defaultAuthorizedShips":
|
||||
next.defaultAuthorizedShips = Array.isArray(value)
|
||||
? value.filter((x): x is string => typeof x === "string")
|
||||
: undefined;
|
||||
next.defaultAuthorizedShips = Array.isArray(value) ? filterStringEntries(value) : undefined;
|
||||
break;
|
||||
case "ownerShip":
|
||||
next.ownerShip = typeof value === "string" ? value : undefined;
|
||||
|
||||
@@ -14,7 +14,7 @@ import type {
|
||||
SpeechSynthesisRequest,
|
||||
SpeechTelephonySynthesisRequest,
|
||||
} from "openclaw/plugin-sdk/speech-core";
|
||||
import { asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { asOptionalRecord, filterStringRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { tempWorkspace, resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
|
||||
@@ -41,20 +41,6 @@ function asStringArray(value: unknown): string[] | undefined {
|
||||
return Array.isArray(value) && value.every((v) => typeof v === "string") ? value : undefined;
|
||||
}
|
||||
|
||||
function readStringRecord(value: unknown): Record<string, string> | undefined {
|
||||
const obj = asOptionalRecord(value);
|
||||
if (!obj) {
|
||||
return undefined;
|
||||
}
|
||||
const result: Record<string, string> = {};
|
||||
for (const [k, v] of Object.entries(obj)) {
|
||||
if (typeof v === "string") {
|
||||
result[k] = v;
|
||||
}
|
||||
}
|
||||
return Object.keys(result).length > 0 ? result : undefined;
|
||||
}
|
||||
|
||||
function normalizeOutputFormat(value: unknown): OutputFormat {
|
||||
if (typeof value !== "string") {
|
||||
return "mp3";
|
||||
@@ -82,7 +68,7 @@ function getConfig(cfg: SpeechProviderConfig): CliConfig | null {
|
||||
outputFormat: normalizeOutputFormat(cfg.outputFormat),
|
||||
timeoutMs: typeof cfg.timeoutMs === "number" ? cfg.timeoutMs : DEFAULT_TIMEOUT_MS,
|
||||
cwd: typeof cfg.cwd === "string" ? cfg.cwd : undefined,
|
||||
env: readStringRecord(cfg.env),
|
||||
env: filterStringRecord(cfg.env),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ErrorCodes, errorShape } from "openclaw/plugin-sdk/gateway-runtime";
|
||||
import { resolveGlobalSingleton } from "openclaw/plugin-sdk/global-singleton";
|
||||
import { normalizeAgentId, parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
|
||||
import {
|
||||
asNonArrayRecord,
|
||||
asNonArrayRecord as asParamRecord,
|
||||
asOptionalRecord,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
@@ -88,10 +88,6 @@ const VoiceCallToolSchema = Type.Union([
|
||||
}),
|
||||
]);
|
||||
|
||||
function asParamRecord(params: unknown): Record<string, unknown> {
|
||||
return asNonArrayRecord(params);
|
||||
}
|
||||
|
||||
function isCliOnlyProcess(): boolean {
|
||||
return process.env.OPENCLAW_CLI === "1" && !process.argv.slice(2).includes("gateway");
|
||||
}
|
||||
|
||||
@@ -5,11 +5,6 @@ import {
|
||||
readStringField,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
/** Read finite numeric config values. */
|
||||
function getNumber(obj: Record<string, unknown> | undefined, key: string): number | undefined {
|
||||
return asFiniteNumber(obj?.[key]);
|
||||
}
|
||||
|
||||
/** Merge legacy provider-specific values into the canonical providers map. */
|
||||
function mergeProviderConfig(
|
||||
providersValue: unknown,
|
||||
@@ -55,11 +50,11 @@ export function migrateVoiceCallLegacyConfigInput(params: {
|
||||
if (streamingSttModel) {
|
||||
legacyStreamingOpenAICompat.model = streamingSttModel;
|
||||
}
|
||||
const streamingSilenceDurationMs = getNumber(streaming, "silenceDurationMs");
|
||||
const streamingSilenceDurationMs = asFiniteNumber(streaming?.silenceDurationMs);
|
||||
if (streamingSilenceDurationMs !== undefined) {
|
||||
legacyStreamingOpenAICompat.silenceDurationMs = streamingSilenceDurationMs;
|
||||
}
|
||||
const streamingVadThreshold = getNumber(streaming, "vadThreshold");
|
||||
const streamingVadThreshold = asFiniteNumber(streaming?.vadThreshold);
|
||||
if (streamingVadThreshold !== undefined) {
|
||||
legacyStreamingOpenAICompat.vadThreshold = streamingVadThreshold;
|
||||
}
|
||||
@@ -138,14 +133,14 @@ export function migrateVoiceCallLegacyConfigInput(params: {
|
||||
`Moved ${configPathPrefix}.streaming.sttModel → ${configPathPrefix}.streaming.providers.openai.model.`,
|
||||
);
|
||||
}
|
||||
if (getNumber(streaming, "silenceDurationMs") !== undefined) {
|
||||
if (asFiniteNumber(streaming?.silenceDurationMs) !== undefined) {
|
||||
changes.push(
|
||||
`Moved ${configPathPrefix}.streaming.silenceDurationMs → ${configPathPrefix}.streaming.providers.openai.silenceDurationMs.`,
|
||||
);
|
||||
} else if (typeof streaming?.silenceDurationMs === "number") {
|
||||
changes.push(`Removed invalid ${configPathPrefix}.streaming.silenceDurationMs.`);
|
||||
}
|
||||
if (getNumber(streaming, "vadThreshold") !== undefined) {
|
||||
if (asFiniteNumber(streaming?.vadThreshold) !== undefined) {
|
||||
changes.push(
|
||||
`Moved ${configPathPrefix}.streaming.vadThreshold → ${configPathPrefix}.streaming.providers.openai.vadThreshold.`,
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/model-session-runtime";
|
||||
import {
|
||||
isRecord,
|
||||
filterStringEntries,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeStringEntries,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
@@ -66,7 +67,7 @@ function readExplicitToolsAllow(value: unknown): string[] | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return allow.filter((entry): entry is string => typeof entry === "string");
|
||||
return filterStringEntries(allow);
|
||||
}
|
||||
|
||||
function resolveVoiceAgentToolsAllow(config: CoreConfig, agentId: string): string[] | undefined {
|
||||
|
||||
@@ -14,7 +14,10 @@ import {
|
||||
} from "openclaw/plugin-sdk/channel-outbound";
|
||||
import { buildInboundHistoryFromEntries } from "openclaw/plugin-sdk/reply-history";
|
||||
import type { FinalizedMsgContext } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
normalizeOptionalString,
|
||||
normalizeStringEntries,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { requireWhatsAppInboundAdmission } from "../../inbound/admission.js";
|
||||
import type { AdmittedWebInboundMessage } from "../../inbound/types.js";
|
||||
import {
|
||||
@@ -147,7 +150,7 @@ function isWhatsAppVisibleDeliveryError(error: unknown): boolean {
|
||||
}
|
||||
|
||||
function readTrimmedString(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
return normalizeOptionalString(value) ?? "";
|
||||
}
|
||||
|
||||
function markWhatsAppReplyDeliveryErrorVisibleAfterFlush(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user