UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

332 lines (331 loc) 13.3 kB
import { c as isRecord } from "./record-coerce-DItp3I4t.js"; import { t as isBlockedObjectKey } from "./prototype-keys-CuYw53fZ.js"; import { t as formatCliCommand } from "./command-format-C7YfyMTd.js"; import { t as parseConfigPathArrayIndex } from "./path-array-index-CvEcUJa-.js"; import { n as formatConcreteConfigPath, o as toDotPath } from "./dot-path-BOSboevO.js"; import { u as rejectConfigNonFiniteNumbers } from "./io.read-helpers-ZKp-UiGx.js"; import { s as formatStrictJsonParseFailure } from "./error-format-Cae7EwnT.js"; import JSON5 from "json5"; //#region src/cli/config-cli-path.ts function formatConfigSetPath(path, pathTokens, source) { return formatConcreteConfigPath(pathTokens ?? path, source); } function parseIndexSegment(raw) { return parseConfigPathArrayIndex(raw); } function isIndexSegment(raw) { return parseIndexSegment(raw) !== void 0; } function parseConfigSetValue(raw, strictJson) { const trimmed = raw.trim(); if (strictJson) { let parsed; try { parsed = JSON.parse(trimmed); } catch (err) { throw new Error(formatStrictJsonParseFailure({ value: raw, cause: err }), { cause: err }); } rejectConfigNonFiniteNumbers(parsed); return parsed; } let parsed; try { parsed = JSON5.parse(trimmed); } catch { return raw; } rejectConfigNonFiniteNumbers(parsed); return parsed; } function validatePathSegments(path) { for (const segment of path) if (!isIndexSegment(segment) && isBlockedObjectKey(segment)) throw new Error(`Invalid path segment: ${segment}`); } function hasOwnPathKey(value, key) { return Object.hasOwn(value, key); } function getAtPath(root, path) { let current = root; for (const segment of path) { if (!current || typeof current !== "object") return { found: false }; if (Array.isArray(current)) { const index = parseIndexSegment(segment); if (index === void 0 || index >= current.length) return { found: false }; current = current[index]; continue; } const record = current; if (!hasOwnPathKey(record, segment)) return { found: false }; current = record[segment]; } return { found: true, value: current }; } function formatConfigUnsetMissingPathMessage(params) { if (params.runtimeOnly) return `Config path not found in authored config: ${params.path}. It only exists after runtime defaults are applied, so there is nothing for config unset to remove. Use ${formatCliCommand("openclaw config set <path> <value>")} to override the inherited value.`; return `Config path not found: ${params.path}. Nothing was changed. Run ${formatCliCommand("openclaw config get <path>")} first if you are unsure of the path.`; } function isSchemaRecord(value) { return isRecord(value); } function schemaTypes(schema) { if (typeof schema.type === "string") return /* @__PURE__ */ new Set([schema.type]); if (Array.isArray(schema.type)) return new Set(schema.type.filter((entry) => typeof entry === "string")); return /* @__PURE__ */ new Set(); } function schemaAlternatives(schema, seen = /* @__PURE__ */ new Set()) { if (seen.has(schema)) return []; seen.add(schema); const alternatives = [schema]; for (const key of [ "anyOf", "oneOf", "allOf" ]) { const entries = schema[key]; if (!Array.isArray(entries)) continue; for (const entry of entries) if (isSchemaRecord(entry)) alternatives.push(...schemaAlternatives(entry, seen)); } return alternatives; } function schemaLooksArray(schema) { return schemaTypes(schema).has("array") || isSchemaRecord(schema.items) || Array.isArray(schema.items); } function schemaLooksObject(schema) { return schemaTypes(schema).has("object") || isSchemaRecord(schema.properties) || schema.additionalProperties === true || isSchemaRecord(schema.additionalProperties); } function propertySchema(schema, segment) { const schemas = []; for (const alternative of schemaAlternatives(schema)) { if (schemaLooksArray(alternative)) { const index = parseIndexSegment(segment); if (index !== void 0) { const indexedItem = Array.isArray(alternative.items) ? alternative.items[index] : alternative.items; if (isSchemaRecord(indexedItem)) schemas.push(indexedItem); } continue; } const explicit = (isSchemaRecord(alternative.properties) ? alternative.properties : void 0)?.[segment]; if (isSchemaRecord(explicit)) schemas.push(explicit); else if (isSchemaRecord(alternative.additionalProperties)) schemas.push(alternative.additionalProperties); } return schemas; } function schemasAtPath(schema, path) { if (!schema) return []; let schemas = [schema]; for (const segment of path) { schemas = schemas.flatMap((candidate) => propertySchema(candidate, segment)); if (schemas.length === 0) return []; } return schemas; } function isConfigSchemaPath(schema, path) { return schemasAtPath(schema, path).length > 0; } function schemaPrefersArrayAtPath(schema, path) { const candidates = schemasAtPath(schema, path).flatMap((candidate) => schemaAlternatives(candidate)); if (candidates.length === 0) return; const hasArray = candidates.some((candidate) => schemaLooksArray(candidate)); const hasObject = candidates.some((candidate) => schemaLooksObject(candidate)); if (hasArray && !hasObject) return true; if (hasObject && !hasArray) return false; } function shouldCreateArrayForMissingPathSegment(params) { if (!params.next || params.options?.numericObjectKeys || !isIndexSegment(params.next)) return false; if (typeof params.options?.pathTokens?.[params.segmentIndex + 1] === "number") return true; if (params.options?.quotedNumericSegments?.has(params.segmentIndex + 1)) return false; const parentPath = params.path.slice(0, params.segmentIndex + 1); return schemaPrefersArrayAtPath(params.options?.schema, parentPath) ?? true; } function setAtPath(root, path, value, options) { const last = path.at(-1); if (last === void 0) throw new Error("Config path must contain at least one segment"); let current = root; for (const [i, segment] of path.slice(0, -1).entries()) { const nextIsIndex = shouldCreateArrayForMissingPathSegment({ path, segmentIndex: i, next: path[i + 1], options }); if (Array.isArray(current)) { const index = parseIndexSegment(segment); if (index === void 0) throw new Error(`Expected numeric index for array segment "${segment}"`); const existing = current[index]; if (!existing || typeof existing !== "object") current[index] = nextIsIndex ? [] : {}; current = current[index]; continue; } if (!current || typeof current !== "object") throw new Error(`Cannot traverse into "${segment}" (not an object)`); const record = current; const existing = hasOwnPathKey(record, segment) ? record[segment] : void 0; if (!existing || typeof existing !== "object") record[segment] = nextIsIndex ? [] : {}; current = record[segment]; } if (Array.isArray(current)) { const index = parseIndexSegment(last); if (index === void 0) throw new Error(`Expected numeric index for array segment "${last}"`); current[index] = value; return; } if (!current || typeof current !== "object") throw new Error(`Cannot set "${last}" (parent is not an object)`); current[last] = value; } function modelArrayIds(value) { if (!Array.isArray(value)) return null; const ids = /* @__PURE__ */ new Set(); for (const entry of value) { if (!isRecord(entry) || typeof entry.id !== "string" || !entry.id.trim()) return null; ids.add(entry.id.trim()); } return ids; } function mergeModelArrays(existing, patch) { const merged = [...existing]; const indexById = /* @__PURE__ */ new Map(); for (const [index, entry] of merged.entries()) if (isRecord(entry) && typeof entry.id === "string" && entry.id.trim()) indexById.set(entry.id.trim(), index); for (const entry of patch) { if (!isRecord(entry) || typeof entry.id !== "string" || !entry.id.trim()) { merged.push(entry); continue; } const id = entry.id.trim(); const existingIndex = indexById.get(id); if (existingIndex === void 0) { indexById.set(id, merged.length); merged.push(entry); continue; } const existingEntry = merged[existingIndex]; merged[existingIndex] = isRecord(existingEntry) ? { ...existingEntry, ...entry } : entry; } return merged; } function isProviderModelListPath(path) { return path.length === 4 && path[0] === "models" && path[1] === "providers" && path[3] === "models"; } function appendMergePath(parent, segment) { return { parent, segment }; } function toMergePath(path) { let current; for (const segment of path) current = appendMergePath(current, segment); return current; } function isProviderModelListMergePath(path) { const providers = path.parent?.parent; const models = providers?.parent; return path.segment === "models" && providers?.segment === "providers" && models?.segment === "models" && models.parent === void 0; } function mergeConfigValue(existing, patch, path) { if (isProviderModelListPath(path) && Array.isArray(existing) && Array.isArray(patch)) return mergeModelArrays(existing, patch); if (isRecord(existing) && isRecord(patch)) { const next = { ...existing }; const pending = [{ target: next, patch, path: toMergePath(path) }]; while (pending.length > 0) { const frame = pending.pop(); for (const [key, value] of Object.entries(frame.patch)) { const current = frame.target[key]; const childPath = appendMergePath(frame.path, key); if (hasOwnPathKey(frame.target, key) && isProviderModelListMergePath(childPath) && Array.isArray(current) && Array.isArray(value)) frame.target[key] = mergeModelArrays(current, value); else if (hasOwnPathKey(frame.target, key) && isRecord(current) && isRecord(value)) { const child = { ...current }; frame.target[key] = child; pending.push({ target: child, patch: value, path: childPath }); } else frame.target[key] = value; } } return next; } throw new Error(`Cannot merge ${toDotPath(path)}; use --replace to replace intentionally.`); } function mergeAtPath(root, path, value, options) { const existing = getAtPath(root, path); setAtPath(root, path, existing.found ? mergeConfigValue(existing.value, value, path) : value, options); } function isProtectedMapReplacementPath(path) { const joined = path.join("."); return joined === "agents.defaults.models" || joined === "models.providers" || path.length === 3 && path[0] === "models" && path[1] === "providers" || joined === "agents.entries" || joined === "plugins.entries" || joined === "auth.profiles"; } function isProtectedArrayReplacementPath(path) { return isProviderModelListPath(path); } function formatRemovedEntries(entries) { const visible = entries.slice(0, 6); const suffix = entries.length > visible.length ? `, ... ${entries.length - visible.length} more` : ""; return `${visible.join(", ")}${suffix}`; } function assertNonDestructiveReplacement(params) { if (params.allowReplace) return; const existing = getAtPath(params.root, params.path); if (!existing.found) return; const pathLabel = toDotPath(params.path); if (isProtectedMapReplacementPath(params.path) && isRecord(existing.value)) { if (!isRecord(params.value)) return; const nextKeys = new Set(Object.keys(params.value)); const removed = Object.keys(existing.value).filter((key) => !nextKeys.has(key)); if (removed.length > 0) throw new Error(`Refusing to replace ${pathLabel}; it would remove existing entries: ${formatRemovedEntries(removed)}. Use --merge to merge object values or --replace to replace intentionally.`); } if (isProtectedArrayReplacementPath(params.path)) { const existingIds = modelArrayIds(existing.value); const nextIds = modelArrayIds(params.value); if (!existingIds || !nextIds) return; const removed = [...existingIds].filter((id) => !nextIds.has(id)); if (removed.length > 0) throw new Error(`Refusing to replace ${pathLabel}; it would remove existing entries: ${formatRemovedEntries(removed)}. Use --merge to merge by id or --replace to replace intentionally.`); } } function unsetAtPath(root, path) { const last = path.at(-1); if (last === void 0) return { removed: false }; let current = root; for (const segment of path.slice(0, -1)) { if (!current || typeof current !== "object") return { removed: false }; if (Array.isArray(current)) { const index = parseIndexSegment(segment); if (index === void 0 || index >= current.length) return { removed: false }; current = current[index]; continue; } const record = current; if (!hasOwnPathKey(record, segment)) return { removed: false }; current = record[segment]; } if (Array.isArray(current)) { const index = parseIndexSegment(last); if (index === void 0 || index >= current.length) return { removed: false }; current.splice(index, 1); return { removed: true, leafContainer: "array" }; } if (!current || typeof current !== "object") return { removed: false }; const record = current; if (!hasOwnPathKey(record, last)) return { removed: false }; delete record[last]; return { removed: true, leafContainer: "object" }; } //#endregion export { isConfigSchemaPath as a, setAtPath as c, getAtPath as i, unsetAtPath as l, formatConfigSetPath as n, mergeAtPath as o, formatConfigUnsetMissingPathMessage as r, parseConfigSetValue as s, assertNonDestructiveReplacement as t, validatePathSegments as u };