Add text features: trusted triggers, context cache, response policy, MCP config tools

This commit is contained in:
Fisto
2026-09-11 15:31:23 +02:00
parent 23a1a8e593
commit 82f78bb2b4
20 changed files with 841 additions and 88 deletions
+1
View File
@@ -36,6 +36,7 @@ const KNOWN_ACTIONS = new Set<string>([
PuppetAction.StateGet,
PuppetAction.MessageSend,
PuppetAction.MessageRead,
PuppetAction.MessageFetch,
PuppetAction.DmSend
]);
+69
View File
@@ -8,12 +8,26 @@
*/
import { openPrivateChannel, sendMessage } from "@vencord/types/utils";
import { findByPropsLazy } from "@vencord/types/webpack";
import { ChannelStore, GuildStore, MessageStore, UserStore } from "@vencord/types/webpack/common";
import { isSnowflake, PUPPET_LIMITS, PuppetAction, PuppetErrorCode, type PuppetResult } from "shared/puppetProtocol";
/** Channel types that can receive text messages. */
const SENDABLE_CHANNEL_TYPES = new Set([0, 1, 3, 5, 10, 11, 12]);
/** Discord's message fetch action, located lazily at runtime. */
const MessageActions = findByPropsLazy("fetchMessages");
function compareIds(a: string, b: string): number {
try {
const left = BigInt(a);
const right = BigInt(b);
return left < right ? -1 : left > right ? 1 : 0;
} catch {
return a < b ? -1 : a > b ? 1 : 0;
}
}
class ActionError extends Error {
constructor(
public readonly code: PuppetErrorCode,
@@ -175,6 +189,61 @@ export async function handlePuppetAction(
});
}
case PuppetAction.MessageFetch: {
const { channelId, limit = 50 } = params;
assert(isSnowflake(channelId), PuppetErrorCode.InvalidSnowflake, "channelId must be a snowflake");
assert(
Number.isInteger(limit) && Number(limit) >= 1 && Number(limit) <= PUPPET_LIMITS.maxFetchLimit,
PuppetErrorCode.InvalidParam,
`limit must be an integer between 1 and ${PUPPET_LIMITS.maxFetchLimit}`
);
if (params.before !== undefined) {
assert(isSnowflake(params.before), PuppetErrorCode.InvalidSnowflake, "before must be a snowflake");
}
if (params.after !== undefined) {
assert(isSnowflake(params.after), PuppetErrorCode.InvalidSnowflake, "after must be a snowflake");
}
assert(!(params.before && params.after), PuppetErrorCode.InvalidParam, "before and after are mutually exclusive");
assert(ChannelStore.getChannel(channelId), PuppetErrorCode.ChannelNotFound, `channel ${channelId} not found`);
const fetchMessages = (MessageActions as any)?.fetchMessages;
assert(
typeof fetchMessages === "function",
PuppetErrorCode.NotSupported,
"message fetch is not available in this client"
);
const before = params.before as string | undefined;
const after = params.after as string | undefined;
await fetchMessages({
channelId,
limit: Number(limit),
...(before ? { before } : {}),
...(after ? { after } : {})
});
const all = MessageStore.getMessages(channelId)?._array ?? [];
const filtered = all.filter(message => {
if (after && compareIds(message.id, after) <= 0) return false;
if (before && compareIds(message.id, before) >= 0) return false;
return true;
});
return ok({
channelId,
fetched: filtered.length,
messages: filtered.slice(-Number(limit)).map(message => ({
id: message.id,
author: { id: message.author.id, username: message.author.username },
content: message.content,
timestamp: message.timestamp,
attachments: (message.attachments ?? []).map(attachment => ({ filename: attachment.filename }))
}))
});
}
case PuppetAction.DmSend: {
const { userId } = params;
assert(isSnowflake(userId), PuppetErrorCode.InvalidSnowflake, "userId must be a snowflake");
+4
View File
@@ -12,6 +12,7 @@ export const enum PuppetAction {
StateGet = "state.get",
MessageSend = "message.send",
MessageRead = "message.read",
MessageFetch = "message.fetch",
DmSend = "dm.send"
}
@@ -21,6 +22,7 @@ export const PuppetActions: readonly PuppetAction[] = [
PuppetAction.StateGet,
PuppetAction.MessageSend,
PuppetAction.MessageRead,
PuppetAction.MessageFetch,
PuppetAction.DmSend
];
@@ -38,6 +40,7 @@ export const enum PuppetErrorCode {
NotSendable = "NOT_SENDABLE",
SendFailed = "SEND_FAILED",
DmUnavailable = "DM_UNAVAILABLE",
NotSupported = "NOT_SUPPORTED",
RateLimited = "RATE_LIMITED",
Internal = "INTERNAL"
}
@@ -70,6 +73,7 @@ export interface PuppetEvent {
export const PUPPET_LIMITS = {
maxContentLength: 2000,
maxReadLimit: 100,
maxFetchLimit: 100,
minActionIntervalMs: 500,
maxActionsPerMinute: 30,
maxRequestBytes: 64 * 1024,