UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

143 lines (142 loc) • 6.47 kB
import { r as createRuntimeConfigReader } from "../../runtime-snapshot-De_QxZI9.mjs"; import "../../runtime-config-snapshot--IkldAgP.mjs"; import { a as resolveXAccountKey, i as resolveXAccount, n as listXRuntimeAccountIds, t as listXAccountIds } from "../../accounts-DgZqMZQn.mjs"; import { i as openXAllowlist, n as mergeXAllowlist, r as normalizeXUserId } from "../../allowlist-ZXKTf1Qk.mjs"; import { a as XBudgetExceededError, o as openXSpend, t as getXApi } from "../../client-DleoFeSj.mjs"; import { t as getXGuestStatus } from "../../guests-C4Ujua48.mjs"; import { n as getXGitHubStatus } from "../../verified-github-BmprWpkw.mjs"; //#region extensions/x/src/admin.ts var XAdminError = class extends Error { constructor(code, message) { super(message); this.code = code; } }; function assertAdmin(request) { request.signal?.throwIfAborted(); request.client?.connectionSignal?.throwIfAborted(); if (!request.client?.connect.scopes?.includes("operator.admin") || request.hasCurrentClientAuthority?.() === false) throw new XAdminError("FORBIDDEN", "X replies management requires an active administrator."); request.sessionMutationAuthorization?.assertCurrent(); request.sessionAccessAuthority?.assertCurrent(); request.sessionMutationCommitGuard?.(); } function hasXAccount(cfg, accountId) { return listXAccountIds(cfg).includes(accountId) || resolveXAccountKey(cfg, accountId) !== void 0; } function callerIdentity(request) { const client = request.client; if (!client) throw new XAdminError("FORBIDDEN", "An authenticated administrator is required."); const identity = client.authenticatedUserId || client.connect.device?.id || client.connId; if (!identity) throw new XAdminError("FORBIDDEN", "The administrator identity is unavailable."); return identity; } function registerXAllowlistMethods(api) { let allowlist; const getAllowlist = () => allowlist ??= openXAllowlist(api.runtime); const accountFor = (request, cfg = request.context.getRuntimeConfig()) => { const raw = request.params.accountId; if (raw !== void 0 && (typeof raw !== "string" || !raw.trim())) throw new XAdminError("INVALID_REQUEST", "accountId must be a nonempty string."); const account = resolveXAccount(cfg, raw); if (!hasXAccount(cfg, account.accountId)) throw new XAdminError("INVALID_REQUEST", "The X account is not configured."); return { cfg, account }; }; const snapshot = async (request, cfg = request.context.getRuntimeConfig(), selectedAccountId) => { const account = selectedAccountId ? resolveXAccount(cfg, selectedAccountId) : accountFor(request, cfg).account; const readConfig = createRuntimeConfigReader(cfg); const [entries, guests, spend, verifiedFromGitHub] = await Promise.all([ getAllowlist().list(account.accountId), getXGuestStatus(api.runtime, account, cfg), openXSpend(api.runtime, account.accountId, () => resolveXAccount(readConfig(), account.accountId).costLimits).status(), getXGitHubStatus(api.runtime, account) ]); assertAdmin(request); return { accountId: account.accountId, accounts: listXRuntimeAccountIds(cfg).map((accountId) => ({ accountId, username: resolveXAccount(cfg, accountId).username })), entries: mergeXAllowlist(account.config.allowFrom ?? [], entries), guests, spend, verifiedFromGitHub }; }; const handlers = { "x.allowlist.list": snapshot, async "x.guests.set"(request) { const enabled = request.params.enabled; if (typeof enabled !== "boolean") throw new XAdminError("INVALID_REQUEST", "enabled must be a boolean."); const { account } = accountFor(request); const { mutateConfigFile } = await import("../../plugin-sdk/config-mutation.js"); const committed = await mutateConfigFile({ afterWrite: { mode: "auto" }, writeOptions: { assertCurrent: () => assertAdmin(request) }, mutate: (draft) => { assertAdmin(request); const channel = draft.channels?.x; if (!channel || !hasXAccount(draft, account.accountId)) throw new XAdminError("INVALID_REQUEST", "The X account is no longer configured."); const key = resolveXAccountKey(draft, account.accountId); const target = (key === void 0 ? void 0 : channel.accounts?.[key]) ?? channel; target.guests = { ...target.guests, enabled }; } }); return await snapshot(request, committed.nextConfig, account.accountId); }, async "x.allowlist.add"(request) { const raw = request.params.username; const username = typeof raw === "string" ? raw.trim().replace(/^@/, "") : ""; if (!/^[A-Za-z0-9_]{1,15}$/.test(username)) throw new XAdminError("INVALID_REQUEST", "username must be an X handle, with or without @."); const { cfg, account } = accountFor(request); const client = await getXApi(account.accountId, cfg); assertAdmin(request); const user = await client.getUserByUsername(username, request.signal); assertAdmin(request); await getAllowlist().put(account.accountId, { userId: user.id, username: user.username, name: user.name ?? user.username, addedBy: callerIdentity(request), addedAt: Date.now() }, () => assertAdmin(request)); return await snapshot(request); }, async "x.allowlist.remove"(request) { const userId = typeof request.params.userId === "string" ? normalizeXUserId(request.params.userId) : void 0; if (!userId) throw new XAdminError("INVALID_REQUEST", "userId must be a numeric X user ID."); const { account } = accountFor(request); await getAllowlist().remove(account.accountId, userId, () => assertAdmin(request)); return await snapshot(request); } }; for (const [method, handler] of Object.entries(handlers)) api.registerGatewayMethod(method, async (request) => { try { assertAdmin(request); request.respond(true, await handler(request)); } catch (error) { if (error instanceof XAdminError) request.respond(false, void 0, { code: error.code, message: error.message }); else if (error instanceof XBudgetExceededError) request.respond(false, void 0, { code: "UNAVAILABLE", message: error.message }); else { api.logger.error(`X replies operation failed (${method}): ${error instanceof Error ? error.message : String(error)}`); request.respond(false, void 0, { code: "UNAVAILABLE", message: "X replies operation failed. Check the Gateway logs and try again." }); } } }, { scope: "operator.admin" }); } //#endregion export { registerXAllowlistMethods };