|
|
|
|
@@ -1,232 +1,94 @@
|
|
|
|
|
import { DebugBuilder } from "../../modules/debugger.mjs";
|
|
|
|
|
import { requestNodeJoinSystem } from "../../modules/socketServerWrappers.mjs";
|
|
|
|
|
import { getSystemByName } from "../../modules/mongo-wrappers/mongoSystemsWrappers.mjs";
|
|
|
|
|
import { getConfig } from "../../modules/mongo-wrappers/mongoConfigWrappers.mjs";
|
|
|
|
|
import { getAvailableTokensInGuild } from "../modules/wrappers.mjs";
|
|
|
|
|
const log = new DebugBuilder("server", "discordBot.modules.gptHandler");
|
|
|
|
|
import dotenv from "dotenv";
|
|
|
|
|
dotenv.config();
|
|
|
|
|
|
|
|
|
|
import { OpenAI } from "openai";
|
|
|
|
|
import { EventEmitter } from "events";
|
|
|
|
|
|
|
|
|
|
// Initialize environment variables
|
|
|
|
|
dotenv.config();
|
|
|
|
|
const openai = new OpenAI(process.env.OPENAI_API_KEY);
|
|
|
|
|
|
|
|
|
|
const log = new DebugBuilder("server", "discordBot.modules.gptHandler");
|
|
|
|
|
const assistant = await openai.beta.assistants.create({
|
|
|
|
|
name: "Emmelia",
|
|
|
|
|
instructions: process.env.DRB_SERVER_INITIAL_PROMPT,
|
|
|
|
|
model: "gpt-4o",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let assistant;
|
|
|
|
|
let eventHandler;
|
|
|
|
|
|
|
|
|
|
(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const openai = new OpenAI(process.env.OPENAI_API_KEY);
|
|
|
|
|
|
|
|
|
|
assistant = await openai.beta.assistants.create({
|
|
|
|
|
name: "Emmelia",
|
|
|
|
|
instructions: (await getConfig("emmeliaInitialPrompt")) || "",
|
|
|
|
|
model: "gpt-4o-mini",
|
|
|
|
|
tools: [
|
|
|
|
|
{
|
|
|
|
|
type: "function",
|
|
|
|
|
function: {
|
|
|
|
|
name: "checkUserVoiceChannel",
|
|
|
|
|
description: "Check if the user is in a voice channel",
|
|
|
|
|
parameters: {
|
|
|
|
|
type: "object",
|
|
|
|
|
properties: {
|
|
|
|
|
userId: {
|
|
|
|
|
type: "string",
|
|
|
|
|
description: "The ID of the user",
|
|
|
|
|
},
|
|
|
|
|
guildId: {
|
|
|
|
|
type: "string",
|
|
|
|
|
description: "The ID of the guild",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
required: ["userId", "guildId"],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: "function",
|
|
|
|
|
function: {
|
|
|
|
|
name: "getSelectedSystem",
|
|
|
|
|
description: "Retrieve the selected system details",
|
|
|
|
|
parameters: {
|
|
|
|
|
type: "object",
|
|
|
|
|
properties: {
|
|
|
|
|
systemName: {
|
|
|
|
|
type: "string",
|
|
|
|
|
description: "The name of the system",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
required: ["systemName"],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: "function",
|
|
|
|
|
function: {
|
|
|
|
|
name: "joinSelectedSystem",
|
|
|
|
|
description: "Join the selected system in the user's voice channel",
|
|
|
|
|
parameters: {
|
|
|
|
|
type: "object",
|
|
|
|
|
properties: {
|
|
|
|
|
userId: {
|
|
|
|
|
type: "string",
|
|
|
|
|
description: "The ID of the user",
|
|
|
|
|
},
|
|
|
|
|
guildId: {
|
|
|
|
|
type: "string",
|
|
|
|
|
description: "The ID of the guild",
|
|
|
|
|
},
|
|
|
|
|
systemName: {
|
|
|
|
|
type: "string",
|
|
|
|
|
description: "The name of the system",
|
|
|
|
|
},
|
|
|
|
|
channelId: {
|
|
|
|
|
type: "string",
|
|
|
|
|
description: "The ID of the voice channel",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
required: ["userId", "guildId", "systemName", "channelId"],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
class EventHandler extends EventEmitter {
|
|
|
|
|
constructor(client) {
|
|
|
|
|
super();
|
|
|
|
|
this.client = client;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async onEvent(event) {
|
|
|
|
|
try {
|
|
|
|
|
console.log(event);
|
|
|
|
|
if (event.event === "thread.run.requires_action") {
|
|
|
|
|
await this.handleRequiresAction(
|
|
|
|
|
event.data,
|
|
|
|
|
event.data.id,
|
|
|
|
|
event.data.thread_id,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Error handling event:", error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async handleRequiresAction(data, runId, threadId) {
|
|
|
|
|
try {
|
|
|
|
|
const toolOutputs = await Promise.all(
|
|
|
|
|
data.required_action.submit_tool_outputs.tool_calls.map(
|
|
|
|
|
async (toolCall) => {
|
|
|
|
|
switch (toolCall.function.name) {
|
|
|
|
|
case "checkUserVoiceChannel":
|
|
|
|
|
return await this.checkUserVoiceChannel(toolCall);
|
|
|
|
|
case "getSelectedSystem":
|
|
|
|
|
return await this.getSelectedSystem(toolCall);
|
|
|
|
|
case "joinSelectedSystem":
|
|
|
|
|
return await this.joinSelectedSystem(toolCall);
|
|
|
|
|
default:
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Unknown function: ${toolCall.function.name}`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
await this.submitToolOutputs(toolOutputs, runId, threadId);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Error processing required action:", error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async checkUserVoiceChannel(toolCall) {
|
|
|
|
|
const { userId, guildId } = JSON.parse(toolCall.function.arguments);
|
|
|
|
|
const guild = await this.client.guilds.get(guildId);
|
|
|
|
|
const member = await guild.members.get(userId);
|
|
|
|
|
const isInVoiceChannel = !!member.voice.channel;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
tool_call_id: toolCall.id,
|
|
|
|
|
output: JSON.stringify({
|
|
|
|
|
isInVoiceChannel,
|
|
|
|
|
channelId: member.voice.channelId,
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async getSelectedSystem(toolCall) {
|
|
|
|
|
const { systemName } = JSON.parse(toolCall.function.arguments);
|
|
|
|
|
const system = await getSystemByName(systemName);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
tool_call_id: toolCall.id,
|
|
|
|
|
output: JSON.stringify(system),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async joinSelectedSystem(toolCall) {
|
|
|
|
|
const { userId, guildId, systemName, channelId } = JSON.parse(
|
|
|
|
|
toolCall.function.arguments,
|
|
|
|
|
);
|
|
|
|
|
const system = await getSystemByName(systemName);
|
|
|
|
|
const guild = await this.client.guilds.fetch(guildId);
|
|
|
|
|
const discordToken = await getAvailableTokensInGuild(guildId);
|
|
|
|
|
|
|
|
|
|
if (discordToken) {
|
|
|
|
|
const result = await requestNodeJoinSystem(
|
|
|
|
|
system,
|
|
|
|
|
channelId,
|
|
|
|
|
discordToken,
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
tool_call_id: toolCall.id,
|
|
|
|
|
output: JSON.stringify({ success: true, result }),
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
return {
|
|
|
|
|
tool_call_id: toolCall.id,
|
|
|
|
|
output: JSON.stringify({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "No available bots.",
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async submitToolOutputs(toolOutputs, runId, threadId) {
|
|
|
|
|
try {
|
|
|
|
|
const stream =
|
|
|
|
|
this.client.beta.threads.runs.submitToolOutputsStream(
|
|
|
|
|
threadId,
|
|
|
|
|
runId,
|
|
|
|
|
{ tool_outputs: toolOutputs },
|
|
|
|
|
);
|
|
|
|
|
for await (const event of stream) {
|
|
|
|
|
this.emit("event", event);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Error submitting tool outputs:", error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
eventHandler = new EventHandler(openai);
|
|
|
|
|
eventHandler.on("event", eventHandler.onEvent.bind(eventHandler));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Initialization error:", error);
|
|
|
|
|
class EventHandler extends EventEmitter {
|
|
|
|
|
constructor(client) {
|
|
|
|
|
super();
|
|
|
|
|
this.client = client;
|
|
|
|
|
}
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
async onEvent(event) {
|
|
|
|
|
try {
|
|
|
|
|
console.log(event);
|
|
|
|
|
// Retrieve events that are denoted with 'requires_action'
|
|
|
|
|
// since these will have our tool_calls
|
|
|
|
|
if (event.event === "thread.run.requires_action") {
|
|
|
|
|
await this.handleRequiresAction(
|
|
|
|
|
event.data,
|
|
|
|
|
event.data.id,
|
|
|
|
|
event.data.thread_id,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Error handling event:", error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async handleRequiresAction(data, runId, threadId) {
|
|
|
|
|
try {
|
|
|
|
|
const toolOutputs =
|
|
|
|
|
data.required_action.submit_tool_outputs.tool_calls.map((toolCall) => {
|
|
|
|
|
// Call the function
|
|
|
|
|
switch (toolCall.function.name) {
|
|
|
|
|
case "getCurrentTemperature":
|
|
|
|
|
return {
|
|
|
|
|
tool_call_id: toolCall.id,
|
|
|
|
|
output: "57",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
// Submit all the tool outputs at the same time
|
|
|
|
|
await this.submitToolOutputs(toolOutputs, runId, threadId);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Error processing required action:", error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async submitToolOutputs(toolOutputs, runId, threadId) {
|
|
|
|
|
try {
|
|
|
|
|
// Use the submitToolOutputsStream helper
|
|
|
|
|
const stream = this.client.beta.threads.runs.submitToolOutputsStream(
|
|
|
|
|
threadId,
|
|
|
|
|
runId,
|
|
|
|
|
{ tool_outputs: toolOutputs },
|
|
|
|
|
);
|
|
|
|
|
for await (const event of stream) {
|
|
|
|
|
this.emit("event", event);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Error submitting tool outputs:", error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const eventHandler = new EventHandler(openai);
|
|
|
|
|
eventHandler.on("event", eventHandler.onEvent.bind(eventHandler));
|
|
|
|
|
|
|
|
|
|
export const gptHandler = async (additionalMessages) => {
|
|
|
|
|
const thread = await openai.beta.threads.create();
|
|
|
|
|
|
|
|
|
|
// Add the additional messages to the conversation
|
|
|
|
|
for (const msgObj of additionalMessages) {
|
|
|
|
|
await openai.beta.threads.messages.create(thread.id, msgObj);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.DEBUG("AI Conversation:", thread);
|
|
|
|
|
|
|
|
|
|
// Run the thread to get a response
|
|
|
|
|
try {
|
|
|
|
|
const thread = await openai.beta.threads.create();
|
|
|
|
|
|
|
|
|
|
for (const msgObj of additionalMessages) {
|
|
|
|
|
await openai.beta.threads.messages.create(thread.id, msgObj);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const stream = await openai.beta.threads.runs.stream(
|
|
|
|
|
thread.id,
|
|
|
|
|
{ assistant_id: assistant.id },
|
|
|
|
|
@@ -237,8 +99,9 @@ export const gptHandler = async (additionalMessages) => {
|
|
|
|
|
eventHandler.emit("event", event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let response;
|
|
|
|
|
const messages = await openai.beta.threads.messages.list(thread.id);
|
|
|
|
|
const response = messages.data[0].content[0].text.value;
|
|
|
|
|
response = messages.data[0].content[0].text.value;
|
|
|
|
|
|
|
|
|
|
log.DEBUG("AI Response:", response);
|
|
|
|
|
|
|
|
|
|
|