diff --git a/package.json b/package.json
index abedd80..9216279 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "aslobot-matrix",
- "version": "0.8.2",
+ "version": "0.9.0",
"description": "",
"license": "ISC",
"author": "",
diff --git a/src/config.preset.json b/src/config.preset.json
index 1b6e656..d408241 100644
--- a/src/config.preset.json
+++ b/src/config.preset.json
@@ -26,6 +26,7 @@
"api": {
"key": ""
},
+ "password": "",
"personalities": [
{
"personalityName": "puk",
diff --git a/src/modules/ai/ai.ts b/src/modules/ai/ai.ts
index d200556..3f2b7f0 100644
--- a/src/modules/ai/ai.ts
+++ b/src/modules/ai/ai.ts
@@ -59,19 +59,17 @@ const onAI = async (
personality = config.app.ai.personalities[state.personality.index];
- let textMod = text.replace("!ai", "").trim().toLowerCase();
+ let textMod = text.replace("!ai", "").trim();
let instructions = {
prefferedLanguages: ["english", "slovak"],
users: alts.map((alt) => ({
names: alt.keys,
alt: alt.alt,
- backstory: alt.id
- ? getUserById(`${alt.id}:${config.server}`).information
- : undefined,
})),
aiPersonality: personality?.personality ?? "",
aiLikes: personality?.likes ?? "",
aiDislikes: personality?.dislikes ?? "",
+ aiMemory: state.aiMemory ?? [],
} as IAIInstructions;
const username = getUserName(user);
@@ -81,6 +79,11 @@ const onAI = async (
}
const responseAI = await getTextGemini(
+ {
+ client: client,
+ roomId: roomId,
+ sender: sender,
+ },
instructions,
`${username}: ${textMod}`,
`${repliedUsername}: ${repliedMessage}`,
diff --git a/src/modules/ai/index.ts b/src/modules/ai/index.ts
index ceedc5b..b69311d 100644
--- a/src/modules/ai/index.ts
+++ b/src/modules/ai/index.ts
@@ -1,2 +1,4 @@
export * from "./ai.js";
export * from "./types.js";
+export * from "./alts.js";
+export * from "./prices.js";
diff --git a/src/modules/ai/types.ts b/src/modules/ai/types.ts
index c7a64cb..7687d52 100644
--- a/src/modules/ai/types.ts
+++ b/src/modules/ai/types.ts
@@ -16,6 +16,7 @@ interface IAIInstructions {
aiPersonality: string;
aiLikes: string;
aiDislikes: string;
+ aiMemory: string[];
}
export { type IAIAlt, type IAIUser, type IAIInstructions };
diff --git a/src/modules/base/base.ts b/src/modules/base/base.ts
index d406df5..26af25f 100644
--- a/src/modules/base/base.ts
+++ b/src/modules/base/base.ts
@@ -69,7 +69,6 @@ const onHelp = (_text: string, roomId: string) => {
!bowling {text} - Repeats your message in bowling
!help - Prints this help message
!me / !me {mention} - Prints data about you
- !myinfo {newinfo} - Updates your description
!leaderboard - Prints total user ranking
!aileaderboard - Prints total user ai cost
diff --git a/src/modules/user/user.ts b/src/modules/user/user.ts
index 76cedc5..7c9d9d0 100644
--- a/src/modules/user/user.ts
+++ b/src/modules/user/user.ts
@@ -21,10 +21,6 @@ const registerModuleUser = (
startConditions: [`${config.app.triggerPrefix}me`],
callbackFunc: onMe,
});
- callbackStore.messageCallbacks.push({
- startConditions: [`${config.app.triggerPrefix}myinfo `],
- callbackFunc: onMyInfo,
- });
callbackStore.messageCallbacks.push({
startConditions: [`${config.app.triggerPrefix}leaderboard`],
callbackFunc: onLeaderboard,
@@ -55,22 +51,6 @@ const onMe = (text: string, roomId: string, sender: string) => {
);
};
-const onMyInfo = (text: string, roomId: string, sender: string) => {
- const user = getUserById(sender);
- const newInformation = text.replace(
- `${config.app.triggerPrefix}myinfo `,
- "",
- );
-
- if (!user || newInformation.length < 3) {
- return;
- }
-
- user.information = newInformation;
-
- client.sendTextMessage(roomId, "Information updated");
-};
-
const onLeaderboard = (_text: string, roomId: string) => {
const mapUsersToLeaderboard = (user: IUser): string => {
const level = getLevel(user.experience);
diff --git a/src/services/ai/ai.ts b/src/services/ai/ai.ts
index ffb434d..31664c7 100644
--- a/src/services/ai/ai.ts
+++ b/src/services/ai/ai.ts
@@ -1,28 +1,123 @@
import { GoogleGenAI } from "@google/genai";
import { config } from "../../config.js";
-import type { AIResponseImage, AIResponseText } from "./types.js";
+import type {
+ AIResponseImage,
+ AIResponseText,
+ AIToolMatrixData,
+} from "./types.js";
import type { IAIInstructions } from "../../modules/ai/types.js";
+import { FunctionCallingConfigMode } from "@google/genai";
+import { toolFunctions, tools } from "./tools.js";
+import type { FunctionResponse } from "@google/genai";
+import type { Content } from "@google/genai";
const googleAI = new GoogleGenAI({
apiKey: config.app.ai.api.key,
});
const getTextGemini = async (
+ matrixData: AIToolMatrixData,
instructions: IAIInstructions,
input: string,
oldInput?: string,
): Promise => {
+ const inputContent: Content = {
+ role: "user",
+ parts: [
+ {
+ text: input,
+ },
+ ],
+ };
+ const oldInputContent: Content = {
+ role: "user",
+ parts: [
+ {
+ text: oldInput ?? "",
+ },
+ ],
+ };
+
+ const contents: Content[] = oldInput
+ ? [oldInputContent, inputContent]
+ : [inputContent];
+
const response = await googleAI.models.generateContent({
model: "gemini-3-flash-preview",
- contents: oldInput ? [oldInput, input] : input,
+ contents: contents,
config: {
systemInstruction: JSON.stringify(instructions),
+ toolConfig: {
+ functionCallingConfig: {
+ mode: FunctionCallingConfigMode.AUTO,
+ },
+ },
+ tools: [{ functionDeclarations: tools }],
+ },
+ });
+
+ let text = response.text ?? "AI Error";
+ let token = response.usageMetadata?.totalTokenCount ?? 0;
+
+ const content = response.candidates?.at(0)?.content;
+ const functionCall = content?.parts?.at(0)?.functionCall;
+
+ if (response.text || !content || !functionCall) {
+ return {
+ text: text,
+ tokens: token,
+ };
+ }
+
+ text = `Calling function ${functionCall.name}`;
+
+ const func = toolFunctions.find(
+ (func) => func.name === functionCall.name,
+ )?.function;
+ if (!func) {
+ return {
+ text: text,
+ tokens: token,
+ };
+ }
+
+ const output = func(matrixData, functionCall.args);
+ const functionResponse: FunctionResponse = {
+ id: functionCall.id ?? "",
+ name: functionCall.name ?? "",
+ response: {
+ output: JSON.stringify(output),
+ },
+ };
+
+ const responseTool = await googleAI.models.generateContent({
+ model: "gemini-3-flash-preview",
+ contents: [
+ ...contents,
+ content,
+ {
+ role: "tool",
+ parts: [
+ {
+ functionResponse: functionResponse,
+ },
+ ],
+ },
+ ],
+ config: {
+ systemInstruction: JSON.stringify(instructions),
+ toolConfig: {
+ functionCallingConfig: {
+ mode: FunctionCallingConfigMode.AUTO,
+ },
+ },
+ tools: [{ functionDeclarations: tools }],
},
});
return {
- text: response.text ?? "AI Error",
- tokens: response.usageMetadata?.totalTokenCount ?? 0,
+ text: responseTool.text ?? "AI Error",
+ tokens: token + (responseTool.usageMetadata?.totalTokenCount ?? 0),
};
};
diff --git a/src/services/ai/index.ts b/src/services/ai/index.ts
index ceedc5b..7e1e37a 100644
--- a/src/services/ai/index.ts
+++ b/src/services/ai/index.ts
@@ -1,2 +1,3 @@
export * from "./ai.js";
export * from "./types.js";
+export * from "./tools.js";
diff --git a/src/services/ai/tools.ts b/src/services/ai/tools.ts
new file mode 100644
index 0000000..e2fbb5b
--- /dev/null
+++ b/src/services/ai/tools.ts
@@ -0,0 +1,139 @@
+import type { FunctionDeclaration } from "@google/genai";
+import { config, packageConfig } from "../../config.js";
+import type { AIToolFunction } from "./types.js";
+import { save, state } from "../../store/store.js";
+
+const tools: FunctionDeclaration[] = [
+ {
+ name: "getBotInfo",
+ description: "Gets information about this AI bot",
+ parametersJsonSchema: {
+ type: "object",
+ properties: {},
+ required: [],
+ },
+ },
+ {
+ name: "shutdownBot",
+ description: "Shuts/Turns this AI bot down",
+ parametersJsonSchema: {
+ type: "object",
+ properties: {
+ password: {
+ type: "string",
+ description: "shutdown password provided by the user",
+ },
+ },
+ required: ["password"],
+ },
+ },
+ {
+ name: "remember",
+ description: "Gets information about this AI bot",
+ parametersJsonSchema: {
+ type: "object",
+ properties: {
+ knowledge: {
+ type: "string",
+ description:
+ "new knowledge to remember and add to bot total knowledge",
+ },
+ },
+ required: ["knowledge"],
+ },
+ },
+ {
+ name: "changeUsername",
+ description: "Changes the username of this AI bot",
+ parametersJsonSchema: {
+ type: "object",
+ properties: {
+ username: {
+ type: "string",
+ description: "new username",
+ },
+ },
+ required: ["username"],
+ },
+ },
+ {
+ name: "getFullUserData",
+ description: "Gets the full user data about all users",
+ parametersJsonSchema: {
+ type: "object",
+ properties: {},
+ required: [],
+ },
+ },
+];
+
+const toolFunctions: AIToolFunction[] = [
+ {
+ name: "getBotInfo",
+ function: () => {
+ return {
+ botInfo: `Aslobot version ${packageConfig.version}`,
+ };
+ },
+ },
+ {
+ name: "shutdownBot",
+ function: async (matrix, args) => {
+ if (args.password === config.app.ai.password) {
+ await matrix.client.sendTextMessage(
+ matrix.roomId,
+ "Saving data...",
+ );
+ save();
+
+ await matrix.client.sendTextMessage(
+ matrix.roomId,
+ "Shutting down...",
+ );
+ process.exit(0);
+ }
+ return {
+ message: "incorrect password",
+ };
+ },
+ },
+ {
+ name: "remember",
+ function: (_matrix, args) => {
+ if (!state.aiMemory) {
+ state.aiMemory = [];
+ }
+ if (args.knowledge) {
+ state.aiMemory.push(args.knowledge);
+ }
+
+ return {
+ message: "success",
+ };
+ },
+ },
+ {
+ name: "changeUsername",
+ function: (matrix, args) => {
+ if (args.username) {
+ state.aiMemory.push(args.knowledge);
+ }
+
+ matrix.client.setDisplayName(args.username);
+
+ return {
+ message: "success",
+ };
+ },
+ },
+ {
+ name: "getFullUserData",
+ function: (_matrix, _args) => {
+ return {
+ users: state.users,
+ };
+ },
+ },
+];
+
+export { tools, toolFunctions };
diff --git a/src/services/ai/types.ts b/src/services/ai/types.ts
index a7cd3db..d5803d3 100644
--- a/src/services/ai/types.ts
+++ b/src/services/ai/types.ts
@@ -1,3 +1,5 @@
+import type { MatrixClient } from "matrix-js-sdk";
+
interface AIResponseText {
text: string;
tokens: number;
@@ -8,4 +10,20 @@ interface AIResponseImage {
tokens: number;
}
-export { type AIResponseText, type AIResponseImage };
+interface AIToolFunction {
+ name: string;
+ function: (matrix: AIToolMatrixData, args: any) => object;
+}
+
+interface AIToolMatrixData {
+ client: MatrixClient;
+ roomId: string;
+ sender: string;
+}
+
+export {
+ type AIResponseText,
+ type AIResponseImage,
+ type AIToolFunction,
+ type AIToolMatrixData,
+};
diff --git a/src/store/store.ts b/src/store/store.ts
index 1095b2f..6afdce6 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -10,6 +10,7 @@ let state: IState = {
index: 0,
startTime: 0,
},
+ aiMemory: [],
};
const load = () => {
diff --git a/src/store/types.ts b/src/store/types.ts
index 9c876c3..5604fa3 100644
--- a/src/store/types.ts
+++ b/src/store/types.ts
@@ -1,11 +1,11 @@
interface IState {
users: IUser[];
personality: IPersonality;
+ aiMemory: string[];
}
interface IUser {
id: string;
- information?: string;
role: TRole;
experience: number;
money: number;