openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
370 lines (369 loc) • 17.1 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-1VuiLaaR.mjs";
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import { t as formatErrorMessage } from "./errors-O33D01XW.mjs";
import { i as readResponseWithLimit, t as cancelUnreadResponseBody } from "./http-response-body-umfRGZ-E.mjs";
import "./http-body-CgfQBa7S.mjs";
import { t as MANIFEST_KEY } from "./legacy-names-Cq8ruaO5.mjs";
import { l as listOfficialExternalPluginCatalogEntries } from "./official-external-plugin-catalog-Dcv926GC.mjs";
import { _ as shouldRequireManifestInstallSourceRef, c as isOfficialExternalPluginCatalogFeed, d as parseOfficialExternalPluginCatalogEntries, f as parseOfficialExternalPluginCatalogTimestamp, i as filterOfficialExternalPluginCatalogEntriesBySourceRefs, l as isOfficialExternalPluginCatalogRollback, n as HostedCatalogSignedFeedMonotonicityError, p as resolveHostedCatalogFeedSource, r as dedupeOfficialExternalPluginCatalogEntries } from "./official-external-plugin-catalog-source-B7ojvzQW.mjs";
import { createHash } from "node:crypto";
//#region src/plugins/official-external-plugin-catalog-hosted.ts
const DEFAULT_HOSTED_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_TIMEOUT_MS = 5e3;
const DEFAULT_HOSTED_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_MAX_BYTES = 1048576;
const DEFAULT_HOSTED_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_CHUNK_TIMEOUT_MS = 5e3;
const DSSE_ENVELOPE_MEDIA_TYPE = "application/vnd.dsse+json";
var HostedCatalogFeedTimestampError = class extends Error {
constructor(message, sequence) {
super(message);
this.sequence = sequence;
}
};
var HostedCatalogSnapshotWriteError = class extends Error {
constructor(originalError) {
super("hosted catalog snapshot write failed");
this.name = "HostedCatalogSnapshotWriteError";
this.originalError = originalError;
}
};
function sha256Hex(value) {
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
}
function parseHostedCatalogContentLength(raw, maxBytes) {
const normalized = normalizeOptionalString(raw);
if (!normalized) return;
if (!/^\d+$/.test(normalized)) throw new Error("hosted catalog feed has invalid content-length");
const size = Number(normalized);
if (!Number.isSafeInteger(size) || size > maxBytes) throw new Error(`hosted catalog feed exceeds ${maxBytes} bytes`);
}
async function readHostedCatalogResponseText(params) {
parseHostedCatalogContentLength(params.response.headers.get("content-length"), params.maxBytes);
if (!params.response.body || typeof params.response.body.getReader !== "function") throw new Error("hosted catalog feed streaming response body unavailable");
const buffer = await readResponseWithLimit(params.response, params.maxBytes, {
chunkTimeoutMs: params.chunkTimeoutMs,
onOverflow: ({ maxBytes }) => /* @__PURE__ */ new Error(`hosted catalog feed exceeds ${maxBytes} bytes`),
onIdleTimeout: ({ chunkTimeoutMs }) => /* @__PURE__ */ new Error(`hosted catalog feed read timed out after ${chunkTimeoutMs}ms`)
});
return new TextDecoder("utf-8", { fatal: true }).decode(buffer);
}
function bundledFallbackResult(error, metadata) {
return {
source: "bundled-fallback",
entries: listOfficialExternalPluginCatalogEntries(),
error: formatErrorMessage(error),
...metadata ? { metadata } : {}
};
}
async function parseHostedCatalogFeedBody(params) {
const raw = JSON.parse(params.body);
if (params.verification?.mode === "signed") {
const { verifyOfficialExternalPluginCatalogSignedEnvelope } = await import("./official-external-plugin-catalog-envelope-0bKCmDWv.mjs");
const threshold = params.verification.threshold ?? 1;
const verification = verifyOfficialExternalPluginCatalogSignedEnvelope(raw, {
trustedKeys: params.verification.keys,
threshold,
...params.allowLegacyBetaEnvelope ? { allowLegacyBetaEnvelope: true } : {}
});
if (!verification.ok) {
const invalidTimestampSequence = verification.error === "invalid-payload" && "authenticatedPayload" in verification ? readOfficialExternalPluginCatalogInvalidTimestampSequence(verification.authenticatedPayload) : void 0;
if (invalidTimestampSequence !== void 0) throw new HostedCatalogFeedTimestampError(verification.message, invalidTimestampSequence);
throw new Error(verification.message);
}
if (params.expectedFeedId && verification.feed.id !== params.expectedFeedId) throw new Error(`hosted catalog feed id "${verification.feed.id}" did not match expected "${params.expectedFeedId}"`);
const generatedAtMs = parseOfficialExternalPluginCatalogTimestamp(verification.feed.generatedAt);
const expiresAt = normalizeOptionalString(verification.feed.expiresAt);
if (generatedAtMs === void 0) throw new Error("hosted catalog signed feed requires a valid generatedAt value");
let expired;
if (!expiresAt) {
if (params.allowMissingExpiry !== true) throw new Error("hosted catalog signed feed requires a valid expiresAt value");
expired = true;
} else {
const expiresAtMs = parseOfficialExternalPluginCatalogTimestamp(expiresAt);
if (expiresAtMs === void 0) throw new Error("hosted catalog signed feed requires a valid expiresAt value");
if (expiresAtMs <= generatedAtMs) throw new Error("hosted catalog signed feed expiresAt must be later than generatedAt");
expired = expiresAtMs <= params.now.getTime();
}
if (expired && params.allowExpired !== true) throw new Error(expiresAt ? `hosted catalog signed feed expired at ${expiresAt}` : "hosted catalog signed feed has no expiresAt");
return {
feed: enforceHostedCatalogFeedInstallAuthority(verification.feed),
trust: {
mode: "signed",
signedBy: verification.signedBy,
signatureCount: verification.signatureCount ?? 1,
threshold,
verifiedAt: params.verifiedAt
},
...expired ? { expired: true } : {}
};
}
if (!isOfficialExternalPluginCatalogFeed(raw)) throw new Error("hosted catalog feed did not match a supported schema version");
if (params.expectedFeedId && raw.id !== params.expectedFeedId) throw new Error(`hosted catalog feed id "${raw.id}" did not match expected "${params.expectedFeedId}"`);
return { feed: enforceHostedCatalogFeedInstallAuthority(raw) };
}
function enforceHostedCatalogFeedInstallAuthority(feed) {
if (feed.schemaVersion < 2) return feed;
return {
...feed,
entries: feed.entries.map((entry) => {
const state = normalizeOptionalString(entry.state);
const publisherTrust = normalizeOptionalString(entry.publisher?.trust);
return state === "available" && publisherTrust === "official" ? entry : removeOfficialExternalPluginCatalogInstallAuthority(entry);
})
};
}
function readOfficialExternalPluginCatalogInvalidTimestampSequence(raw) {
if (!isRecord(raw)) return;
if (typeof raw.generatedAt === "string" && parseOfficialExternalPluginCatalogTimestamp(raw.generatedAt) !== void 0) return;
const normalized = {
...raw,
generatedAt: "1970-01-01T00:00:00.000Z"
};
return isOfficialExternalPluginCatalogFeed(normalized) ? normalized.sequence : void 0;
}
async function loadHostedCatalogSnapshotResult(params) {
assertSnapshotMatchesRequestValidators({
snapshot: params.snapshot,
ifNoneMatch: params.ifNoneMatch,
ifModifiedSince: params.ifModifiedSince
});
const checksum = sha256Hex(params.snapshot.body);
if (checksum !== params.snapshot.metadata.checksum) throw new Error("hosted catalog snapshot checksum mismatch");
if (params.expectedSha256 && params.expectedSha256 !== checksum) throw new Error("hosted catalog snapshot checksum did not match expected checksum");
const parsed = await parseHostedCatalogFeedBody({
body: params.snapshot.body,
expectedFeedId: params.expectedFeedId,
verification: params.verification,
verifiedAt: params.snapshot.trust?.verifiedAt ?? params.snapshot.savedAt,
allowLegacyBetaEnvelope: true,
now: params.now,
allowExpired: true,
allowMissingExpiry: true
});
const entries = dedupeOfficialExternalPluginCatalogEntries(filterOfficialExternalPluginCatalogEntriesBySourceRefs(parseOfficialExternalPluginCatalogEntries(parsed.feed), {
catalogConfig: params.catalogConfig,
requireManifestInstallSourceRef: params.requireManifestInstallSourceRef
}));
const visibleEntries = parsed.expired ? entries.map((entry) => removeOfficialExternalPluginCatalogInstallAuthority(entry)) : entries;
return {
source: "hosted-snapshot",
entries: visibleEntries,
feed: parsed.expired ? {
...parsed.feed,
entries: visibleEntries
} : parsed.feed,
metadata: params.snapshot.metadata,
snapshot: params.snapshot,
...parsed.trust ? { trust: parsed.trust } : {},
error: parsed.expired ? `${formatErrorMessage(params.error)}; ${parsed.feed.expiresAt ? `hosted catalog signed feed expired at ${parsed.feed.expiresAt}` : "hosted catalog signed feed has no expiresAt"}` : formatErrorMessage(params.error)
};
}
function removeOfficialExternalPluginCatalogInstallAuthority(entry) {
const { install: _feedInstall, [MANIFEST_KEY]: manifest, ...metadata } = entry;
if (!manifest) return {
...metadata,
state: "unavailable"
};
const { install: _manifestInstall, ...manifestMetadata } = manifest;
return {
...metadata,
state: "unavailable",
[MANIFEST_KEY]: manifestMetadata
};
}
function assertSnapshotMatchesRequestValidators(params) {
if (params.ifNoneMatch && params.snapshot.metadata.etag !== params.ifNoneMatch) throw new Error("hosted catalog snapshot ETag did not match request validator");
if (!params.ifNoneMatch && params.ifModifiedSince && params.snapshot.metadata.lastModified !== params.ifModifiedSince) throw new Error("hosted catalog snapshot Last-Modified did not match request validator");
}
async function snapshotOrBundledFallbackResult(params) {
let error = params.error;
if (params.snapshotStore) try {
const snapshot = await params.snapshotStore.read(params.url);
if (snapshot) return await loadHostedCatalogSnapshotResult({
...params,
snapshot
});
} catch (snapshotErr) {
error = `${formatErrorMessage(params.error)}; snapshot fallback failed: ${formatErrorMessage(snapshotErr)}`;
}
if (params.verification?.mode === "signed") return {
source: "bundled-fallback",
entries: [],
error: formatErrorMessage(error)
};
return bundledFallbackResult(error, params.metadata);
}
async function resolveHostedCatalogSnapshotStore(params) {
if (params.snapshotStore !== void 0) return params.snapshotStore ?? void 0;
const { createSqliteHostedOfficialExternalPluginCatalogSnapshotStore } = await import("./official-external-plugin-catalog-snapshot-store-Bz4xyzts.mjs");
return createSqliteHostedOfficialExternalPluginCatalogSnapshotStore({
...params.env ? { env: params.env } : {},
...params.stateDir ? { stateDir: params.stateDir } : {},
...params.stateDatabasePath ? { stateDatabasePath: params.stateDatabasePath } : {}
});
}
async function loadHostedOfficialExternalPluginCatalogEntries(params) {
let source;
try {
source = resolveHostedCatalogFeedSource({
feedUrl: params?.feedUrl,
feedProfile: params?.feedProfile,
catalogConfig: params?.catalogConfig
});
} catch (err) {
return bundledFallbackResult(err);
}
const { url } = source;
const snapshotStore = await resolveHostedCatalogSnapshotStore({
snapshotStore: params?.snapshotStore,
env: params?.env,
stateDir: params?.stateDir,
stateDatabasePath: params?.stateDatabasePath
});
const expectedSha256 = normalizeOptionalString(params?.expectedSha256);
const currentTime = () => params?.now?.() ?? /* @__PURE__ */ new Date();
const requireManifestInstallSourceRef = shouldRequireManifestInstallSourceRef({
feedUrl: params?.feedUrl,
feedProfile: params?.feedProfile,
catalogConfig: params?.catalogConfig
});
if (params?.offline === true) return await snapshotOrBundledFallbackResult({
error: "hosted catalog feed offline mode",
snapshotStore,
url: url.href,
expectedSha256,
catalogConfig: params?.catalogConfig,
requireManifestInstallSourceRef,
expectedFeedId: source.expectedFeedId,
verification: source.verification,
now: currentTime()
});
const headers = new Headers();
const ifNoneMatch = normalizeOptionalString(params?.ifNoneMatch);
const signedOperation = source.verification?.mode === "signed";
const ifModifiedSince = signedOperation ? void 0 : normalizeOptionalString(params?.ifModifiedSince);
if (ifNoneMatch) headers.set("if-none-match", ifNoneMatch);
if (ifModifiedSince) headers.set("if-modified-since", ifModifiedSince);
if (signedOperation) headers.set("accept", DSSE_ENVELOPE_MEDIA_TYPE);
const loadFallback = (error, metadata, now = currentTime()) => snapshotOrBundledFallbackResult({
error,
snapshotStore,
url: url.href,
metadata,
expectedSha256,
ifNoneMatch,
ifModifiedSince,
catalogConfig: params?.catalogConfig,
requireManifestInstallSourceRef,
expectedFeedId: source.expectedFeedId,
verification: source.verification,
now
});
const metadataBase = (response) => {
const etag = normalizeOptionalString(response.headers.get("etag"));
const lastModified = normalizeOptionalString(response.headers.get("last-modified"));
return {
url: url.href,
status: response.status,
...etag ? { etag } : {},
...lastModified ? { lastModified } : {}
};
};
let response;
let release;
try {
const { fetchWithSsrFGuard } = await import("./fetch-guard-Chm3knY-.mjs");
const guarded = await fetchWithSsrFGuard({
url: url.href,
fetchImpl: params?.fetchImpl,
init: {
method: "GET",
headers
},
requireHttps: true,
maxRedirects: 2,
timeoutMs: params?.timeoutMs ?? DEFAULT_HOSTED_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_TIMEOUT_MS,
policy: { hostnameAllowlist: source.hostnameAllowlist },
auditContext: "official-external-plugin-catalog-feed"
});
response = guarded.response;
release = guarded.release;
const base = metadataBase(response);
if (response.status === 304 || !response.ok) return await loadFallback(`hosted catalog feed returned HTTP ${response.status}`, base);
if (signedOperation && response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== DSSE_ENVELOPE_MEDIA_TYPE) return await loadFallback(`signed hosted catalog feed must use ${DSSE_ENVELOPE_MEDIA_TYPE}`, base);
const body = await readHostedCatalogResponseText({
response,
maxBytes: params?.maxBytes ?? DEFAULT_HOSTED_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_MAX_BYTES,
chunkTimeoutMs: params?.chunkTimeoutMs ?? DEFAULT_HOSTED_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_CHUNK_TIMEOUT_MS
});
const checksum = sha256Hex(body);
const metadata = {
...base,
checksum
};
if (expectedSha256 && expectedSha256 !== checksum) return await loadFallback(`hosted catalog feed checksum mismatch: expected ${expectedSha256}`, metadata);
const now = currentTime();
const verifiedAt = now.toISOString();
const parsed = await parseHostedCatalogFeedBody({
body,
expectedFeedId: source.expectedFeedId,
verification: source.verification,
verifiedAt,
now
}).catch((err) => loadFallback(err, metadata, now));
if ("source" in parsed) return parsed;
if (snapshotStore && parsed.trust?.mode === "signed") {
const currentSnapshot = await snapshotStore.read(url.href);
if (currentSnapshot?.trust?.mode === "signed") {
const current = currentSnapshot.monotonic?.mode === "signed-feed" ? currentSnapshot.monotonic : (await parseHostedCatalogFeedBody({
body: currentSnapshot.body,
expectedFeedId: source.expectedFeedId,
verification: source.verification,
verifiedAt: currentSnapshot.trust.verifiedAt,
allowLegacyBetaEnvelope: true,
now,
allowExpired: true,
allowMissingExpiry: true
}).catch((err) => {
if (err instanceof HostedCatalogFeedTimestampError) return { feed: { sequence: err.sequence } };
throw err;
})).feed;
if (isOfficialExternalPluginCatalogRollback({
candidate: parsed.feed,
current
})) throw new HostedCatalogSignedFeedMonotonicityError("hosted catalog signed feed sequence is older than current snapshot");
}
}
const entries = filterOfficialExternalPluginCatalogEntriesBySourceRefs(parseOfficialExternalPluginCatalogEntries(parsed.feed), {
catalogConfig: params?.catalogConfig,
requireManifestInstallSourceRef
});
await snapshotStore?.write({
body,
metadata,
savedAt: verifiedAt,
...parsed.trust ? { trust: parsed.trust } : {},
...parsed.trust?.mode === "signed" ? { monotonic: {
mode: "signed-feed",
sequence: parsed.feed.sequence,
generatedAt: parsed.feed.generatedAt
} } : {}
}).catch((err) => {
if (err instanceof HostedCatalogSignedFeedMonotonicityError) throw err;
if (params?.requireSnapshotWrite) throw new HostedCatalogSnapshotWriteError(err);
});
return {
source: "hosted",
entries: dedupeOfficialExternalPluginCatalogEntries(entries),
feed: parsed.feed,
metadata,
...parsed.trust ? { trust: parsed.trust } : {}
};
} catch (err) {
if (err instanceof HostedCatalogSnapshotWriteError) throw err.originalError;
return await loadFallback(err);
} finally {
await cancelUnreadResponseBody(response);
await release?.().catch(() => void 0);
}
}
//#endregion
export { loadHostedOfficialExternalPluginCatalogEntries };