GPTcord/src/toOpenAIMessages.ts
Wroclaw b7f6a5fe91 toOpenAIMessages: Add fallback to use user id on empty resolved name
Previously if someone named whose current mechanism
resolved to empty string ("Джо" for example)
then the openai would scream that the name
does not match their server side regex.

Meaning that the bot could not be used
in a given channel until that person
changes it's display name
or it will be forgotten for the bot.
2023-09-27 20:45:23 +02:00

122 lines
4 KiB
TypeScript

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<string> {
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<DiscordMessage>,
): 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();
}