import { ChatCompletionMessageParam as OpenAIMessage } from "openai/resources/chat"; import { Collection, Message as DiscordMessage, InteractionResponse } from "discord.js"; import FoldToAscii from "fold-to-ascii"; import { config } from "./index"; import countTokens from "./tokenCounter"; import { RequestMessage } from "./execution"; /** * formats the request to use as a message contend in OpenAI api * @param request the request to format * @returns the formatted request */ export async function formatRequestOrResponse(request: RequestMessage | InteractionResponse): Promise { if (request instanceof DiscordMessage) { return formatMessage(request); } if (request instanceof InteractionResponse) { return formatMessage(await request.fetch()); } return formatMessage(await request.fetchReply()); } /** * Formats the message to use as a message content in OpenAI api * @param message the message to format * @returns the formatted message */ export function formatMessage(message: DiscordMessage): string { let rvalue: string = message.cleanContent; for (const attachment of message.attachments) { rvalue += ` [Attachment: ${attachment[1].name}`; rvalue += attachment[1].description ? ` - ${attachment[1].description}` : ""; rvalue += "]"; } for (const embed of message.embeds) { rvalue += ` [Embed:`; rvalue += embed.title ? ` ${embed.title}` : ""; rvalue += embed.author ? ` by ${embed.author.name}` : ""; rvalue += (embed.title || embed.author) && embed.description ? " -": ""; rvalue += embed.description ? ` ${embed.description}` : ""; for (const field of embed.fields) { rvalue += ` [${field.name} - ${field.value}]`; } rvalue += "]"; } for (const sticker of message.stickers.values()) { rvalue += ` [Sticker:`; rvalue += sticker.name; rvalue += sticker.description ? ` - ${sticker.description}` : ""; rvalue += "]"; } return rvalue.trim(); } /** * Formats name to be accepted in OpenAI api * @param name the name to format * @returns formatted name */ function formatName(name: string): string { // replace all characters to ascii equivelant return FoldToAscii.foldReplacing(name) // White spaces are not allowed .replace(" ", "_") // replace characters that are not accepted by api with '_' .replace(/[^a-zA-Z0-9_-]/g, "_") // remove trailing '-_' characters .replace(/^[-_]+|[-_]+$/g, "") // remove duplicated '-_' character sequences .replace(/([-_])([-_])+/g, "$1"); } /** * Gets the name of the author of the message, formatted to be used in OpenAI api * @param message the message to get the author name * @returns the proper author name of the message */ function getAuthorUsername(message: DiscordMessage): string | undefined { if (message.author.id === message.client.user.id ) return undefined; if (message.member) { const name = formatName(message.member.displayName); if (name.length >= 3) return name; } const name = formatName(message.author.username); if (name.length > 0) return name; return message.author.id; } /** * Converts the Iterable of Discord Messages to array of OpenAI Messages to send * first message in the interable will be the last of the returned array (reverse order) * @param messages the iterable to convert * @returns the converted messages */ export default function toOpenAIMessages( messages: Iterable, ): OpenAIMessage[] { const rvalue: OpenAIMessage[] = []; let tokenCount = 0; for (const message of messages) { const content = formatMessage(message); // FIXME: tokens are not being counted properly (it's lower than it is) but it's enough for me for now. tokenCount += countTokens(content); if (tokenCount > config.readLimits.tokens) break; rvalue.push({ role: message.author.id === message.client.user.id ? "assistant" : "user", content: content, name: getAuthorUsername(message), }); } rvalue.push(...config.systemPrompt([...messages][0]).reverse()); return rvalue.reverse(); }