UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

305 lines (304 loc) • 15.3 kB
import { m as readNonBlankString } from "./string-coerce-1VuiLaaR.mjs"; import { w as parseStrictPositiveInteger } from "./number-coercion-CuVhdCBs.mjs"; import { c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import { d as normalizeStringEntries } from "./string-normalization-CECOqf64.mjs"; import { a as parseConcreteConfigPathWithProvenance, o as toDotPath, r as parseConcreteConfigPath } from "./dot-path-CVD5Jolm.mjs"; import { d as isValidFileSecretRefId, f as isValidSecretProviderAlias, l as isValidEnvSecretRefId, o as formatExecSecretRefIdValidationMessage, v as validateExecSecretRefId } from "./ref-contract-D0kWy5jy.mjs"; import { a as coerceSecretRef } from "./types.secrets-DsPRn9Dz.mjs"; import { t as formatCliCommand } from "./command-format-DwEdrDKL.mjs"; import { _ as SecretProviderSchema } from "./zod-schema.core-MaNAY9dn.mjs"; import { c as resolveConfigSecretTargetByPath } from "./target-registry-query-djbCpoM3.mjs"; import "./target-registry-C9njyDoW.mjs"; import { n as visitConfigValueTree } from "./value-tree-3oLf9xok.mjs"; import { t as readByteStreamWithLimit } from "./read-byte-stream-with-limit-CNew-qG0.mjs"; import { n as formatConfigSetPath, s as parseConfigSetValue, u as validatePathSegments } from "./config-cli-path-Dh9BCpH3.mjs"; import { a as resolveConfigSetMode, i as readConfigMutationFileSync, n as parseConfigMutationJson5, t as parseBatchSource } from "./config-set-input-CnFLn9KI.mjs"; //#region src/cli/config-cli-input.ts const CONFIG_PATCH_STDIN_MAX_BYTES = 1048576; function modeError(message) { return /* @__PURE__ */ new Error(`config set mode error: ${message}`); } function configPatchModeError(message) { return /* @__PURE__ */ new Error(`config patch mode error: ${message}`); } function parseSecretRefSource(raw, label) { const source = raw.trim(); if (source === "env" || source === "file" || source === "exec" || source === "store") return source; throw new Error(`${label} must be one of: env, file, exec, store.`); } function parseSecretRefBuilder(params) { const provider = params.provider.trim(); if (!provider) throw new Error(`${params.fieldPrefix}.provider is required.`); if (!isValidSecretProviderAlias(provider)) throw new Error(`${params.fieldPrefix}.provider must match /^[a-z][a-z0-9_-]{0,63}$/ (example: "default").`); const source = parseSecretRefSource(params.source, `${params.fieldPrefix}.source`); const id = params.id.trim(); if (!id) throw new Error(`${params.fieldPrefix}.id is required.`); if ((source === "env" || source === "store") && !isValidEnvSecretRefId(id)) throw new Error(`${params.fieldPrefix}.id must match /^[A-Z][A-Z0-9_]{0,127}$/ for ${source} refs.`); if (source === "file" && !isValidFileSecretRefId(id)) throw new Error(`${params.fieldPrefix}.id must be an absolute JSON pointer (or "value" for singleValue mode).`); if (source === "exec" && !validateExecSecretRefId(id).ok) throw new Error(formatExecSecretRefIdValidationMessage()); return { source, provider, id }; } function parseOptionalPositiveInteger(raw, flag) { if (raw === void 0) return; const trimmed = raw.trim(); if (!trimmed) throw new Error(`${flag} must not be empty.`); const parsed = parseStrictPositiveInteger(trimmed); if (parsed === void 0) throw new Error(`${flag} must be a positive integer.`); return parsed; } function parseProviderEnvEntries(entries) { if (!entries || entries.length === 0) return; const env = {}; for (const entry of entries) { const separator = entry.indexOf("="); if (separator <= 0) throw new Error("--provider-env expects KEY=*** entries."); const key = entry.slice(0, separator).trim(); if (!key) throw new Error("--provider-env key must not be empty."); env[key] = entry.slice(separator + 1); } return Object.keys(env).length > 0 ? env : void 0; } function validateProviderAliasPath(path) { if (path.length !== 3 || path[0] !== "secrets" || path[1] !== "providers") throw new Error("Provider builder mode requires path \"secrets.providers.<alias>\" (example: secrets.providers.vault)."); const alias = path[2] ?? ""; if (!isValidSecretProviderAlias(alias)) throw new Error(`Provider alias "${alias}" must match /^[a-z][a-z0-9_-]{0,63}$/ (example: "default").`); } function buildProviderFromBuilder(opts) { const sourceRaw = opts.providerSource?.trim(); if (!sourceRaw) throw new Error("--provider-source is required in provider builder mode."); const source = parseSecretRefSource(sourceRaw, "--provider-source"); const timeoutMs = parseOptionalPositiveInteger(opts.providerTimeoutMs, "--provider-timeout-ms"); const maxBytes = parseOptionalPositiveInteger(opts.providerMaxBytes, "--provider-max-bytes"); const noOutputTimeoutMs = parseOptionalPositiveInteger(opts.providerNoOutputTimeoutMs, "--provider-no-output-timeout-ms"); const maxOutputBytes = parseOptionalPositiveInteger(opts.providerMaxOutputBytes, "--provider-max-output-bytes"); const providerEnv = parseProviderEnvEntries(opts.providerEnv); let provider; if (source === "env") { const allowlist = normalizeStringEntries(opts.providerAllowlist); for (const envName of allowlist) if (!isValidEnvSecretRefId(envName)) throw new Error(`--provider-allowlist entry "${envName}" must match /^[A-Z][A-Z0-9_]{0,127}$/.`); provider = { source: "env", ...allowlist.length > 0 ? { allowlist } : {} }; } else if (source === "file") { const filePath = opts.providerPath?.trim(); if (!filePath) throw new Error("--provider-path is required when --provider-source file is used."); const modeRaw = opts.providerMode?.trim(); if (modeRaw && modeRaw !== "singleValue" && modeRaw !== "json") throw new Error("--provider-mode must be one of: singleValue, json."); const mode = modeRaw === "singleValue" || modeRaw === "json" ? modeRaw : void 0; provider = { source: "file", path: filePath, ...mode ? { mode } : {}, ...timeoutMs !== void 0 ? { timeoutMs } : {}, ...maxBytes !== void 0 ? { maxBytes } : {} }; } else if (source === "store") provider = { source: "store" }; else { const command = opts.providerCommand?.trim(); if (!command) throw new Error("--provider-command is required when --provider-source exec is used."); provider = { source: "exec", command, ...opts.providerArg?.length ? { args: opts.providerArg } : {}, ...timeoutMs !== void 0 ? { timeoutMs } : {}, ...noOutputTimeoutMs !== void 0 ? { noOutputTimeoutMs } : {}, ...maxOutputBytes !== void 0 ? { maxOutputBytes } : {}, ...opts.providerJsonOnly ? { jsonOnly: true } : {}, ...providerEnv ? { env: providerEnv } : {}, ...opts.providerPassEnv?.length ? { passEnv: normalizeStringEntries(opts.providerPassEnv) } : {}, ...opts.providerTrustedDir?.length ? { trustedDirs: normalizeStringEntries(opts.providerTrustedDir) } : {} }; } const validated = SecretProviderSchema.safeParse(provider); if (!validated.success) { const issue = validated.error.issues[0]; throw new Error(`Provider builder config invalid at ${issue?.path?.join(".") ?? "<provider>"}: ${issue?.message ?? "Invalid provider config."}`); } return validated.data; } function parseSecretRefFromUnknown(value, label) { if (!isRecord(value)) throw new Error(`${label} must be an object with source/provider/id.`); if (typeof value.provider !== "string" || typeof value.source !== "string" || typeof value.id !== "string") throw new Error(`${label} must include string fields: source, provider, id.`); return parseSecretRefBuilder({ provider: value.provider, source: value.source, id: value.id, fieldPrefix: label }); } function buildAssignmentOperation(params) { const resolved = resolveConfigSecretTargetByPath(params.requestedPath, params.pathTokens); const coercedRef = coerceSecretRef(params.value); return { inputMode: params.inputMode, requestedPath: params.requestedPath, ...params.pathTokens ? { pathTokens: params.pathTokens } : {}, ...params.quotedNumericSegments ? { quotedNumericSegments: params.quotedNumericSegments } : {}, setPath: coercedRef && resolved?.entry.secretShape === "sibling_ref" && resolved.refPathSegments ? resolved.refPathSegments : params.requestedPath, value: params.value, ...params.validatedRef && resolved ? { schemaValidated: true } : {} }; } function parseBatchOperations(entries) { return entries.map((entry, index) => { const { tokens: pathTokens, quotedNumericSegments } = parseConcreteConfigPathWithProvenance(entry.path); const path = pathTokens.map(String); const pathFields = { requestedPath: path, pathTokens, quotedNumericSegments }; if (entry.ref === void 0 && entry.provider !== void 0) { validateProviderAliasPath(path); const validated = SecretProviderSchema.safeParse(entry.provider); if (!validated.success) { const issue = validated.error.issues[0]; throw new Error(`batch[${index}].provider invalid at ${issue?.path?.join(".") ?? "<provider>"}: ${issue?.message ?? ""}`); } return { inputMode: "json", ...pathFields, setPath: path, value: validated.data, schemaValidated: true }; } return buildAssignmentOperation({ ...pathFields, value: entry.ref === void 0 ? entry.value : parseSecretRefFromUnknown(entry.ref, `batch[${index}].ref`), inputMode: "json", validatedRef: entry.ref !== void 0 }); }); } function buildConfigSetOperations(params) { const strictJson = Boolean(params.opts.strictJson || params.opts.json); const mode = resolveConfigSetMode(params.opts); if (params.opts.allowExec && !params.opts.dryRun) throw modeError("--allow-exec requires --dry-run."); if (params.opts.merge && params.opts.replace) throw modeError("choose either --merge or --replace, not both."); const batchEntries = parseBatchSource(params.opts); if (batchEntries) { if (params.path !== void 0 || params.value !== void 0) throw modeError("batch mode does not accept <path> or <value> arguments."); return parseBatchOperations(batchEntries); } const parsedConcretePath = typeof params.path === "string" && params.path.trim() ? parseConcreteConfigPathWithProvenance(params.path) : void 0; if (!parsedConcretePath) throw modeError(mode === "ref_builder" ? "ref builder mode requires <path>." : mode === "provider_builder" ? "provider builder mode requires <path>." : "value/json mode requires <path> when batch mode is not used."); const pathFields = { requestedPath: parsedConcretePath.tokens.map(String), pathTokens: parsedConcretePath.tokens, quotedNumericSegments: parsedConcretePath.quotedNumericSegments }; if (mode === "ref_builder") { if (params.value !== void 0) throw modeError("ref builder mode does not accept <value>."); if (!params.opts.refProvider || !params.opts.refSource || !params.opts.refId) throw modeError("ref builder mode requires --ref-provider <alias>, --ref-source <env|file|exec|store>, and --ref-id <id>."); return [buildAssignmentOperation({ ...pathFields, value: parseSecretRefBuilder({ provider: params.opts.refProvider, source: params.opts.refSource, id: params.opts.refId, fieldPrefix: "ref" }), inputMode: "builder", validatedRef: true })]; } if (mode === "provider_builder") { if (params.value !== void 0) throw modeError("provider builder mode does not accept <value>."); const value = buildProviderFromBuilder(params.opts); validateProviderAliasPath(pathFields.requestedPath); return [{ inputMode: "builder", ...pathFields, setPath: pathFields.requestedPath, value, schemaValidated: true }]; } if (params.value === void 0) throw modeError("value/json mode requires <value>."); return [buildAssignmentOperation({ ...pathFields, value: parseConfigSetValue(params.value, strictJson), inputMode: mode === "json" ? "json" : "value" })]; } async function readStdinText() { if (process.stdin.isTTY) throw configPatchModeError("--stdin refuses to read from an interactive terminal; pipe input or use --file <path>."); process.stdin.setEncoding("utf8"); return (await readByteStreamWithLimit(process.stdin, { maxBytes: CONFIG_PATCH_STDIN_MAX_BYTES, onOverflow: ({ maxBytes }) => configPatchModeError(`--stdin input exceeds ${maxBytes} bytes; use --file <path> for larger patches.`) })).toString("utf8"); } function buildUnsetOperation(path, pathTokens) { return { inputMode: "unset", requestedPath: path, ...pathTokens ? { pathTokens } : {}, setPath: path, value: void 0, mutation: "delete" }; } async function readConfigPatchOperations(opts) { const file = readNonBlankString(opts.file); const stdin = Boolean(opts.stdin); if (Boolean(file) === stdin) throw configPatchModeError("provide exactly one of --file <path> or --stdin."); const sourceLabel = stdin ? "--stdin" : "--file"; const raw = file ? readConfigMutationFileSync(file, "--file") : await readStdinText(); const patch = parseConfigMutationJson5(raw, `${sourceLabel} as JSON5`); const replacePaths = (opts.replacePath ?? []).map(parseConcreteConfigPath); if (!isRecord(patch)) throw configPatchModeError("input must be a JSON5 object patch."); const operations = []; const pathKey = (path) => JSON.stringify(path); const replacePathKeys = new Set(replacePaths.map(pathKey)); const replacePathLengths = new Set(replacePaths.map((path) => path.length)); const matchedReplacePathKeys = /* @__PURE__ */ new Set(); visitConfigValueTree(patch, (value, path) => { const segment = path.at(-1); if (segment === void 0) return true; validatePathSegments([segment]); const replacementKey = replacePathLengths.has(path.length) ? pathKey(path) : void 0; const replace = replacementKey !== void 0 && replacePathKeys.has(replacementKey); if (replace) matchedReplacePathKeys.add(replacementKey); const ref = isRecord(value) ? coerceSecretRef(value) : null; const mergeObject = !replace && isRecord(value) && !ref; if (mergeObject && Object.keys(value).length > 0) return true; if (value === null) operations.push({ ...buildUnsetOperation([...path]), inputMode: "json" }); else { const operation = buildAssignmentOperation({ requestedPath: [...path], value: ref ? parseSecretRefFromUnknown(value, `patch.${toDotPath(path)}`) : value, inputMode: "json", validatedRef: Boolean(ref) }); if (replace || mergeObject) operation.mutation = replace ? "replace" : "merge"; operations.push(operation); } return false; }); const unusedReplacePath = replacePaths.find((replacePath) => !matchedReplacePathKeys.has(pathKey(replacePath))); if (unusedReplacePath) throw configPatchModeError(`--replace-path ${formatConfigSetPath(unusedReplacePath)} did not match any value in the input patch.`); if (operations.length === 0) throw configPatchModeError("input patch did not contain any config updates."); return operations; } function formatPluginInstallConfigSetError() { return [ "plugins.installs is managed by the plugin index and cannot be edited with config set.", "", "Use plugin commands instead:", ` ${formatCliCommand("openclaw plugins install <spec>")}`, ` ${formatCliCommand("openclaw plugins update <plugin-id>")}`, ` ${formatCliCommand("openclaw plugins uninstall <plugin-id>")}` ].join("\n"); } //#endregion export { readConfigPatchOperations as a, formatPluginInstallConfigSetError as i, buildUnsetOperation as n, configPatchModeError as r, buildConfigSetOperations as t };