UNPKG

akm-cli

Version:

akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur

96 lines (95 loc) • 4.23 kB
// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. import { snapshotStrictRecord } from "./record.js"; function fail(path, detail) { throw new TypeError(`${path} ${detail}`); } /** * Validate, clone, and freeze a value for durable JSON transport. * * `undefined`, non-finite numbers, non-plain objects, and cycles are rejected * instead of being silently rewritten by `JSON.stringify`. That makes field * presence meaningful at the execution boundary: omit a key, or provide an * explicit JSON value (including `false`, `0`, `[]`, `{}`, `""`, or `null`). */ export function cloneExecutionJson(value, path = "execution value", ancestors = new Set()) { if (value === null || typeof value === "string" || typeof value === "boolean") return value; if (typeof value === "number") { if (!Number.isFinite(value)) fail(path, "must contain only finite numbers"); return value; } if (value === undefined) fail(path, "must be omitted rather than set to undefined"); if (typeof value !== "object") fail(path, "must be JSON-safe"); if (ancestors.has(value)) fail(path, "must not contain a cycle"); const nextAncestors = new Set(ancestors); nextAncestors.add(value); if (Array.isArray(value)) { if (Object.getPrototypeOf(value) !== Array.prototype) fail(path, "array must use the standard Array prototype"); const ownKeys = Reflect.ownKeys(value); const lengthDescriptor = Reflect.getOwnPropertyDescriptor(value, "length"); if (!lengthDescriptor || !("value" in lengthDescriptor) || typeof lengthDescriptor.value !== "number" || !Number.isInteger(lengthDescriptor.value) || lengthDescriptor.value < 0) { fail(path, "array length must be a stable nonnegative integer data property"); } const length = lengthDescriptor.value; if (ownKeys.length !== length + 1) { fail(path, "array must be dense and contain no non-index properties"); } for (const key of ownKeys) { if (key === "length") continue; if (typeof key !== "string" || !/^(?:0|[1-9]\d*)$/.test(key) || Number(key) >= length) { fail(path, "array must contain only canonical index properties"); } } const cloned = []; for (let index = 0; index < length; index++) { const descriptor = Reflect.getOwnPropertyDescriptor(value, String(index)); if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) { fail(path, "array must be dense enumerable data properties"); } cloned.push(cloneExecutionJson(descriptor.value, `${path}[${index}]`, nextAncestors)); } return Object.freeze(cloned); } const snapshot = snapshotStrictRecord(value, path); const cloned = Object.create(null); for (const [key, child] of Object.entries(snapshot)) { Object.defineProperty(cloned, key, { value: cloneExecutionJson(child, `${path}.${key}`, nextAncestors), enumerable: true, configurable: false, writable: false, }); } return Object.freeze(cloned); } export function cloneExecutionJsonObject(value, path) { const cloned = cloneExecutionJson(value, path); if (cloned === null || Array.isArray(cloned) || typeof cloned !== "object") { fail(path, "must be a JSON object"); } return cloned; } /** Stable object-key ordering for request fingerprints; array order is semantic. */ export function sortExecutionJson(value) { if (Array.isArray(value)) return value.map(sortExecutionJson); if (value === null || typeof value !== "object") return value; const sorted = Object.create(null); for (const [key, child] of Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) { sorted[key] = sortExecutionJson(child); } return sorted; }