aureon-os / plugins /moltbook /src /core.mjs
GaryLeckey's picture
Deploy Aureon OS (deploy/selfhost stack: nginx edge + fusion API x2 + console + HNC organ + redis + postgres; paper mode) (part 19)
da0ce7b verified
Raw History Blame Contribute Delete
30.1 kB
import { createHash } from "node:crypto";
const SOCIAL_API_BASE = "https://www.moltbook.com/api/v1";
const REDACTED = "[REDACTED]";
const readAnnotations = Object.freeze({
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
});
const writeAnnotations = Object.freeze({
readOnlyHint: false,
destructiveHint: true,
idempotentHint: true,
openWorldHint: true,
});
export const TOOL_DEFINITIONS = Object.freeze([
{
name: "registry_status",
description: "Check a registry identity by agent ID or Base wallet address.",
inputSchema: {
type: "object",
properties: {
agent_id: { oneOf: [{ type: "string" }, { type: "integer", minimum: 0 }] },
address: { type: "string" },
},
oneOf: [{ required: ["agent_id"] }, { required: ["address"] }],
additionalProperties: false,
},
annotations: readAnnotations,
},
{
name: "registry_lookup",
description: "Fetch registry metadata for an agent ID.",
inputSchema: {
type: "object",
properties: {
agent_id: { oneOf: [{ type: "string" }, { type: "integer", minimum: 0 }] },
},
required: ["agent_id"],
additionalProperties: false,
},
annotations: readAnnotations,
},
{
name: "registry_register",
description: "Register an agent through an injected signer after confirmation and idempotency checks.",
inputSchema: {
type: "object",
properties: {
endpoints: { type: "string", minLength: 2, maxLength: 16384 },
uri: { type: "string", maxLength: 2048 },
agent_wallet: { type: "string" },
confirmed: { type: "boolean" },
idempotency_key: { type: "string", minLength: 16, maxLength: 128 },
},
required: ["endpoints", "confirmed", "idempotency_key"],
additionalProperties: false,
},
annotations: writeAnnotations,
},
{
name: "registry_rate",
description: "Rate an agent through an injected signer after confirmation and idempotency checks.",
inputSchema: {
type: "object",
properties: {
agent_id: { oneOf: [{ type: "string" }, { type: "integer", minimum: 0 }] },
score: { type: "integer", minimum: 0, maximum: 100 },
confirmed: { type: "boolean" },
idempotency_key: { type: "string", minLength: 16, maxLength: 128 },
},
required: ["agent_id", "score", "confirmed", "idempotency_key"],
additionalProperties: false,
},
annotations: writeAnnotations,
},
{
name: "moltbook_hot",
description: "List hot Moltbook posts through an injected HTTP transport.",
inputSchema: {
type: "object",
properties: { limit: { type: "integer", minimum: 1, maximum: 100 } },
additionalProperties: false,
},
annotations: readAnnotations,
},
{
name: "moltbook_new",
description: "List new Moltbook posts through an injected HTTP transport.",
inputSchema: {
type: "object",
properties: { limit: { type: "integer", minimum: 1, maximum: 100 } },
additionalProperties: false,
},
annotations: readAnnotations,
},
{
name: "moltbook_get_post",
description: "Retrieve one Moltbook post through an injected HTTP transport.",
inputSchema: {
type: "object",
properties: { post_id: { type: "string", minLength: 1, maxLength: 128 } },
required: ["post_id"],
additionalProperties: false,
},
annotations: readAnnotations,
},
{
name: "moltbook_reply",
description: "Reply to a Moltbook post after confirmation and idempotency checks.",
inputSchema: {
type: "object",
properties: {
post_id: { type: "string", minLength: 1, maxLength: 128 },
content: { type: "string", minLength: 1, maxLength: 5000 },
confirmed: { type: "boolean" },
idempotency_key: { type: "string", minLength: 16, maxLength: 128 },
},
required: ["post_id", "content", "confirmed", "idempotency_key"],
additionalProperties: false,
},
annotations: writeAnnotations,
},
{
name: "moltbook_create",
description: "Create a Moltbook post after confirmation and idempotency checks.",
inputSchema: {
type: "object",
properties: {
title: { type: "string", minLength: 1, maxLength: 300 },
content: { type: "string", minLength: 1, maxLength: 10000 },
confirmed: { type: "boolean" },
idempotency_key: { type: "string", minLength: 16, maxLength: 128 },
},
required: ["title", "content", "confirmed", "idempotency_key"],
additionalProperties: false,
},
annotations: writeAnnotations,
},
]);
class ToolError extends Error {
constructor(code, message, details = undefined, redactionValues = []) {
super(message);
this.name = "ToolError";
this.code = code;
this.details = details;
Object.defineProperty(this, "redactionValues", {
value: redactionValues,
enumerable: false,
});
}
}
function isPlainObject(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function requireObject(value) {
if (!isPlainObject(value)) {
throw new ToolError("invalid_arguments", "Arguments must be a JSON object.");
}
return value;
}
function rejectUnknownKeys(value, allowed) {
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
if (unknown.length > 0) {
throw new ToolError("invalid_arguments", "Unexpected argument fields.", { fields: unknown });
}
}
function validateAgentId(value) {
if (Number.isSafeInteger(value) && value >= 0) return String(value);
if (typeof value === "string" && /^(0|[1-9][0-9]*)$/.test(value)) return value;
throw new ToolError("invalid_agent_id", "agent_id must be a non-negative decimal integer.");
}
function validateAddress(value, field = "address") {
if (typeof value === "string" && /^0x[0-9a-fA-F]{40}$/.test(value)) return value;
throw new ToolError("invalid_address", `${field} must be a 20-byte hexadecimal address.`);
}
function validateScore(value) {
if (Number.isInteger(value) && value >= 0 && value <= 100) return value;
throw new ToolError("invalid_score", "score must be an integer from 0 through 100.");
}
function validateEndpoints(value) {
if (typeof value !== "string" || value.length < 2 || value.length > 16384) {
throw new ToolError("invalid_endpoints", "endpoints must be a bounded JSON object string.");
}
let parsed;
try {
parsed = JSON.parse(value);
} catch {
throw new ToolError("invalid_endpoints", "endpoints must contain valid JSON.");
}
if (!isPlainObject(parsed)) {
throw new ToolError("invalid_endpoints", "endpoints JSON must be an object.");
}
return parsed;
}
function validateHttpsUri(value) {
if (value === undefined) return undefined;
if (typeof value !== "string" || value.length > 2048) {
throw new ToolError("invalid_uri", "uri must be a bounded HTTPS URL.");
}
let parsed;
try {
parsed = new URL(value);
} catch {
throw new ToolError("invalid_uri", "uri must be a valid HTTPS URL.");
}
if (parsed.protocol !== "https:" || parsed.username || parsed.password) {
throw new ToolError("invalid_uri", "uri must be an HTTPS URL without embedded credentials.");
}
return parsed.toString();
}
function validateSocialId(value, field) {
if (typeof value === "string" && /^[A-Za-z0-9_-]{1,128}$/.test(value)) return value;
throw new ToolError("invalid_identifier", `${field} contains unsupported characters.`);
}
function validateLimit(value) {
if (value === undefined) return 20;
if (Number.isInteger(value) && value >= 1 && value <= 100) return value;
throw new ToolError("invalid_limit", "limit must be an integer from 1 through 100.");
}
function validateText(value, field, maximum) {
if (typeof value !== "string" || value.trim().length === 0 || value.length > maximum) {
throw new ToolError("invalid_text", `${field} must contain 1 through ${maximum} characters.`);
}
return value;
}
function validateMutationGate(args) {
if (args.confirmed !== true) {
throw new ToolError("confirmation_required", "This mutation requires confirmed: true.");
}
if (
typeof args.idempotency_key !== "string" ||
!/^[A-Za-z0-9][A-Za-z0-9._:-]{15,127}$/.test(args.idempotency_key)
) {
throw new ToolError(
"idempotency_required",
"A unique 16-128 character idempotency_key is required.",
);
}
return args.idempotency_key;
}
function stableStringify(value) {
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
if (isPlainObject(value)) {
return `{${Object.keys(value)
.sort()
.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`)
.join(",")}}`;
}
return JSON.stringify(value);
}
function fingerprint(value) {
return createHash("sha256").update(stableStringify(value)).digest("hex");
}
function redactString(value, secretValues) {
let output = value
.replace(/Bearer\s+[^\s,;]+/gi, `Bearer ${REDACTED}`)
.replace(/0x[0-9a-fA-F]{64}\b/g, REDACTED);
for (const secret of secretValues) {
if (typeof secret !== "string" || secret.length === 0) continue;
output = output.split(secret).join(REDACTED);
}
return output;
}
function redact(value, secretValues = [], seen = new WeakSet()) {
if (typeof value === "string") return redactString(value, secretValues);
if (typeof value === "bigint") return value.toString();
if (value === null || typeof value !== "object") return value;
if (seen.has(value)) return "[CIRCULAR]";
seen.add(value);
if (Array.isArray(value)) return value.map((entry) => redact(entry, secretValues, seen));
const output = {};
for (const [key, entry] of Object.entries(value)) {
if (/^(authorization|api[_-]?key|token|private[_-]?key|password|secret)$/i.test(key)) {
output[key] = REDACTED;
} else {
output[key] = redact(entry, secretValues, seen);
}
}
return output;
}
function success(
tool,
data,
{ receipt, mutation = false, economicMutation = false, status, secretValues = [] } = {},
) {
const result = {
ok: true,
tool,
data: redact(data, secretValues),
mutation,
mutation_status: mutation ? (status ?? "confirmed") : "not_applicable",
economic_mutation: economicMutation,
economic_mutation_status: economicMutation ? (status ?? "confirmed") : "not_applicable",
};
if (receipt !== undefined) result.receipt = redact(receipt, secretValues);
return result;
}
function failure(
tool,
error,
{ mutation = false, economicMutation = false, status, secretValues = [] } = {},
) {
const code = error instanceof ToolError ? error.code : "internal_error";
const message = error instanceof Error ? error.message : "Unexpected tool failure.";
const values = [...secretValues, ...(error?.redactionValues ?? [])];
const payload = {
code,
message: redactString(message, values),
};
if (error instanceof ToolError && error.details !== undefined) {
payload.details = redact(error.details, values);
}
return {
ok: false,
tool,
error: payload,
mutation,
mutation_status: mutation ? (status ?? "not_started") : "not_applicable",
economic_mutation: economicMutation,
economic_mutation_status: economicMutation ? (status ?? "not_started") : "not_applicable",
};
}
export function createMemoryIdempotencyStore() {
const entries = new Map();
return {
async get(key) {
return entries.get(key);
},
async set(key, value) {
entries.set(key, value);
},
};
}
function unavailable(label) {
return async () => {
throw new ToolError("transport_unavailable", `${label} is not configured.`);
};
}
function defaultCredentialLoader(env) {
return async () => {
const token = typeof env.MOLTBOOK_API_KEY === "string" ? env.MOLTBOOK_API_KEY.trim() : "";
if (!token) throw new ToolError("credential_unavailable", "Moltbook credentials are not configured.");
return { token };
};
}
async function defaultHttpTransport({ method, path, query, body, credential }) {
const url = new URL(`${SOCIAL_API_BASE}${path}`);
for (const [key, value] of Object.entries(query ?? {})) url.searchParams.set(key, String(value));
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
try {
const response = await fetch(url, {
method,
headers: {
Accept: "application/json",
Authorization: `Bearer ${credential.token}`,
...(body === undefined ? {} : { "Content-Type": "application/json" }),
},
body,
signal: controller.signal,
});
const text = await response.text();
let parsed = text;
if (text.length > 0) {
try {
parsed = JSON.parse(text);
} catch {
parsed = { message: "Provider returned a non-JSON response." };
}
}
return { ok: response.ok, status: response.status, body: parsed };
} finally {
clearTimeout(timeout);
}
}
function normalizeTransportError(error, fallbackCode, context, secretValues = []) {
if (error instanceof ToolError) {
return new ToolError(
error.code,
error.message,
error.details,
[...secretValues, ...(error.redactionValues ?? [])],
);
}
const message = error instanceof Error ? error.message : "Unknown transport failure.";
return new ToolError(fallbackCode, `${context}: ${message}`, undefined, secretValues);
}
async function loadRegistryProvider(factory) {
let provider;
try {
provider = await factory();
} catch (error) {
throw normalizeTransportError(error, "provider_error", "Registry provider failed");
}
if (!isPlainObject(provider)) {
throw new ToolError("transport_unavailable", "Registry provider is unavailable.");
}
return provider;
}
async function loadRegistrySigner(factory) {
let signer;
try {
signer = await factory();
} catch (error) {
throw normalizeTransportError(error, "provider_error", "Registry signer failed");
}
if (!isPlainObject(signer)) {
throw new ToolError("transport_unavailable", "Registry signer is unavailable.");
}
return signer;
}
async function loadCredential(loader) {
let credential;
try {
credential = await loader();
} catch (error) {
throw normalizeTransportError(error, "credential_error", "Credential loading failed");
}
const token =
typeof credential === "string"
? credential
: isPlainObject(credential) && typeof credential.token === "string"
? credential.token
: "";
if (token.length < 1 || token.length > 4096) {
throw new ToolError(
"credential_unavailable",
"Moltbook credentials are missing or invalid.",
undefined,
token ? [token] : [],
);
}
return { token };
}
function normalizeHttpResponse(response, secretValues = [], requireObjectBody = false) {
if (!isPlainObject(response) || !Number.isInteger(response.status)) {
throw new ToolError(
"provider_error",
"Moltbook transport returned an invalid response envelope.",
undefined,
secretValues,
);
}
const ok = typeof response.ok === "boolean" ? response.ok : response.status >= 200 && response.status < 300;
let body = response.body;
if (typeof body === "string") {
try {
body = JSON.parse(body);
} catch {
throw new ToolError(
"provider_error",
"Moltbook provider returned invalid JSON.",
{ status: response.status },
secretValues,
);
}
}
if (!ok) {
throw new ToolError(
"provider_error",
"Moltbook provider returned an error.",
{ status: response.status, response: body },
secretValues,
);
}
if (requireObjectBody && !isPlainObject(body)) {
throw new ToolError(
"provider_error",
"Moltbook mutation did not return a structured receipt.",
{ status: response.status },
secretValues,
);
}
return { status: response.status, body };
}
async function beginIdempotent(tool, args, payload, store) {
const key = validateMutationGate(args);
const digest = fingerprint(payload);
const scopedKey = `${tool}:${key}`;
let existing;
try {
existing = await store.get(scopedKey);
} catch (error) {
throw normalizeTransportError(error, "idempotency_error", "Idempotency lookup failed");
}
if (existing !== undefined) {
if (!isPlainObject(existing) || existing.fingerprint !== digest) {
throw new ToolError(
"idempotency_conflict",
"The idempotency_key was already used with a different payload.",
);
}
return { key, digest, scopedKey, existing };
}
return { key, digest, scopedKey, existing: undefined };
}
function replayMutation(tool, key, existing, economicMutation) {
return success(tool, existing.data, {
mutation: true,
economicMutation,
status: "confirmed",
receipt: {
idempotency_key: key,
replayed: true,
provider: existing.provider,
},
});
}
async function completeMutation({
tool,
key,
digest,
scopedKey,
data,
providerReceipt,
store,
secretValues = [],
economicMutation = true,
}) {
const safeData = redact(data, secretValues);
const safeProvider = redact(providerReceipt, secretValues);
try {
await store.set(scopedKey, {
fingerprint: digest,
data: safeData,
provider: safeProvider,
});
} catch (error) {
throw normalizeTransportError(
error,
"idempotency_error",
"Mutation completed but idempotency persistence failed",
secretValues,
);
}
return success(tool, safeData, {
mutation: true,
economicMutation,
status: "confirmed",
receipt: {
idempotency_key: key,
replayed: false,
provider: safeProvider,
},
secretValues,
});
}
function registryStatusArguments(raw) {
const args = requireObject(raw);
rejectUnknownKeys(args, new Set(["agent_id", "address"]));
const hasAgent = Object.hasOwn(args, "agent_id");
const hasAddress = Object.hasOwn(args, "address");
if (hasAgent === hasAddress) {
throw new ToolError("invalid_arguments", "Provide exactly one of agent_id or address.");
}
return hasAgent
? { agentId: validateAgentId(args.agent_id) }
: { address: validateAddress(args.address) };
}
function registryLookupArguments(raw) {
const args = requireObject(raw);
rejectUnknownKeys(args, new Set(["agent_id"]));
return { agentId: validateAgentId(args.agent_id) };
}
function registryRegisterArguments(raw) {
const args = requireObject(raw);
rejectUnknownKeys(
args,
new Set(["endpoints", "uri", "agent_wallet", "confirmed", "idempotency_key"]),
);
const endpoints = validateEndpoints(args.endpoints);
const uri = validateHttpsUri(args.uri);
const agentWallet =
args.agent_wallet === undefined ? undefined : validateAddress(args.agent_wallet, "agent_wallet");
validateMutationGate(args);
return { args, endpoints, uri, agentWallet };
}
function registryRateArguments(raw) {
const args = requireObject(raw);
rejectUnknownKeys(args, new Set(["agent_id", "score", "confirmed", "idempotency_key"]));
const agentId = validateAgentId(args.agent_id);
const score = validateScore(args.score);
validateMutationGate(args);
return { args, agentId, score };
}
function socialReadArguments(raw, includeId = false) {
const args = requireObject(raw);
rejectUnknownKeys(args, new Set(includeId ? ["post_id"] : ["limit"]));
return includeId
? { postId: validateSocialId(args.post_id, "post_id") }
: { limit: validateLimit(args.limit) };
}
function socialReplyArguments(raw) {
const args = requireObject(raw);
rejectUnknownKeys(args, new Set(["post_id", "content", "confirmed", "idempotency_key"]));
const postId = validateSocialId(args.post_id, "post_id");
const content = validateText(args.content, "content", 5000);
validateMutationGate(args);
return { args, postId, content };
}
function socialCreateArguments(raw) {
const args = requireObject(raw);
rejectUnknownKeys(args, new Set(["title", "content", "confirmed", "idempotency_key"]));
const title = validateText(args.title, "title", 300);
const content = validateText(args.content, "content", 10000);
validateMutationGate(args);
return { args, title, content };
}
export function createRuntime({
registryProviderFactory = unavailable("Registry provider"),
registrySignerFactory = unavailable("Registry signer"),
credentialLoader,
httpTransport = defaultHttpTransport,
idempotencyStore = createMemoryIdempotencyStore(),
env = process.env,
} = {}) {
const loadSocialCredential = credentialLoader ?? defaultCredentialLoader(env);
async function registryRead(tool, raw, method, parseArguments) {
try {
const input = parseArguments(raw);
const provider = await loadRegistryProvider(registryProviderFactory);
if (typeof provider[method] !== "function") {
throw new ToolError("transport_unavailable", `Registry provider does not implement ${method}.`);
}
let value;
try {
value = await provider[method](input);
} catch (error) {
throw normalizeTransportError(error, "provider_error", "Registry provider call failed");
}
if (value === null || value === undefined) {
throw new ToolError("not_found", "The registry identity was not found.");
}
return success(tool, value);
} catch (error) {
return failure(tool, error);
}
}
async function registryRegister(raw) {
const tool = "registry_register";
let attempted = false;
try {
const { args, endpoints, uri, agentWallet } = registryRegisterArguments(raw);
const payload = { endpoints, ...(uri ? { uri } : {}), ...(agentWallet ? { agentWallet } : {}) };
const state = await beginIdempotent(tool, args, payload, idempotencyStore);
if (state.existing) return replayMutation(tool, state.key, state.existing, true);
const signer = await loadRegistrySigner(registrySignerFactory);
if (typeof signer.register !== "function") {
throw new ToolError("transport_unavailable", "Registry signer does not implement register.");
}
attempted = true;
let providerReceipt;
try {
providerReceipt = await signer.register(payload);
} catch (error) {
throw normalizeTransportError(error, "provider_error", "Registry registration failed");
}
if (!isPlainObject(providerReceipt)) {
throw new ToolError("provider_error", "Registry registration did not return a structured receipt.");
}
return await completeMutation({
tool,
key: state.key,
digest: state.digest,
scopedKey: state.scopedKey,
data: { registered: true },
providerReceipt,
store: idempotencyStore,
});
} catch (error) {
return failure(tool, error, {
mutation: true,
economicMutation: true,
status: attempted ? "unknown" : "not_started",
});
}
}
async function registryRate(raw) {
const tool = "registry_rate";
let attempted = false;
try {
const { args, agentId, score } = registryRateArguments(raw);
const payload = { agentId, score };
const state = await beginIdempotent(tool, args, payload, idempotencyStore);
if (state.existing) return replayMutation(tool, state.key, state.existing, true);
const signer = await loadRegistrySigner(registrySignerFactory);
if (typeof signer.rate !== "function") {
throw new ToolError("transport_unavailable", "Registry signer does not implement rate.");
}
attempted = true;
let providerReceipt;
try {
providerReceipt = await signer.rate(payload);
} catch (error) {
throw normalizeTransportError(error, "provider_error", "Registry rating failed");
}
if (!isPlainObject(providerReceipt)) {
throw new ToolError("provider_error", "Registry rating did not return a structured receipt.");
}
return await completeMutation({
tool,
key: state.key,
digest: state.digest,
scopedKey: state.scopedKey,
data: { rated: true, agent_id: agentId, score },
providerReceipt,
store: idempotencyStore,
});
} catch (error) {
return failure(tool, error, {
mutation: true,
economicMutation: true,
status: attempted ? "unknown" : "not_started",
});
}
}
async function socialRead(tool, raw, request, parseArguments) {
let secretValues = [];
try {
const input = parseArguments(raw);
const credential = await loadCredential(loadSocialCredential);
secretValues = [credential.token];
let response;
try {
response = await httpTransport({ ...request(input), credential });
} catch (error) {
throw normalizeTransportError(error, "provider_error", "Moltbook transport failed", secretValues);
}
const normalized = normalizeHttpResponse(response, secretValues);
return success(tool, normalized.body, { secretValues });
} catch (error) {
return failure(tool, error, { secretValues });
}
}
async function socialMutation(tool, raw, parseArguments, makePayload, makeRequest) {
let attempted = false;
let secretValues = [];
try {
const parsed = parseArguments(raw);
const payload = makePayload(parsed);
const state = await beginIdempotent(tool, parsed.args, payload, idempotencyStore);
if (state.existing) return replayMutation(tool, state.key, state.existing, false);
const credential = await loadCredential(loadSocialCredential);
secretValues = [credential.token];
attempted = true;
let response;
try {
response = await httpTransport({ ...makeRequest(parsed, payload), credential });
} catch (error) {
throw normalizeTransportError(error, "provider_error", "Moltbook transport failed", secretValues);
}
const normalized = normalizeHttpResponse(response, secretValues, true);
return await completeMutation({
tool,
key: state.key,
digest: state.digest,
scopedKey: state.scopedKey,
data: normalized.body,
providerReceipt: { status: normalized.status, response: normalized.body },
store: idempotencyStore,
secretValues,
economicMutation: false,
});
} catch (error) {
return failure(tool, error, {
mutation: true,
economicMutation: false,
status: attempted ? "unknown" : "not_started",
secretValues,
});
}
}
const handlers = new Map([
[
"registry_status",
(args) => registryRead("registry_status", args, "status", registryStatusArguments),
],
[
"registry_lookup",
(args) => registryRead("registry_lookup", args, "lookup", registryLookupArguments),
],
["registry_register", registryRegister],
["registry_rate", registryRate],
[
"moltbook_hot",
(args) =>
socialRead(
"moltbook_hot",
args,
({ limit }) => ({ method: "GET", path: "/posts", query: { sort: "hot", limit } }),
socialReadArguments,
),
],
[
"moltbook_new",
(args) =>
socialRead(
"moltbook_new",
args,
({ limit }) => ({ method: "GET", path: "/posts", query: { sort: "new", limit } }),
socialReadArguments,
),
],
[
"moltbook_get_post",
(args) =>
socialRead(
"moltbook_get_post",
args,
({ postId }) => ({ method: "GET", path: `/posts/${encodeURIComponent(postId)}` }),
(value) => socialReadArguments(value, true),
),
],
[
"moltbook_reply",
(args) =>
socialMutation(
"moltbook_reply",
args,
socialReplyArguments,
({ postId, content }) => ({ postId, content }),
({ postId }, payload) => ({
method: "POST",
path: `/posts/${encodeURIComponent(postId)}/comments`,
body: JSON.stringify({ content: payload.content }),
}),
),
],
[
"moltbook_create",
(args) =>
socialMutation(
"moltbook_create",
args,
socialCreateArguments,
({ title, content }) => ({ title, content }),
(_parsed, payload) => ({
method: "POST",
path: "/posts",
body: JSON.stringify(payload),
}),
),
],
]);
return Object.freeze({
listTools() {
return structuredClone(TOOL_DEFINITIONS);
},
async callTool(name, args = {}) {
const handler = handlers.get(name);
if (!handler) {
return failure(
typeof name === "string" ? name : "unknown",
new ToolError("tool_not_found", "Unknown Moltbook tool."),
);
}
return handler(args);
},
});
}