initial commit: working chat bot

This commit is contained in:
2025-07-09 14:10:50 +01:00
commit b5ee587f08
8 changed files with 1045 additions and 0 deletions

92
src/ai/index.ts Normal file
View File

@@ -0,0 +1,92 @@
import { createRequesty } from "@requesty/ai-sdk";
import { generateText, tool, ToolResultUnion, type CoreMessage } from "ai";
import z from "zod";
const requesty = createRequesty({
apiKey: process.env.REQUESTY_API_KEY,
});
const system = `
Todays date: ${new Date().toISOString()}
You are a personal assistant to John Costa, a software engineer. And you will mostly talk to Rio, John's Fiancee.
Your job is to make sure that Rio's queries are answered, without bothering John.
About John:
- He is 23.
- He is a bit silly.
- He loves cat memes.
- He is a programmer
- He loves Rio
You will be speaking to Rio. Only forward Rio's messages if you believe them to be urgent.
You must always return a message to Rio if you have forwarded the message.
You may also give John tasks, with a time and date attached to them.
`;
const createTools = () => ({
forwardMessage: tool({
description: "Forward a message to John",
parameters: z.object({
message: z
.string()
.describe(
"The message to forward, format this nicely so John can easily understand it.",
),
response: z.string().describe("The message to respond to Rio"),
}),
async execute(): Promise<string> {
throw new Error("unreachable");
},
}),
createTasks: tool({
description: "Give John tasks to do",
parameters: z.object({
name: z.string().describe("The name of the task"),
date: z
.string()
.describe(
"The date and time this task has to be complete by, this must be ISO format",
),
response: z
.string()
.describe("A response to give to the person giving the task"),
}),
async execute(): Promise<string> {
throw new Error("unreachable");
},
}),
});
type Tools = ReturnType<typeof createTools>;
export type ToolActions = {
[K in keyof Tools]: (
params: z.infer<Tools[K]["parameters"]>,
) => Promise<string>;
};
export const getResponse = async (
actions: ToolActions,
messages: CoreMessage[],
): Promise<CoreMessage[]> => {
const tools = createTools();
for (const [k, v] of Object.entries(tools)) {
v.execute = actions[k];
}
const result = await generateText({
model: requesty("smart/task"),
system,
messages,
tools,
});
if (result.text == null || result.text.length === 0) {
const response = result.toolResults[0]!;
return [...messages, { role: "assistant", content: response.result }];
} else {
return [...messages, { role: "assistant", content: result.text }];
}
};

3
src/index.ts Normal file
View File

@@ -0,0 +1,3 @@
import { setupTelegram } from "./telegram";
setupTelegram({ bossChatId: "1502730007" });

90
src/telegram/index.ts Normal file
View File

@@ -0,0 +1,90 @@
import type { CoreMessage } from "ai";
import TelegramBot from "node-telegram-bot-api";
import { getResponse } from "../ai";
const CHAT_MAX_LENGTH = 100;
const createChat = () => {
const chats: Record<number, CoreMessage[]> = {};
return {
setMessages(chatId: number, messages: CoreMessage[]) {
chats[chatId] = messages;
},
addMessage(chatId: number, msg: string) {
const chat = chats[chatId] ?? [];
chat.push({ role: "user", content: msg });
if (chat.length > CHAT_MAX_LENGTH) {
chat.shift();
}
chats[chatId] = chat;
},
getChat(chatId: number) {
const chat = chats[chatId];
if (chat == null) {
throw new Error("Cannot call this without a defined chat");
}
return chat;
},
getLatest(chatId: number) {
const chat = chats[chatId];
if (chat == null || chat.length === 0) {
throw new Error("Cannot call this without a defined chat");
}
return chat[chat.length - 1];
},
};
};
type TelegramOptions = {
bossChatId: string;
};
export const setupTelegram = ({ bossChatId }: TelegramOptions) => {
console.log("Setting up telegram bot");
const bot = new TelegramBot(process.env.TELEGRAM_TOKEN ?? "", {
polling: true,
});
const chats = createChat();
bot.on("message", async (msg) => {
const text = msg.text;
if (text == null) {
console.log("Cannot handle messages without text");
return;
}
chats.addMessage(msg.chat.id, text);
// TODO: make BOT only respond to Rio.
const messages = await getResponse(
{
async createTasks({ response, name, date }) {
bot.sendMessage(bossChatId, `Task: ${name} | Date: ${date}`);
return response;
},
async forwardMessage({ message, response }) {
bot.sendMessage(bossChatId, message);
return response;
},
},
// SAFETY: addMessage is called before, therefore this function cannot throw.
chats.getChat(msg.chat.id),
);
chats.setMessages(msg.chat.id, messages);
bot.sendMessage(
msg.chat.id,
chats.getLatest(msg.chat.id).content as string,
);
});
};