enabled whatsapp interface
This commit is contained in:
parent
4a01429e4a
commit
cdc3fcab74
|
@ -7,12 +7,12 @@ export const pathInDataDir = (filename: string) => path.join(dataDir, filename);
|
||||||
|
|
||||||
interface PlatformIdentity {
|
interface PlatformIdentity {
|
||||||
platform:
|
platform:
|
||||||
| "discord"
|
| "discord"
|
||||||
| "whatsapp"
|
| "whatsapp"
|
||||||
| "email"
|
| "email"
|
||||||
| "events"
|
| "events"
|
||||||
| "linear_key"
|
| "linear_key"
|
||||||
| "linear_email";
|
| "linear_email";
|
||||||
id: string; // Platform-specific user ID
|
id: string; // Platform-specific user ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,13 +61,28 @@ const ConfigSchema = z.object({
|
||||||
rolePermissions: z.record(z.string(), z.array(z.string())),
|
rolePermissions: z.record(z.string(), z.array(z.string())),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load user configuration data from file
|
// Mutable exports that will be updated
|
||||||
const userConfigPath = pathInDataDir("user-config.json");
|
export let userConfigs: UserConfig[] = [];
|
||||||
const rawData = fs.readFileSync(userConfigPath, "utf-8");
|
export let rolePermissions: Record<string, string[]> = {};
|
||||||
const parsedData = JSON.parse(rawData);
|
|
||||||
|
|
||||||
// Validate the parsed JSON using the Zod schema
|
// Function to load config
|
||||||
const configData = ConfigSchema.parse(parsedData);
|
function loadConfig() {
|
||||||
|
try {
|
||||||
|
const userConfigPath = pathInDataDir("user-config.json");
|
||||||
|
const rawData = fs.readFileSync(userConfigPath, "utf-8");
|
||||||
|
const parsedData = JSON.parse(rawData);
|
||||||
|
const configData = ConfigSchema.parse(parsedData);
|
||||||
|
|
||||||
// Export the validated data
|
// Update the exported variables
|
||||||
export const { users: userConfigs, rolePermissions } = configData;
|
userConfigs = configData.users;
|
||||||
|
rolePermissions = configData.rolePermissions;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading config:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial load
|
||||||
|
loadConfig();
|
||||||
|
|
||||||
|
// Setup auto-reload every minute
|
||||||
|
setInterval(loadConfig, 60 * 1000);
|
||||||
|
|
|
@ -32,16 +32,17 @@ export class MessageProcessor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private checkpointMessageString = "🔄 Chat context has been reset.";
|
||||||
|
|
||||||
public async processMessage(message: Message): Promise<void> {
|
public async processMessage(message: Message): Promise<void> {
|
||||||
const userId = message.author.id;
|
const userId = message.author.id;
|
||||||
const channelId = message.channelId || userId; // Use message.id if channelId is not available
|
const channelId = message.channelId || userId; // Use message.id if channelId is not available
|
||||||
|
|
||||||
// Check if the message is a stop message
|
// Check if the message is a stop message
|
||||||
if (["stop", "reset"].includes(message.content.toLowerCase())) {
|
if (["stop", "reset"].includes(message.content.toLowerCase())) {
|
||||||
message.platform !== "whatsapp" &&
|
(await message.send({
|
||||||
(await message.send({
|
content: this.checkpointMessageString,
|
||||||
content: "---setting this point as the start---",
|
}));
|
||||||
}));
|
|
||||||
// Clear maps
|
// Clear maps
|
||||||
const hashes = this.channelIdHashMap.get(channelId) ?? [];
|
const hashes = this.channelIdHashMap.get(channelId) ?? [];
|
||||||
hashes.forEach((hash) => {
|
hashes.forEach((hash) => {
|
||||||
|
@ -98,8 +99,7 @@ export class MessageProcessor {
|
||||||
let stopIndex = -1;
|
let stopIndex = -1;
|
||||||
for (let i = 0; i < history.length; i++) {
|
for (let i = 0; i < history.length; i++) {
|
||||||
if (
|
if (
|
||||||
history[i].content === "---setting this point as the start---" ||
|
history[i].content === this.checkpointMessageString
|
||||||
history[i].content.replaceAll("!", "").trim() === "stop"
|
|
||||||
) {
|
) {
|
||||||
stopIndex = i;
|
stopIndex = i;
|
||||||
break;
|
break;
|
||||||
|
@ -179,6 +179,10 @@ export class MessageProcessor {
|
||||||
.map((e) => JSON.stringify(e))
|
.map((e) => JSON.stringify(e))
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
|
console.log("Embeds", embeds?.length);
|
||||||
|
console.log("Files", files?.length);
|
||||||
|
console.log("Attachments", msg?.attachments?.length);
|
||||||
|
|
||||||
// Transcribe voice messages
|
// Transcribe voice messages
|
||||||
const voiceMessagesPromises = (msg.attachments || [])
|
const voiceMessagesPromises = (msg.attachments || [])
|
||||||
.filter(
|
.filter(
|
||||||
|
@ -197,6 +201,11 @@ export class MessageProcessor {
|
||||||
|
|
||||||
const voiceMessages = await Promise.all(voiceMessagesPromises);
|
const voiceMessages = await Promise.all(voiceMessagesPromises);
|
||||||
|
|
||||||
|
console.log("Voice Messages", voiceMessages);
|
||||||
|
|
||||||
|
const images = (msg.attachments || [])
|
||||||
|
.filter((a) => a.mediaType?.includes("image"))
|
||||||
|
|
||||||
// Process context message if any
|
// Process context message if any
|
||||||
let contextMessage = null;
|
let contextMessage = null;
|
||||||
if (msg.threadId) {
|
if (msg.threadId) {
|
||||||
|
@ -219,12 +228,12 @@ export class MessageProcessor {
|
||||||
created_at: format(msg.timestamp, "yyyy-MM-dd HH:mm:ss") + " IST",
|
created_at: format(msg.timestamp, "yyyy-MM-dd HH:mm:ss") + " IST",
|
||||||
context_message: contextMessage
|
context_message: contextMessage
|
||||||
? {
|
? {
|
||||||
author: contextMessage.author.username,
|
author: contextMessage.author.username,
|
||||||
created_at:
|
created_at:
|
||||||
format(contextMessage.timestamp, "yyyy-MM-dd HH:mm:ss") +
|
format(contextMessage.timestamp, "yyyy-MM-dd HH:mm:ss") +
|
||||||
" IST",
|
" IST",
|
||||||
content: contextMessage.content,
|
content: contextMessage.content,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
context_files:
|
context_files:
|
||||||
contextMessage?.attachments?.map((a) => a.url) || undefined,
|
contextMessage?.attachments?.map((a) => a.url) || undefined,
|
||||||
|
@ -238,7 +247,20 @@ export class MessageProcessor {
|
||||||
|
|
||||||
const aiMessage: OpenAI.Chat.ChatCompletionMessageParam = {
|
const aiMessage: OpenAI.Chat.ChatCompletionMessageParam = {
|
||||||
role,
|
role,
|
||||||
content: contextAsJson,
|
content: (images.length ? [
|
||||||
|
...images.map(img => {
|
||||||
|
return {
|
||||||
|
type: "image_url",
|
||||||
|
image_url: {
|
||||||
|
url: img.base64 || img.url,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: contextAsJson,
|
||||||
|
}
|
||||||
|
] : contextAsJson) as string,
|
||||||
name:
|
name:
|
||||||
user?.name ||
|
user?.name ||
|
||||||
msg.author.username.replace(/\s+/g, "_").substring(0, 64),
|
msg.author.username.replace(/\s+/g, "_").substring(0, 64),
|
||||||
|
@ -260,7 +282,7 @@ export class MessageProcessor {
|
||||||
|
|
||||||
// Collect hashes
|
// Collect hashes
|
||||||
history.forEach((msg) => {
|
history.forEach((msg) => {
|
||||||
const hash = this.generateHash(msg.content);
|
const hash = this.generateHash(typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content));
|
||||||
channelHashes.push(hash);
|
channelHashes.push(hash);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -376,9 +398,13 @@ ${summaryContent}
|
||||||
queueEntry.runningTools = true;
|
queueEntry.runningTools = true;
|
||||||
}
|
}
|
||||||
// Indicate running tools
|
// Indicate running tools
|
||||||
if (this.sentMessage) {
|
if (this.sentMessage?.platformAdapter.config.indicators.processing) {
|
||||||
await this.sentMessage.edit({ content: `Running ${fnc.name}...` });
|
if (this.sentMessage) {
|
||||||
} else await message.send({ content: `Running ${fnc.name}...` });
|
await this.sentMessage.edit({ content: `Running ${fnc.name}...` });
|
||||||
|
} else {
|
||||||
|
this.sentMessage = await message.send({ content: `Running ${fnc.name}...` })
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.on("message", (m) => {
|
.on("message", (m) => {
|
||||||
if (
|
if (
|
||||||
|
@ -434,7 +460,7 @@ ${summaryContent}
|
||||||
|
|
||||||
private generateHash(input: string): string {
|
private generateHash(input: string): string {
|
||||||
const hash = createHash("sha256");
|
const hash = createHash("sha256");
|
||||||
hash.update(input);
|
hash.update(typeof input === "string" ? input : JSON.stringify(input));
|
||||||
return hash.digest("hex");
|
return hash.digest("hex");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {
|
||||||
DMChannel,
|
DMChannel,
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
import { UserConfig, userConfigs } from "../config";
|
import { UserConfig, userConfigs } from "../config";
|
||||||
|
import { get_transcription } from "../tools/ask"; // Add this import
|
||||||
|
|
||||||
export class DiscordAdapter implements PlatformAdapter {
|
export class DiscordAdapter implements PlatformAdapter {
|
||||||
private client: Client;
|
private client: Client;
|
||||||
|
@ -254,6 +255,33 @@ export class DiscordAdapter implements PlatformAdapter {
|
||||||
// Expose getMessageInterface method
|
// Expose getMessageInterface method
|
||||||
public getMessageInterface = this.createMessageInterface;
|
public getMessageInterface = this.createMessageInterface;
|
||||||
|
|
||||||
|
public async handleMediaAttachment(attachment: Attachment) {
|
||||||
|
if (!attachment.url) return { mediaType: 'other' as const };
|
||||||
|
|
||||||
|
const response = await fetch(attachment.url);
|
||||||
|
const buffer = await response.arrayBuffer();
|
||||||
|
|
||||||
|
if (attachment.contentType?.includes('image')) {
|
||||||
|
const base64 = `data:${attachment.contentType};base64,${Buffer.from(buffer).toString('base64')}`;
|
||||||
|
return {
|
||||||
|
base64,
|
||||||
|
mediaType: 'image' as const
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachment.contentType?.includes('audio')) {
|
||||||
|
// Create temporary file for transcription
|
||||||
|
const tempFile = new File([buffer], 'audio', { type: attachment.contentType });
|
||||||
|
const transcription = await get_transcription(tempFile);
|
||||||
|
return {
|
||||||
|
transcription,
|
||||||
|
mediaType: 'audio' as const
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { mediaType: 'other' as const };
|
||||||
|
}
|
||||||
|
|
||||||
private async convertMessage(
|
private async convertMessage(
|
||||||
discordMessage: DiscordMessage
|
discordMessage: DiscordMessage
|
||||||
): Promise<StdMessage> {
|
): Promise<StdMessage> {
|
||||||
|
@ -263,10 +291,19 @@ export class DiscordAdapter implements PlatformAdapter {
|
||||||
config: this.getUserById(discordMessage.author.id),
|
config: this.getUserById(discordMessage.author.id),
|
||||||
};
|
};
|
||||||
|
|
||||||
const attachments: Attachment[] = discordMessage.attachments.map(
|
const attachments: Attachment[] = await Promise.all(
|
||||||
(attachment) => ({
|
discordMessage.attachments.map(async (attachment) => {
|
||||||
url: attachment.url,
|
const stdAttachment: Attachment = {
|
||||||
contentType: attachment.contentType || undefined,
|
url: attachment.url,
|
||||||
|
contentType: attachment.contentType || undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const processedMedia = await this.handleMediaAttachment(stdAttachment);
|
||||||
|
if (processedMedia.base64) stdAttachment.base64 = processedMedia.base64;
|
||||||
|
if (processedMedia.transcription) stdAttachment.transcription = processedMedia.transcription;
|
||||||
|
stdAttachment.mediaType = processedMedia.mediaType;
|
||||||
|
|
||||||
|
return stdAttachment;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -402,7 +439,7 @@ export class DiscordAdapter implements PlatformAdapter {
|
||||||
// Helper method to safely send messages with length checks
|
// Helper method to safely send messages with length checks
|
||||||
private async safeSend(
|
private async safeSend(
|
||||||
target: TextChannel | DiscordUser,
|
target: TextChannel | DiscordUser,
|
||||||
messageData: string | { content?: string; [key: string]: any }
|
messageData: string | { content?: string;[key: string]: any }
|
||||||
): Promise<DiscordMessage> {
|
): Promise<DiscordMessage> {
|
||||||
let content: string | undefined;
|
let content: string | undefined;
|
||||||
if (typeof messageData === "string") {
|
if (typeof messageData === "string") {
|
||||||
|
@ -432,7 +469,7 @@ export class DiscordAdapter implements PlatformAdapter {
|
||||||
// Helper method to safely reply with length checks
|
// Helper method to safely reply with length checks
|
||||||
private async safeReply(
|
private async safeReply(
|
||||||
message: DiscordMessage,
|
message: DiscordMessage,
|
||||||
messageData: string | { content?: string; [key: string]: any }
|
messageData: string | { content?: string;[key: string]: any }
|
||||||
): Promise<DiscordMessage> {
|
): Promise<DiscordMessage> {
|
||||||
let content: string | undefined;
|
let content: string | undefined;
|
||||||
if (typeof messageData === "string") {
|
if (typeof messageData === "string") {
|
||||||
|
@ -456,7 +493,7 @@ export class DiscordAdapter implements PlatformAdapter {
|
||||||
// Helper method to safely edit messages with length checks
|
// Helper method to safely edit messages with length checks
|
||||||
private async safeEdit(
|
private async safeEdit(
|
||||||
message: DiscordMessage,
|
message: DiscordMessage,
|
||||||
data: string | { content?: string; [key: string]: any }
|
data: string | { content?: string;[key: string]: any }
|
||||||
): Promise<DiscordMessage> {
|
): Promise<DiscordMessage> {
|
||||||
let content: string | undefined;
|
let content: string | undefined;
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
|
|
|
@ -4,6 +4,18 @@ import { startEventsServer } from "./events";
|
||||||
import { Message } from "./message";
|
import { Message } from "./message";
|
||||||
import { WhatsAppAdapter } from "./whatsapp";
|
import { WhatsAppAdapter } from "./whatsapp";
|
||||||
|
|
||||||
|
// Add debounce utility function
|
||||||
|
function debounce<T extends (...args: any[]) => any>(
|
||||||
|
func: T,
|
||||||
|
wait: number
|
||||||
|
): (...args: Parameters<T>) => void {
|
||||||
|
let timeout: NodeJS.Timeout;
|
||||||
|
return (...args: Parameters<T>) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => func(...args), wait) as any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize Discord Adapter and Processor
|
// Initialize Discord Adapter and Processor
|
||||||
export const discordAdapter = new DiscordAdapter();
|
export const discordAdapter = new DiscordAdapter();
|
||||||
|
|
||||||
|
@ -17,9 +29,13 @@ export function startInterfaces() {
|
||||||
discordAdapter.onMessage(async (message) => {
|
discordAdapter.onMessage(async (message) => {
|
||||||
await discordProcessor.processMessage(message);
|
await discordProcessor.processMessage(message);
|
||||||
});
|
});
|
||||||
whatsappAdapter.onMessage(async (message) => {
|
|
||||||
|
// Debounce WhatsApp messages with 500ms delay
|
||||||
|
const debouncedWhatsAppProcessor = debounce(async (message) => {
|
||||||
await whatsappProcessor.processMessage(message);
|
await whatsappProcessor.processMessage(message);
|
||||||
});
|
}, 1000);
|
||||||
|
|
||||||
|
whatsappAdapter.onMessage(debouncedWhatsAppProcessor);
|
||||||
startEventsServer();
|
startEventsServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,9 @@ export interface Attachment {
|
||||||
contentType?: string;
|
contentType?: string;
|
||||||
data?: Buffer | string;
|
data?: Buffer | string;
|
||||||
type?: string;
|
type?: string;
|
||||||
|
mediaType?: 'image' | 'audio' | 'other';
|
||||||
|
base64?: string;
|
||||||
|
transcription?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Embed {
|
export interface Embed {
|
||||||
|
@ -31,10 +34,10 @@ export interface MessageData {
|
||||||
options?: any;
|
options?: any;
|
||||||
flags?: any;
|
flags?: any;
|
||||||
file?:
|
file?:
|
||||||
| {
|
| {
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
| { path: string };
|
| { path: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Message {
|
export interface Message {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { UserConfig } from "../config";
|
import { UserConfig } from "../config";
|
||||||
import { Message, User } from "./message";
|
import { Attachment, Message, User } from "./message";
|
||||||
|
|
||||||
export interface FetchOptions {
|
export interface FetchOptions {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
|
@ -25,4 +25,9 @@ export interface PlatformAdapter {
|
||||||
processing: boolean;
|
processing: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
handleMediaAttachment?(attachment: Attachment): Promise<{
|
||||||
|
base64?: string;
|
||||||
|
transcription?: string;
|
||||||
|
mediaType: 'image' | 'audio' | 'other';
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,20 +12,17 @@ import {
|
||||||
MessageMedia,
|
MessageMedia,
|
||||||
} from "whatsapp-web.js";
|
} from "whatsapp-web.js";
|
||||||
import { UserConfig, userConfigs } from "../config";
|
import { UserConfig, userConfigs } from "../config";
|
||||||
import { eventManager } from "./events";
|
// import { eventManager } from "./events";
|
||||||
import { return_current_listeners } from "../tools/events";
|
import { return_current_listeners } from "../tools/events";
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
|
import { get_transcription } from "../tools/ask"; // Add this import
|
||||||
// const allowedUsers = ["pooja", "raj"];
|
|
||||||
const allowedUsers: string[] = [];
|
|
||||||
|
|
||||||
export class WhatsAppAdapter implements PlatformAdapter {
|
export class WhatsAppAdapter implements PlatformAdapter {
|
||||||
private client: WAClient;
|
private client: WAClient;
|
||||||
private botUserId: string = "918884016724@c.us";
|
|
||||||
|
|
||||||
public config = {
|
public config = {
|
||||||
indicators: {
|
indicators: {
|
||||||
typing: false,
|
typing: true,
|
||||||
processing: false,
|
processing: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -39,6 +36,11 @@ export class WhatsAppAdapter implements PlatformAdapter {
|
||||||
console.log("WhatsApp Client is ready!");
|
console.log("WhatsApp Client is ready!");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.client.on("qr", (qr) => {
|
||||||
|
console.log("QR Code received. Please scan with WhatsApp:");
|
||||||
|
console.log(qr);
|
||||||
|
});
|
||||||
|
|
||||||
this.client.initialize();
|
this.client.initialize();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`Failed to initialize WhatsApp client: `, error);
|
console.log(`Failed to initialize WhatsApp client: `, error);
|
||||||
|
@ -62,6 +64,8 @@ export class WhatsAppAdapter implements PlatformAdapter {
|
||||||
|
|
||||||
public onMessage(callback: (message: StdMessage) => void): void {
|
public onMessage(callback: (message: StdMessage) => void): void {
|
||||||
this.client.on("message_create", async (waMessage: WAMessage) => {
|
this.client.on("message_create", async (waMessage: WAMessage) => {
|
||||||
|
|
||||||
|
|
||||||
// emit internal event only if text message and there is an active listener
|
// emit internal event only if text message and there is an active listener
|
||||||
const listeners = return_current_listeners();
|
const listeners = return_current_listeners();
|
||||||
if (
|
if (
|
||||||
|
@ -69,16 +73,16 @@ export class WhatsAppAdapter implements PlatformAdapter {
|
||||||
!waMessage.fromMe &&
|
!waMessage.fromMe &&
|
||||||
listeners.find((l) => l.eventId.includes("whatsapp"))
|
listeners.find((l) => l.eventId.includes("whatsapp"))
|
||||||
) {
|
) {
|
||||||
const contact = await this.client.getContactById(waMessage.from);
|
// const contact = await this.client.getContactById(waMessage.from);
|
||||||
eventManager.emit("got_whatsapp_message", {
|
// eventManager.emit("got_whatsapp_message", {
|
||||||
sender_id: waMessage.from,
|
// sender_id: waMessage.from,
|
||||||
sender_contact_name:
|
// sender_contact_name:
|
||||||
contact.name || contact.shortName || contact.pushname || "NA",
|
// contact.name || contact.shortName || contact.pushname || "NA",
|
||||||
timestamp: waMessage.timestamp,
|
// timestamp: waMessage.timestamp,
|
||||||
content: waMessage.body,
|
// content: waMessage.body,
|
||||||
profile_image_url: await contact.getProfilePicUrl(),
|
// profile_image_url: await contact.getProfilePicUrl(),
|
||||||
is_group_message: contact.isGroup.toString(),
|
// is_group_message: contact.isGroup.toString(),
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
// user must exist in userConfigs
|
// user must exist in userConfigs
|
||||||
|
@ -88,14 +92,15 @@ export class WhatsAppAdapter implements PlatformAdapter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// user must be in allowedUsers
|
// // user must be in allowedUsers
|
||||||
if (!allowedUsers.includes(usr.name)) {
|
// if (!allowedUsers.includes(usr.name)) {
|
||||||
// console.log(`User not allowed: ${usr.name}`);
|
// console.log(`User not allowed: ${usr.name}`, allowedUsers);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Ignore messages sent by the bot
|
// Ignore messages sent by the bot
|
||||||
if (waMessage.fromMe) return;
|
if (waMessage.fromMe) return;
|
||||||
|
|
||||||
const message = await this.convertMessage(waMessage);
|
const message = await this.convertMessage(waMessage);
|
||||||
|
|
||||||
callback(message);
|
callback(message);
|
||||||
|
@ -125,7 +130,7 @@ export class WhatsAppAdapter implements PlatformAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getBotId(): string {
|
public getBotId(): string {
|
||||||
return this.botUserId;
|
return this.client.info.wid.user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createMessageInterface(userId: string): Promise<StdMessage> {
|
public async createMessageInterface(userId: string): Promise<StdMessage> {
|
||||||
|
@ -280,6 +285,32 @@ export class WhatsAppAdapter implements PlatformAdapter {
|
||||||
// Expose this method so it can be accessed elsewhere
|
// Expose this method so it can be accessed elsewhere
|
||||||
public getMessageInterface = this.createMessageInterface;
|
public getMessageInterface = this.createMessageInterface;
|
||||||
|
|
||||||
|
public async handleMediaAttachment(attachment: Attachment) {
|
||||||
|
if (!attachment.data) return { mediaType: 'other' as const };
|
||||||
|
|
||||||
|
const buffer = Buffer.from(attachment.data as string, 'base64');
|
||||||
|
|
||||||
|
if (attachment.type?.includes('image')) {
|
||||||
|
const base64 = `data:${attachment.contentType};base64,${buffer.toString('base64')}`;
|
||||||
|
return {
|
||||||
|
base64,
|
||||||
|
mediaType: 'image' as const
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachment.type?.includes('audio')) {
|
||||||
|
// Create temporary file for transcription
|
||||||
|
const tempFile = new File([buffer], 'audio', { type: attachment.contentType });
|
||||||
|
const transcription = await get_transcription(tempFile);
|
||||||
|
return {
|
||||||
|
transcription,
|
||||||
|
mediaType: 'audio' as const
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { mediaType: 'other' as const };
|
||||||
|
}
|
||||||
|
|
||||||
private async convertMessage(waMessage: WAMessage): Promise<StdMessage> {
|
private async convertMessage(waMessage: WAMessage): Promise<StdMessage> {
|
||||||
const contact = await waMessage.getContact();
|
const contact = await waMessage.getContact();
|
||||||
|
|
||||||
|
@ -292,14 +323,25 @@ export class WhatsAppAdapter implements PlatformAdapter {
|
||||||
// Convert attachments
|
// Convert attachments
|
||||||
let attachments: Attachment[] = [];
|
let attachments: Attachment[] = [];
|
||||||
if (waMessage.hasMedia) {
|
if (waMessage.hasMedia) {
|
||||||
|
console.log("Downloading media...");
|
||||||
const media = await waMessage.downloadMedia();
|
const media = await waMessage.downloadMedia();
|
||||||
|
|
||||||
attachments.push({
|
const attachment: Attachment = {
|
||||||
url: "", // WhatsApp does not provide a direct URL to the media
|
url: "",
|
||||||
data: media.data,
|
data: media.data,
|
||||||
contentType: media.mimetype,
|
contentType: media.mimetype,
|
||||||
type: waMessage.type,
|
type: waMessage.type
|
||||||
});
|
};
|
||||||
|
|
||||||
|
console.log("Processing media attachment...");
|
||||||
|
|
||||||
|
const processedMedia = await this.handleMediaAttachment(attachment);
|
||||||
|
console.log("Processed media attachment:", processedMedia.transcription);
|
||||||
|
if (processedMedia.base64) attachment.base64 = processedMedia.base64;
|
||||||
|
if (processedMedia.transcription) attachment.transcription = processedMedia.transcription;
|
||||||
|
attachment.mediaType = processedMedia.mediaType;
|
||||||
|
|
||||||
|
attachments.push(attachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
const stdMessage: StdMessage = {
|
const stdMessage: StdMessage = {
|
||||||
|
@ -358,7 +400,7 @@ export class WhatsAppAdapter implements PlatformAdapter {
|
||||||
user.identities.some(
|
user.identities.some(
|
||||||
(identity) =>
|
(identity) =>
|
||||||
identity.platform === "whatsapp" &&
|
identity.platform === "whatsapp" &&
|
||||||
identity.id === contact.id._serialized
|
identity.id === contact.id.user
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return userConfig ? userConfig.roles : ["user"];
|
return userConfig ? userConfig.roles : ["user"];
|
||||||
|
@ -400,7 +442,11 @@ export class WhatsAppAdapter implements PlatformAdapter {
|
||||||
sendTyping: async () => {
|
sendTyping: async () => {
|
||||||
// WhatsApp Web API does not support sending typing indicators directly
|
// WhatsApp Web API does not support sending typing indicators directly
|
||||||
// You may leave this as a no-op
|
// You may leave this as a no-op
|
||||||
|
const chat = await this.client.getChatById(waMessage.from)
|
||||||
|
await chat.sendStateTyping()
|
||||||
|
|
||||||
},
|
},
|
||||||
|
attachments,
|
||||||
};
|
};
|
||||||
|
|
||||||
return stdMessage;
|
return stdMessage;
|
||||||
|
@ -540,6 +586,8 @@ export class WhatsAppAdapter implements PlatformAdapter {
|
||||||
sendTyping: async () => {
|
sendTyping: async () => {
|
||||||
// WhatsApp Web API does not support sending typing indicators directly
|
// WhatsApp Web API does not support sending typing indicators directly
|
||||||
// You may leave this as a no-op
|
// You may leave this as a no-op
|
||||||
|
const chat = await this.client.getChatById(sentWAMessage.from)
|
||||||
|
await chat.sendStateTyping()
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,9 @@ import { memory_manager_guide, memory_manager_init } from "./memory-manager";
|
||||||
// Paths to the JSON files
|
// Paths to the JSON files
|
||||||
const ACTIONS_FILE_PATH = pathInDataDir("actions.json");
|
const ACTIONS_FILE_PATH = pathInDataDir("actions.json");
|
||||||
|
|
||||||
|
// Add this constant at the top with other constants
|
||||||
|
const MIN_SCHEDULE_INTERVAL_SECONDS = 600; // 10 minutes in seconds
|
||||||
|
|
||||||
// Define schema for creating an action
|
// Define schema for creating an action
|
||||||
export const CreateActionParams = z.object({
|
export const CreateActionParams = z.object({
|
||||||
actionId: z
|
actionId: z
|
||||||
|
@ -347,6 +350,13 @@ export async function create_action(
|
||||||
let { actionId, description, schedule, instruction, tool_names } =
|
let { actionId, description, schedule, instruction, tool_names } =
|
||||||
parsed.data;
|
parsed.data;
|
||||||
|
|
||||||
|
// Validate schedule frequency
|
||||||
|
if (!validateScheduleFrequency(schedule)) {
|
||||||
|
return {
|
||||||
|
error: "❌ Schedule frequency cannot be less than 10 minutes. Please adjust the schedule."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Get the userId from contextMessage
|
// Get the userId from contextMessage
|
||||||
const userId: string = contextMessage.author.id;
|
const userId: string = contextMessage.author.id;
|
||||||
|
|
||||||
|
@ -484,6 +494,13 @@ export async function update_action(
|
||||||
notify,
|
notify,
|
||||||
} = parsed.data;
|
} = parsed.data;
|
||||||
|
|
||||||
|
// Validate schedule frequency
|
||||||
|
if (!validateScheduleFrequency(schedule)) {
|
||||||
|
return {
|
||||||
|
error: "❌ Schedule frequency cannot be less than 10 minutes. Please adjust the schedule."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Get the userId from contextMessage
|
// Get the userId from contextMessage
|
||||||
const userId: string = contextMessage.author.id;
|
const userId: string = contextMessage.author.id;
|
||||||
|
|
||||||
|
@ -534,16 +551,25 @@ const action_tools: (
|
||||||
schema: CreateActionParams,
|
schema: CreateActionParams,
|
||||||
description: `Creates a new action.
|
description: `Creates a new action.
|
||||||
|
|
||||||
|
**IMPORTANT SCHEDULING LIMITATION:**
|
||||||
|
Actions CANNOT be scheduled more frequently than once every 10 minutes. This is a hard system limitation that cannot be overridden.
|
||||||
|
- For delays: Minimum delay is 600 seconds (10 minutes)
|
||||||
|
- For cron: Must have at least 10 minutes between executions
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
- **User:** "Send a summary email in 10 minutes"
|
- **User:** "Send a summary email every 10 minutes"
|
||||||
- **Action ID:** "send_summary_email"
|
- **Action ID:** "send_summary_email"
|
||||||
- **Description:** "Sends a summary email after a delay."
|
- **Description:** "Sends a summary email periodically"
|
||||||
- **Schedule:** { type: "delay", time: 600 }
|
- **Schedule:** { type: "cron", time: "*/10 * * * *" }
|
||||||
- **Instruction:** "Compose and send a summary email to the user."
|
- **Instruction:** "Compose and send a summary email to the user."
|
||||||
- **Required Tools:** ["email_service"]
|
- **Required Tools:** ["email_service"]
|
||||||
|
|
||||||
**Notes:**
|
**Invalid Examples:**
|
||||||
- Supported scheduling types: 'delay' (in seconds), 'cron' (cron expressions).
|
❌ Every 5 minutes: "*/5 * * * *"
|
||||||
|
❌ Delay of 300 seconds
|
||||||
|
❌ Multiple times within 10 minutes
|
||||||
|
|
||||||
|
The system will automatically reject any schedule that attempts to run more frequently than every 10 minutes.
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
// zodFunction({
|
// zodFunction({
|
||||||
|
@ -710,5 +736,41 @@ Use the data provided above to fulfill the user's request.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace the existing validateCronFrequency function with this more comprehensive one
|
||||||
|
function validateScheduleFrequency(schedule: { type: "delay" | "cron"; time: number | string }): boolean {
|
||||||
|
try {
|
||||||
|
if (schedule.type === "delay") {
|
||||||
|
const delaySeconds = schedule.time as number;
|
||||||
|
return delaySeconds >= MIN_SCHEDULE_INTERVAL_SECONDS;
|
||||||
|
} else if (schedule.type === "cron") {
|
||||||
|
const cronExpression = schedule.time as string;
|
||||||
|
const intervals = cronExpression.split(' ');
|
||||||
|
|
||||||
|
// Check minutes field (first position)
|
||||||
|
const minutesPart = intervals[0];
|
||||||
|
if (minutesPart === '*') return false;
|
||||||
|
if (minutesPart.includes('/')) {
|
||||||
|
const step = parseInt(minutesPart.split('/')[1]);
|
||||||
|
if (step < 10) return false;
|
||||||
|
}
|
||||||
|
// Convert specific minute values to ensure they're at least 10 minutes apart
|
||||||
|
if (!minutesPart.includes('/')) {
|
||||||
|
const minutes = minutesPart.split(',').map(Number);
|
||||||
|
if (minutes.length > 1) {
|
||||||
|
minutes.sort((a, b) => a - b);
|
||||||
|
for (let i = 1; i < minutes.length; i++) {
|
||||||
|
if (minutes[i] - minutes[i - 1] < 10) return false;
|
||||||
|
}
|
||||||
|
if ((60 - minutes[minutes.length - 1] + minutes[0]) < 10) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize by loading actions from file when the module is loaded
|
// Initialize by loading actions from file when the module is loaded
|
||||||
loadActionsFromFile();
|
loadActionsFromFile();
|
||||||
|
|
68
tools/ask.ts
68
tools/ask.ts
|
@ -133,12 +133,12 @@ export async function ask({
|
||||||
tools,
|
tools,
|
||||||
seed,
|
seed,
|
||||||
json,
|
json,
|
||||||
image_url,
|
image_urls, // Changed from image_url to image_urls array
|
||||||
}: {
|
}: {
|
||||||
model?: string;
|
model?: string;
|
||||||
prompt: string;
|
prompt: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
image_url?: string;
|
image_urls?: string[]; // Changed to array of strings
|
||||||
name?: string;
|
name?: string;
|
||||||
tools?: RunnableToolFunctionWithParse<any>[];
|
tools?: RunnableToolFunctionWithParse<any>[];
|
||||||
seed?: string;
|
seed?: string;
|
||||||
|
@ -166,6 +166,22 @@ export async function ask({
|
||||||
// Retrieve existing message history
|
// Retrieve existing message history
|
||||||
const history = getMessageHistory(seed);
|
const history = getMessageHistory(seed);
|
||||||
|
|
||||||
|
let messageContent: any = message;
|
||||||
|
if (image_urls && image_urls.length > 0) {
|
||||||
|
messageContent = [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: message,
|
||||||
|
},
|
||||||
|
...image_urls.map((url) => ({
|
||||||
|
type: "image_url",
|
||||||
|
image_url: {
|
||||||
|
url: url,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
// Combine system prompt with message history and new user message
|
// Combine system prompt with message history and new user message
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
|
@ -175,24 +191,11 @@ export async function ask({
|
||||||
...history,
|
...history,
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
content: image_url
|
content: messageContent,
|
||||||
? [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: message,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "image_url",
|
|
||||||
image_url: {
|
|
||||||
url: image_url,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: message,
|
|
||||||
name,
|
name,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
image_url && console.log("got image:", image_url?.slice(0, 20));
|
image_urls && console.log("got images:", image_urls.length);
|
||||||
} else if (seed && !message) {
|
} else if (seed && !message) {
|
||||||
// If seed is provided but no new message, just retrieve history
|
// If seed is provided but no new message, just retrieve history
|
||||||
const history = getMessageHistory(seed);
|
const history = getMessageHistory(seed);
|
||||||
|
@ -205,22 +208,25 @@ export async function ask({
|
||||||
];
|
];
|
||||||
} else if (!seed && message) {
|
} else if (!seed && message) {
|
||||||
// If no seed but message is provided, send system prompt and user message without history
|
// If no seed but message is provided, send system prompt and user message without history
|
||||||
|
let messageContent: any = message;
|
||||||
|
if (image_urls && image_urls.length > 0) {
|
||||||
|
messageContent = [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: message,
|
||||||
|
},
|
||||||
|
...image_urls.map((url) => ({
|
||||||
|
type: "image_url",
|
||||||
|
image_url: {
|
||||||
|
url: url,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
messages.push({
|
messages.push({
|
||||||
role: "user",
|
role: "user",
|
||||||
content: image_url
|
content: messageContent,
|
||||||
? [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: message,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "image_url",
|
|
||||||
image_url: {
|
|
||||||
url: image_url,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: message,
|
|
||||||
name,
|
name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,13 +17,13 @@ const CommunicationManagerSchema = z.object({
|
||||||
.describe(
|
.describe(
|
||||||
"The platform you prefer to use, you can leave this empty to default to the current user's platform."
|
"The platform you prefer to use, you can leave this empty to default to the current user's platform."
|
||||||
),
|
),
|
||||||
prefered_recipient_details: z
|
// prefered_recipient_details: z
|
||||||
.object({
|
// .object({
|
||||||
name: z.string().optional(),
|
// name: z.string().optional(),
|
||||||
user_id: z.string().optional(),
|
// user_id: z.string().optional(),
|
||||||
})
|
// })
|
||||||
.optional()
|
// .optional()
|
||||||
.describe("Give these details only if you have them."),
|
// .describe("Give these details only if you have them."),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type CommunicationManager = z.infer<typeof CommunicationManagerSchema>;
|
export type CommunicationManager = z.infer<typeof CommunicationManagerSchema>;
|
||||||
|
@ -70,7 +70,7 @@ export async function communication_manager(
|
||||||
{
|
{
|
||||||
request,
|
request,
|
||||||
prefered_platform,
|
prefered_platform,
|
||||||
prefered_recipient_details,
|
// prefered_recipient_details,
|
||||||
}: CommunicationManager,
|
}: CommunicationManager,
|
||||||
context_message: Message
|
context_message: Message
|
||||||
) {
|
) {
|
||||||
|
@ -78,27 +78,61 @@ export async function communication_manager(
|
||||||
memory_manager_init(context_message, "communications_manager")
|
memory_manager_init(context_message, "communications_manager")
|
||||||
);
|
);
|
||||||
|
|
||||||
const prompt = `You are a Communication Manager Tool.
|
const prompt = `You are a Communication Manager Tool responsible for routing messages to the correct recipients.
|
||||||
|
|
||||||
Your task is to route messages to the correct recipient.
|
CONTEXT INFORMATION:
|
||||||
|
1. Current User (Sender): ${context_message.author.config?.name}
|
||||||
|
2. Current Platform: ${context_message.platform}
|
||||||
|
3. WhatsApp Access: ${context_message.getUserRoles().includes("creator")}
|
||||||
|
4. Available Platforms: discord, whatsapp, email
|
||||||
|
|
||||||
---
|
STEP-BY-STEP PROCESS:
|
||||||
|
1. First, identify the recipient(s) from the request
|
||||||
|
2. Then, check if recipient exists in this list of known users:
|
||||||
|
${JSON.stringify(userConfigs, null, 2)}
|
||||||
|
|
||||||
|
3. If recipient not found in above list:
|
||||||
|
- Use search_user tool to find them
|
||||||
|
- Wait for search results before proceeding
|
||||||
|
|
||||||
|
4. Platform Selection:
|
||||||
|
- If prefered_platform is specified, use that
|
||||||
|
- If not specified, use current platform: ${context_message.platform}
|
||||||
|
- For WhatsApp, verify you have creator access first
|
||||||
|
|
||||||
|
TOOLS AVAILABLE:
|
||||||
|
- search_user: Find user details by name
|
||||||
|
- send_message_to: Send message on discord/whatsapp
|
||||||
|
- send_email: Send emails (requires verified email address)
|
||||||
|
- memory_manager: Store user preferences and contact names
|
||||||
|
|
||||||
${memory_manager_guide("communications_manager", context_message.author.id)}
|
${memory_manager_guide("communications_manager", context_message.author.id)}
|
||||||
|
|
||||||
You can use the \`memory_manager\` tool to remember user preferences, such as what the user calls certain contacts, to help you route messages better.
|
MESSAGE DELIVERY GUIDELINES:
|
||||||
|
Act as a professional assistant delivering messages between people. Consider:
|
||||||
|
|
||||||
---
|
1. Relationship Context:
|
||||||
|
- Professional for workplace communications
|
||||||
|
- Casual for friends and family
|
||||||
|
- Respectful for all contexts
|
||||||
|
|
||||||
**Default Platform (if not mentioned):** ${context_message.platform}
|
2. Message Delivery Style:
|
||||||
|
- Frame the message naturally as an assistant would when passing along information
|
||||||
|
- Maintain the original intent and tone of the sender
|
||||||
|
- Add appropriate context without changing the core message
|
||||||
|
|
||||||
**Configuration of All Users:** ${JSON.stringify(userConfigs)}
|
3. Natural Communication:
|
||||||
|
- Deliver messages as if you're the assistant of the user: ${context_message.author.config?.name}.
|
||||||
|
- Adapt your tone based on the message urgency and importance
|
||||||
|
- Include relevant context when delivering reminders or requests
|
||||||
|
- Keep the human element in the communication
|
||||||
|
|
||||||
**Can Access 'WhatsApp':** ${context_message.getUserRoles().includes("creator")}
|
Remember: You're not just forwarding messages, you're acting as a professional assistant helping facilitate communication between people. Make your delivery natural and appropriate for each situation.
|
||||||
|
|
||||||
**Guidelines:**
|
ERROR PREVENTION:
|
||||||
|
- Don't halucinate or invent contact details
|
||||||
- If the user does not mention a platform, use the same platform as the current user.
|
- Always verify platform availability before sending
|
||||||
|
- If unsure about recipient, ask for clarification
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const response = await ask({
|
const response = await ask({
|
||||||
|
@ -106,9 +140,7 @@ You can use the \`memory_manager\` tool to remember user preferences, such as wh
|
||||||
model: "gpt-4o-mini",
|
model: "gpt-4o-mini",
|
||||||
message: `request: ${request}
|
message: `request: ${request}
|
||||||
|
|
||||||
prefered_platform: ${prefered_platform}
|
prefered_platform: ${prefered_platform}`,
|
||||||
|
|
||||||
prefered_recipient_details: ${JSON.stringify(prefered_recipient_details)}`,
|
|
||||||
tools,
|
tools,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -128,13 +160,11 @@ export const communication_manager_tool = (context_message: Message) =>
|
||||||
function: (args) => communication_manager(args, context_message),
|
function: (args) => communication_manager(args, context_message),
|
||||||
name: "communication_manager",
|
name: "communication_manager",
|
||||||
schema: CommunicationManagerSchema,
|
schema: CommunicationManagerSchema,
|
||||||
description: `Communications Manager.
|
description: `Sends messages to one or more recipients across different platforms (discord, whatsapp, email).
|
||||||
|
|
||||||
This tool routes messages to the specified user on the appropriate platform.
|
Input format:
|
||||||
|
request: "send [message] to [recipient(s)]"
|
||||||
|
prefered_platform: (optional) platform name
|
||||||
|
|
||||||
Use it to send messages to users on various platforms.
|
The tool handles recipient lookup, message composition, and delivery automatically.`,
|
||||||
|
|
||||||
Provide detailed information to ensure the message reaches the correct recipient.
|
|
||||||
|
|
||||||
Include in your request the full message content and its context along with the recipient's details.`,
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,10 +13,24 @@ export interface PromptAugmentationResult {
|
||||||
updatedSystemPrompt?: string;
|
updatedSystemPrompt?: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
updatedTools?: RunnableToolFunctionWithParse<any>[];
|
updatedTools?: RunnableToolFunctionWithParse<any>[];
|
||||||
attachedImageBase64?: string;
|
attachedImagesBase64?: string[]; // Changed to string array
|
||||||
model: string;
|
model: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to handle transcription of a single file or array of files
|
||||||
|
*/
|
||||||
|
async function handleTranscription(
|
||||||
|
input: File | File[]
|
||||||
|
): Promise<string | string[]> {
|
||||||
|
if (Array.isArray(input)) {
|
||||||
|
return Promise.all(
|
||||||
|
input.map((file) => get_transcription(file as globalThis.File))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return get_transcription(input as globalThis.File);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1) Voice Event Augmentation
|
* 1) Voice Event Augmentation
|
||||||
* - Possibly do transcription if an audio File is present.
|
* - Possibly do transcription if an audio File is present.
|
||||||
|
@ -28,28 +42,104 @@ async function voiceEventAugmentation(
|
||||||
baseTools: RunnableToolFunctionWithParse<any>[] | undefined,
|
baseTools: RunnableToolFunctionWithParse<any>[] | undefined,
|
||||||
contextMessage: Message
|
contextMessage: Message
|
||||||
): Promise<PromptAugmentationResult> {
|
): Promise<PromptAugmentationResult> {
|
||||||
let attachedImageBase64: string | undefined;
|
let attachedImagesBase64: string[] = []; // Changed to array
|
||||||
|
|
||||||
// Transcribe if there's an audio file
|
// Handle transcription - single file or array
|
||||||
if (payload?.transcription && payload.transcription instanceof File) {
|
if (payload?.transcription) {
|
||||||
console.log("Transcribing audio for voice event listener.");
|
if (
|
||||||
const file = payload.transcription;
|
payload.transcription instanceof File ||
|
||||||
payload.transcription = await get_transcription(file as globalThis.File);
|
Array.isArray(payload.transcription)
|
||||||
|
) {
|
||||||
|
console.log("Transcribing audio(s) for voice event listener.");
|
||||||
|
payload.transcription = await handleTranscription(payload.transcription);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for an attached image
|
// Handle other_reference_data - single file or array
|
||||||
const otherContextData = payload?.other_reference_data;
|
const otherContextData = payload?.other_reference_data;
|
||||||
if (
|
if (otherContextData) {
|
||||||
otherContextData instanceof File &&
|
if (Array.isArray(otherContextData)) {
|
||||||
otherContextData.type.includes("image")
|
const results = await Promise.all(
|
||||||
) {
|
otherContextData.map(async (item) => {
|
||||||
console.log("Got image in voice event payload; converting to base64...");
|
if (item instanceof File) {
|
||||||
const buffer = await otherContextData.arrayBuffer();
|
if (item.type.includes("audio")) {
|
||||||
attachedImageBase64 = `data:${otherContextData.type};base64,${Buffer.from(
|
return await get_transcription(item as globalThis.File);
|
||||||
buffer
|
} else if (item.type.includes("image")) {
|
||||||
).toString("base64")}`;
|
const buffer = await item.arrayBuffer();
|
||||||
|
const imageBase64 = `data:${item.type};base64,${Buffer.from(
|
||||||
|
buffer
|
||||||
|
).toString("base64")}`;
|
||||||
|
attachedImagesBase64.push(imageBase64); // Add to array
|
||||||
|
return imageBase64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
payload.other_reference_data = results;
|
||||||
|
} else if (otherContextData instanceof File) {
|
||||||
|
if (otherContextData.type.includes("audio")) {
|
||||||
|
payload.other_reference_data = await get_transcription(
|
||||||
|
otherContextData as globalThis.File
|
||||||
|
);
|
||||||
|
} else if (otherContextData.type.includes("image")) {
|
||||||
|
const buffer = await otherContextData.arrayBuffer();
|
||||||
|
const imageBase64 = `data:${otherContextData.type};base64,${Buffer.from(
|
||||||
|
buffer
|
||||||
|
).toString("base64")}`;
|
||||||
|
attachedImagesBase64.push(imageBase64); // Add to array
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const files = payload?.files;
|
||||||
|
if (files) {
|
||||||
|
if (Array.isArray(files)) {
|
||||||
|
const results = await Promise.all(
|
||||||
|
files.map(async (file) => {
|
||||||
|
if (file instanceof File) {
|
||||||
|
if (file.type.includes("audio")) {
|
||||||
|
const res = await get_transcription(file as globalThis.File);
|
||||||
|
return `transcript of file: ${file.name}:
|
||||||
|
|
||||||
|
${res}
|
||||||
|
`
|
||||||
|
} else if (file.type.includes("image")) {
|
||||||
|
const buffer = await file.arrayBuffer();
|
||||||
|
const imageBase64 = `data:${file.type};base64,${Buffer.from(
|
||||||
|
buffer
|
||||||
|
).toString("base64")}`;
|
||||||
|
attachedImagesBase64.push(imageBase64); // Add to array
|
||||||
|
return `image file: ${file.name}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
payload.files = results;
|
||||||
|
} else if (files instanceof File) {
|
||||||
|
if (files.type.includes("audio")) {
|
||||||
|
// payload.files = await get_transcription(files as globalThis.File);
|
||||||
|
const res = await get_transcription(files as globalThis.File);
|
||||||
|
payload.files = `transcript of file: ${files.name}:
|
||||||
|
|
||||||
|
${res}
|
||||||
|
`;
|
||||||
|
|
||||||
|
} else if (files.type.includes("image")) {
|
||||||
|
const buffer = await files.arrayBuffer();
|
||||||
|
const imageBase64 = `data:${files.type};base64,${Buffer.from(
|
||||||
|
buffer
|
||||||
|
).toString("base64")}`;
|
||||||
|
attachedImagesBase64.push(imageBase64); // Add to array
|
||||||
|
payload.files = `image file: ${files.name}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log("Payload: ", payload);
|
||||||
|
// console.log("IMages: ", attachedImagesBase64);
|
||||||
|
|
||||||
let message = `
|
let message = `
|
||||||
You are in voice trigger mode.
|
You are in voice trigger mode.
|
||||||
|
|
||||||
|
@ -72,7 +162,7 @@ Your response must be in plain text without extra formatting or Markdown.
|
||||||
updatedSystemPrompt: prompt,
|
updatedSystemPrompt: prompt,
|
||||||
message,
|
message,
|
||||||
updatedTools: tools,
|
updatedTools: tools,
|
||||||
attachedImageBase64,
|
attachedImagesBase64, // Now returning array
|
||||||
model: "gpt-4o",
|
model: "gpt-4o",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -147,7 +237,7 @@ async function defaultAugmentation(
|
||||||
): Promise<PromptAugmentationResult> {
|
): Promise<PromptAugmentationResult> {
|
||||||
return {
|
return {
|
||||||
updatedTools: baseTools,
|
updatedTools: baseTools,
|
||||||
attachedImageBase64: undefined,
|
attachedImagesBase64: [], // Changed to empty array
|
||||||
model: "gpt-4o-mini",
|
model: "gpt-4o-mini",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -187,7 +277,7 @@ export async function buildPromptAndToolsForEvent(
|
||||||
finalPrompt: string;
|
finalPrompt: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
finalTools: RunnableToolFunctionWithParse<any>[] | undefined;
|
finalTools: RunnableToolFunctionWithParse<any>[] | undefined;
|
||||||
attachedImage?: string;
|
attachedImages?: string[];
|
||||||
model?: string;
|
model?: string;
|
||||||
}> {
|
}> {
|
||||||
console.log(`Building prompt for event: ${eventId}`);
|
console.log(`Building prompt for event: ${eventId}`);
|
||||||
|
@ -227,11 +317,10 @@ You are called when an event triggers. Your task is to execute the user's instru
|
||||||
- **Event ID:** ${eventId}
|
- **Event ID:** ${eventId}
|
||||||
- **Description:** ${description}
|
- **Description:** ${description}
|
||||||
- **Payload:** ${JSON.stringify(payload, null, 2)}
|
- **Payload:** ${JSON.stringify(payload, null, 2)}
|
||||||
- **Will Auto Notify Creator of Listener:** ${
|
- **Will Auto Notify Creator of Listener:** ${notify
|
||||||
notify
|
|
||||||
? "Yes, no need to send it yourself"
|
? "Yes, no need to send it yourself"
|
||||||
: "No, you need to notify the user manually"
|
: "No, you need to notify the user manually"
|
||||||
}
|
}
|
||||||
- **Instruction:** ${instruction}
|
- **Instruction:** ${instruction}
|
||||||
|
|
||||||
**Action Required:**
|
**Action Required:**
|
||||||
|
@ -257,7 +346,7 @@ You are called when an event triggers. Your task is to execute the user's instru
|
||||||
const {
|
const {
|
||||||
additionalSystemPrompt,
|
additionalSystemPrompt,
|
||||||
updatedTools,
|
updatedTools,
|
||||||
attachedImageBase64,
|
attachedImagesBase64,
|
||||||
updatedSystemPrompt,
|
updatedSystemPrompt,
|
||||||
model,
|
model,
|
||||||
message,
|
message,
|
||||||
|
@ -271,7 +360,7 @@ You are called when an event triggers. Your task is to execute the user's instru
|
||||||
return {
|
return {
|
||||||
finalPrompt: updatedSystemPrompt || finalPrompt,
|
finalPrompt: updatedSystemPrompt || finalPrompt,
|
||||||
finalTools: updatedTools,
|
finalTools: updatedTools,
|
||||||
attachedImage: attachedImageBase64,
|
attachedImages: attachedImagesBase64,
|
||||||
model,
|
model,
|
||||||
message,
|
message,
|
||||||
};
|
};
|
||||||
|
|
|
@ -434,7 +434,7 @@ function registerListener(listener: EventListener) {
|
||||||
|
|
||||||
console.time("buildPromptAndToolsForEvent");
|
console.time("buildPromptAndToolsForEvent");
|
||||||
// Now call the helper from the new file
|
// Now call the helper from the new file
|
||||||
const { finalPrompt, finalTools, attachedImage, model, message } =
|
const { finalPrompt, finalTools, attachedImages, model, message } =
|
||||||
await buildPromptAndToolsForEvent(
|
await buildPromptAndToolsForEvent(
|
||||||
eventId,
|
eventId,
|
||||||
description,
|
description,
|
||||||
|
@ -457,7 +457,7 @@ function registerListener(listener: EventListener) {
|
||||||
model: model,
|
model: model,
|
||||||
message,
|
message,
|
||||||
prompt: finalPrompt,
|
prompt: finalPrompt,
|
||||||
image_url: attachedImage, // If there's an attached image base64
|
image_urls: attachedImages, // If there's an attached image base64
|
||||||
seed: `${eventId}-${listener.id}`,
|
seed: `${eventId}-${listener.id}`,
|
||||||
tools: finalTools,
|
tools: finalTools,
|
||||||
});
|
});
|
||||||
|
|
584
tools/index.ts
584
tools/index.ts
|
@ -69,7 +69,7 @@ import { linear_manager_tool } from "./linear-manager";
|
||||||
// get time function
|
// get time function
|
||||||
const GetTimeParams = z.object({});
|
const GetTimeParams = z.object({});
|
||||||
type GetTimeParams = z.infer<typeof GetTimeParams>;
|
type GetTimeParams = z.infer<typeof GetTimeParams>;
|
||||||
async function get_date_time({}: GetTimeParams) {
|
async function get_date_time({ }: GetTimeParams) {
|
||||||
return { response: new Date().toLocaleString() };
|
return { response: new Date().toLocaleString() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ async function run_bash_command({ command }: RunBashCommandParams) {
|
||||||
// exit process
|
// exit process
|
||||||
const ExitProcessParams = z.object({});
|
const ExitProcessParams = z.object({});
|
||||||
type ExitProcessParams = z.infer<typeof ExitProcessParams>;
|
type ExitProcessParams = z.infer<typeof ExitProcessParams>;
|
||||||
async function restart_self({}: ExitProcessParams, context_message: Message) {
|
async function restart_self({ }: ExitProcessParams, context_message: Message) {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
send_sys_log("Restarting myself"),
|
send_sys_log("Restarting myself"),
|
||||||
context_message.send({
|
context_message.send({
|
||||||
|
@ -148,6 +148,8 @@ export function getTools(
|
||||||
) {
|
) {
|
||||||
const userRoles = context_message.getUserRoles();
|
const userRoles = context_message.getUserRoles();
|
||||||
|
|
||||||
|
console.log("User roles: ", userRoles);
|
||||||
|
|
||||||
// Aggregate permissions from all roles
|
// Aggregate permissions from all roles
|
||||||
const userPermissions = new Set<string>();
|
const userPermissions = new Set<string>();
|
||||||
userRoles.forEach((role) => {
|
userRoles.forEach((role) => {
|
||||||
|
@ -170,254 +172,254 @@ export function getTools(
|
||||||
name: string;
|
name: string;
|
||||||
tool: RunnableToolFunction<any> | RunnableToolFunction<any>[];
|
tool: RunnableToolFunction<any> | RunnableToolFunction<any>[];
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
name: "calculator",
|
name: "calculator",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: calculator,
|
function: calculator,
|
||||||
schema: CalculatorParams,
|
schema: CalculatorParams,
|
||||||
description: "Evaluate math expression",
|
description: "Evaluate math expression",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "getTime",
|
name: "getTime",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: get_date_time,
|
function: get_date_time,
|
||||||
schema: GetTimeParams,
|
schema: GetTimeParams,
|
||||||
description: "Get current date and time",
|
description: "Get current date and time",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "search_user_ids",
|
|
||||||
tool: zodFunction({
|
|
||||||
function: (args) => search_user(args, context_message),
|
|
||||||
name: "search_user_ids",
|
name: "search_user_ids",
|
||||||
schema: SearchUserParams,
|
tool: zodFunction({
|
||||||
description: `Search and get user's details. Use this only when required.`,
|
function: (args) => search_user(args, context_message),
|
||||||
}),
|
name: "search_user_ids",
|
||||||
},
|
schema: SearchUserParams,
|
||||||
|
description: `Search and get user's details. Use this only when required.`,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
// Computer nerd tools
|
// Computer nerd tools
|
||||||
|
|
||||||
// {
|
// {
|
||||||
// name: "search_whatsapp_contacts",
|
// name: "search_whatsapp_contacts",
|
||||||
// tool: zodFunction({
|
// tool: zodFunction({
|
||||||
// function: search_whatsapp_contacts,
|
// function: search_whatsapp_contacts,
|
||||||
// schema: SearchContactsParams,
|
// schema: SearchContactsParams,
|
||||||
// description: `Search for contacts in user's whatsapp account. Use this to get whatsapp user_id of any user.
|
// description: `Search for contacts in user's whatsapp account. Use this to get whatsapp user_id of any user.
|
||||||
// Note: Confirm from the user before sending any messages to the contacts found using this search.
|
// Note: Confirm from the user before sending any messages to the contacts found using this search.
|
||||||
// `,
|
// `,
|
||||||
// }),
|
// }),
|
||||||
// },
|
// },
|
||||||
/* {
|
/* {
|
||||||
name: "scrapeWeb",
|
name: "scrapeWeb",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: scrape_and_convert_to_markdown,
|
function: scrape_and_convert_to_markdown,
|
||||||
schema: ScrapeAndConvertToMarkdownParams,
|
schema: ScrapeAndConvertToMarkdownParams,
|
||||||
name: "scrape_web",
|
name: "scrape_web",
|
||||||
description: `Get data from a webpage.`,
|
description: `Get data from a webpage.`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "uploadFile",
|
name: "uploadFile",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: upload_file,
|
function: upload_file,
|
||||||
schema: UploadFileParams,
|
schema: UploadFileParams,
|
||||||
description: `Upload a LOCAL file to a MinIO bucket and return its public URL.
|
description: `Upload a LOCAL file to a MinIO bucket and return its public URL.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
- The filePath should be a local file path in the /tmp directory.
|
- The filePath should be a local file path in the /tmp directory.
|
||||||
- If you want to re-upload a file from the internet, you can download it using run_shell_command to a /tmp directory and then upload it.
|
- If you want to re-upload a file from the internet, you can download it using run_shell_command to a /tmp directory and then upload it.
|
||||||
|
|
||||||
Use cases:
|
Use cases:
|
||||||
- You can use this to share files from inside the code interpreter using the /tmp file path.
|
- You can use this to share files from inside the code interpreter using the /tmp file path.
|
||||||
- You can use this to share files that only you have access to, like temporary files or discord files.
|
- You can use this to share files that only you have access to, like temporary files or discord files.
|
||||||
- You can use this when the user explicitly asks for a file to be shared with them or wants to download a file.`,
|
- You can use this when the user explicitly asks for a file to be shared with them or wants to download a file.`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "getFileList",
|
name: "getFileList",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: get_file_list,
|
function: get_file_list,
|
||||||
schema: GetFileListParams,
|
schema: GetFileListParams,
|
||||||
description: `Get the list of public URLs for all files in the MinIO bucket`,
|
description: `Get the list of public URLs for all files in the MinIO bucket`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "getYouTubeVideoData",
|
name: "getYouTubeVideoData",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: get_youtube_video_data,
|
function: get_youtube_video_data,
|
||||||
schema: YoutubeTranscriptParams as any,
|
schema: YoutubeTranscriptParams as any,
|
||||||
description:
|
description:
|
||||||
"Get YouTube video data. Use this only when sent a YouTube URL. Do not use this for YouTube search.",
|
"Get YouTube video data. Use this only when sent a YouTube URL. Do not use this for YouTube search.",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "getDownloadLink",
|
name: "getDownloadLink",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: get_download_link as any,
|
function: get_download_link as any,
|
||||||
schema: YoutubeDownloaderParams,
|
schema: YoutubeDownloaderParams,
|
||||||
description: `Get download link for YouTube links.
|
description: `Get download link for YouTube links.
|
||||||
Also, always hide the length of links that are too long by formatting them with markdown.
|
Also, always hide the length of links that are too long by formatting them with markdown.
|
||||||
For any site other than YouTube, use code interpreter to scrape the download link.
|
For any site other than YouTube, use code interpreter to scrape the download link.
|
||||||
|
|
||||||
If the user wants the file and not just the link:
|
If the user wants the file and not just the link:
|
||||||
You can use the direct link you get from this to download the media inside code interpreter and then share the downloaded files using the send message tool.
|
You can use the direct link you get from this to download the media inside code interpreter and then share the downloaded files using the send message tool.
|
||||||
Make sure that the file size is within discord limits.`,
|
Make sure that the file size is within discord limits.`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "codeInterpreter",
|
name: "codeInterpreter",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: (args) => code_interpreter(args, context_message),
|
function: (args) => code_interpreter(args, context_message),
|
||||||
name: "code_interpreter",
|
name: "code_interpreter",
|
||||||
schema: PythonCodeParams,
|
schema: PythonCodeParams,
|
||||||
description: `Primary Function: Run Python code in an isolated environment.
|
description: `Primary Function: Run Python code in an isolated environment.
|
||||||
Key Libraries: pandas for data analysis, matplotlib for visualization.
|
Key Libraries: pandas for data analysis, matplotlib for visualization.
|
||||||
Use Cases: Data analysis, plotting, image/video processing using ffmpeg for video, complex calculations, and attachment analysis.
|
Use Cases: Data analysis, plotting, image/video processing using ffmpeg for video, complex calculations, and attachment analysis.
|
||||||
You can also use this to try to scrape and get download links from non-YouTube sites.
|
You can also use this to try to scrape and get download links from non-YouTube sites.
|
||||||
|
|
||||||
File sharing:
|
File sharing:
|
||||||
To share a file with a user from inside code interpreter, you can save the file to the /tmp/ directory and then use the send message tool to send the file to the user by using the full path of the file, including the /tmp part in the path.
|
To share a file with a user from inside code interpreter, you can save the file to the /tmp/ directory and then use the send message tool to send the file to the user by using the full path of the file, including the /tmp part in the path.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
Import necessary libraries; retry if issues arise.
|
Import necessary libraries; retry if issues arise.
|
||||||
For web scraping, process data to stay within a 10,000 token limit.
|
For web scraping, process data to stay within a 10,000 token limit.
|
||||||
Use run_shell_command to check or install dependencies.
|
Use run_shell_command to check or install dependencies.
|
||||||
Try to fix any errors that are returned at least once before sending to the user, especially syntax/type errors.`,
|
Try to fix any errors that are returned at least once before sending to the user, especially syntax/type errors.`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "runShellCommand",
|
name: "runShellCommand",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: (args) =>
|
function: (args) =>
|
||||||
run_command_in_code_interpreter_env(args, context_message),
|
run_command_in_code_interpreter_env(args, context_message),
|
||||||
name: "run_shell_command",
|
name: "run_shell_command",
|
||||||
schema: RunPythonCommandParams,
|
schema: RunPythonCommandParams,
|
||||||
description: `Run bash command. Use this to install any needed dependencies.`,
|
description: `Run bash command. Use this to install any needed dependencies.`,
|
||||||
}),
|
}),
|
||||||
}, */
|
}, */
|
||||||
|
|
||||||
// {
|
// {
|
||||||
// name: "generateChart",
|
// name: "generateChart",
|
||||||
// tool: zodFunction({
|
// tool: zodFunction({
|
||||||
// function: chart,
|
// function: chart,
|
||||||
// name: "generate_chart",
|
// name: "generate_chart",
|
||||||
// schema: ChartParams,
|
// schema: ChartParams,
|
||||||
// description: `Generate chart PNG image URL using quickchart.io`,
|
// description: `Generate chart PNG image URL using quickchart.io`,
|
||||||
// }),
|
// }),
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// name: "memeOrCatMaker",
|
// name: "memeOrCatMaker",
|
||||||
// tool: zodFunction({
|
// tool: zodFunction({
|
||||||
// function: meme_maker,
|
// function: meme_maker,
|
||||||
// name: "meme_or_cat_maker",
|
// name: "meme_or_cat_maker",
|
||||||
// schema: MemeMakerParams,
|
// schema: MemeMakerParams,
|
||||||
// description: `Generate meme image URL using memegen.link OR generate cat image URL using cataas.com
|
// description: `Generate meme image URL using memegen.link OR generate cat image URL using cataas.com
|
||||||
|
|
||||||
// Just provide the info in the query, and it will generate the URL for you.
|
// Just provide the info in the query, and it will generate the URL for you.
|
||||||
// This can include any memegen.link or cataas.com specific parameters.
|
// This can include any memegen.link or cataas.com specific parameters.
|
||||||
// Make sure to give as many details as you can about what the user wants.
|
// Make sure to give as many details as you can about what the user wants.
|
||||||
// Also, make sure to send the images and memes as files to the user using the send message tool unless explicitly asked to send the URL.`,
|
// Also, make sure to send the images and memes as files to the user using the send message tool unless explicitly asked to send the URL.`,
|
||||||
// }),
|
// }),
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// name: "sendMessageToChannel",
|
// name: "sendMessageToChannel",
|
||||||
// tool: zodFunction({
|
// tool: zodFunction({
|
||||||
// function: (args) => send_general_message(args, context_message),
|
// function: (args) => send_general_message(args, context_message),
|
||||||
// name: "send_message_to_channel",
|
// name: "send_message_to_channel",
|
||||||
// schema: SendGeneralMessageParams,
|
// schema: SendGeneralMessageParams,
|
||||||
// description: `Send message to the current Discord channel.
|
// description: `Send message to the current Discord channel.
|
||||||
// You can also use this for reminders or other scheduled messages by calculating the delay from the current time.
|
// You can also use this for reminders or other scheduled messages by calculating the delay from the current time.
|
||||||
// If the user does not specify a time for a reminder, think of one based on the task.
|
// If the user does not specify a time for a reminder, think of one based on the task.
|
||||||
// If no channel ID is provided, the message will be sent to the user you are currently chatting with.`,
|
// If no channel ID is provided, the message will be sent to the user you are currently chatting with.`,
|
||||||
// }),
|
// }),
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// name: "searchChat",
|
// name: "searchChat",
|
||||||
// tool: zodFunction({
|
// tool: zodFunction({
|
||||||
// function: (args) => search_chat(args, context_message),
|
// function: (args) => search_chat(args, context_message),
|
||||||
// name: "search_chat",
|
// name: "search_chat",
|
||||||
// schema: SearchChatParams,
|
// schema: SearchChatParams,
|
||||||
// description: `Search for messages in the current channel based on query parameters.
|
// description: `Search for messages in the current channel based on query parameters.
|
||||||
// This will search the last 100 (configurable by setting the limit parameter) messages in the channel.
|
// This will search the last 100 (configurable by setting the limit parameter) messages in the channel.
|
||||||
// Set user_only parameter to true if you want to search only the user's messages.`,
|
// Set user_only parameter to true if you want to search only the user's messages.`,
|
||||||
// }),
|
// }),
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
name: "serviceChecker",
|
name: "serviceChecker",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: service_checker,
|
function: service_checker,
|
||||||
name: "service_checker",
|
name: "service_checker",
|
||||||
schema: ServiceCheckerParams,
|
schema: ServiceCheckerParams,
|
||||||
description: `Check the status of a service by querying the status page of the service. Use this when the user asks if something is up or down in the context of a service.`,
|
description: `Check the status of a service by querying the status page of the service. Use this when the user asks if something is up or down in the context of a service.`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// name: "getTotalTokens",
|
// name: "getTotalTokens",
|
||||||
// tool: zodFunction({
|
// tool: zodFunction({
|
||||||
// function: get_total_tokens,
|
// function: get_total_tokens,
|
||||||
// name: "get_total_tokens",
|
// name: "get_total_tokens",
|
||||||
// schema: GetTotalTokensParams,
|
// schema: GetTotalTokensParams,
|
||||||
// description: `Get total tokens used by a model in a date range
|
// description: `Get total tokens used by a model in a date range
|
||||||
|
|
||||||
// The pricing as of 2024 is:
|
// The pricing as of 2024 is:
|
||||||
// gpt-4o:
|
// gpt-4o:
|
||||||
// $5.00 / 1M prompt tokens
|
// $5.00 / 1M prompt tokens
|
||||||
// $15.00 / 1M completion tokens
|
// $15.00 / 1M completion tokens
|
||||||
|
|
||||||
// gpt-4o-mini:
|
// gpt-4o-mini:
|
||||||
// $0.150 / 1M prompt tokens
|
// $0.150 / 1M prompt tokens
|
||||||
// $0.600 / 1M completion tokens
|
// $0.600 / 1M completion tokens
|
||||||
|
|
||||||
// Use calculator to make the math calculations.`,
|
// Use calculator to make the math calculations.`,
|
||||||
// }),
|
// }),
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
name: "communicationsManagerTool",
|
name: "communicationsManagerTool",
|
||||||
tool: communication_manager_tool(context_message),
|
tool: communication_manager_tool(context_message),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "calendarManagerTool",
|
name: "calendarManagerTool",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: (args) => calendarManager(args, context_message),
|
function: (args) => calendarManager(args, context_message),
|
||||||
name: "calendar_manager",
|
name: "calendar_manager",
|
||||||
schema: CalendarManagerParams,
|
schema: CalendarManagerParams,
|
||||||
description: `Manage calendar events using user's Calendar.
|
description: `Manage calendar events using user's Calendar.
|
||||||
You can just forward the user's request to this tool and it will handle the rest.`,
|
You can just forward the user's request to this tool and it will handle the rest.`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "remindersManagerTools",
|
name: "remindersManagerTools",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: (args) => remindersManager(args, context_message),
|
function: (args) => remindersManager(args, context_message),
|
||||||
name: "reminders_manager",
|
name: "reminders_manager",
|
||||||
schema: RemindersManagerParams,
|
schema: RemindersManagerParams,
|
||||||
description: `Manage reminders using user's reminders.
|
description: `Manage reminders using user's reminders.
|
||||||
You can just forward the user's request to this tool and it will handle the rest.
|
You can just forward the user's request to this tool and it will handle the rest.
|
||||||
|
|
||||||
More detailed todos that dont need user notification will be managed by the notes manager tool instead.
|
More detailed todos that dont need user notification will be managed by the notes manager tool instead.
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "homeAssistantManagerTool",
|
name: "homeAssistantManagerTool",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: (args) => homeManager(args, context_message),
|
function: (args) => homeManager(args, context_message),
|
||||||
name: "home_assistant_manager",
|
name: "home_assistant_manager",
|
||||||
schema: HomeManagerParams,
|
schema: HomeManagerParams,
|
||||||
description: `Manage home assistant devices and services in natural language.
|
description: `Manage home assistant devices and services in natural language.
|
||||||
Give as much details as possible to get the best results.
|
Give as much details as possible to get the best results.
|
||||||
Especially what devices that the user named and what action they want to perform on them.
|
Especially what devices that the user named and what action they want to perform on them.
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "notesManagerTool",
|
name: "notesManagerTool",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: (args) => notesManager(args, context_message),
|
function: (args) => notesManager(args, context_message),
|
||||||
name: "notes_manager",
|
name: "notes_manager",
|
||||||
schema: NotesManagerParams,
|
schema: NotesManagerParams,
|
||||||
description: `Manage notes using user's notes.
|
description: `Manage notes using user's notes.
|
||||||
|
|
||||||
You can just forward the user's request verbatim (or by adding more clarity) to this tool and it will handle the rest.
|
You can just forward the user's request verbatim (or by adding more clarity) to this tool and it will handle the rest.
|
||||||
|
|
||||||
|
@ -425,57 +427,57 @@ Try to fix any errors that are returned at least once before sending to the user
|
||||||
if user talks about any notes, lists, journal, gym entry, standup, personal journal, etc.
|
if user talks about any notes, lists, journal, gym entry, standup, personal journal, etc.
|
||||||
You can also use this for advanced todos that are more planning related. (these are not reminders, and will not notify the user)
|
You can also use this for advanced todos that are more planning related. (these are not reminders, and will not notify the user)
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "linkManagerTool",
|
name: "linkManagerTool",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: (args) => linkManager(args, context_message),
|
function: (args) => linkManager(args, context_message),
|
||||||
name: "link_manager",
|
name: "link_manager",
|
||||||
schema: LinkManagerParams,
|
schema: LinkManagerParams,
|
||||||
description: `Manage links using LinkWarden.
|
description: `Manage links using LinkWarden.
|
||||||
You can just forward the user's request to this tool and it will handle the rest.
|
You can just forward the user's request to this tool and it will handle the rest.
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ProjectManager",
|
name: "ProjectManager",
|
||||||
tool: linear_manager_tool(context_message),
|
tool: linear_manager_tool(context_message),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "actionsManagerTool",
|
name: "actionsManagerTool",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: (args) => actionManager(args, context_message),
|
function: (args) => actionManager(args, context_message),
|
||||||
name: "actions_manager",
|
name: "actions_manager",
|
||||||
schema: ActionManagerParamsSchema,
|
schema: ActionManagerParamsSchema,
|
||||||
description: `Manage scheduled actions using the Actions Manager.
|
description: `Manage scheduled actions using the Actions Manager.
|
||||||
|
|
||||||
Forward user requests to create, update, retrieve, or remove actions.
|
Forward user requests to create, update, retrieve, or remove actions.
|
||||||
|
|
||||||
You can use this for when a user wants you to do something at a specific time or after a specific time.`,
|
You can use this for when a user wants you to do something at a specific time or after a specific time.`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "eventsManagerTool",
|
name: "eventsManagerTool",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: (args) => event_manager(args, context_message),
|
function: (args) => event_manager(args, context_message),
|
||||||
name: "events_manager",
|
name: "events_manager",
|
||||||
schema: EventManagerSchema,
|
schema: EventManagerSchema,
|
||||||
description: `Manage events using the Events Manager.
|
description: `Manage events using the Events Manager.
|
||||||
|
|
||||||
Forward user requests to create, update, retrieve, or remove events.
|
Forward user requests to create, update, retrieve, or remove events.
|
||||||
|
|
||||||
When to use:
|
When to use:
|
||||||
if user wants to create some automation based on some event.`,
|
if user wants to create some automation based on some event.`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "softwareEngineerManagerTool",
|
name: "softwareEngineerManagerTool",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: (args) => dockerToolManager(args, context_message),
|
function: (args) => dockerToolManager(args, context_message),
|
||||||
name: "software_engineer_manager",
|
name: "software_engineer_manager",
|
||||||
schema: DockerToolManagerSchema,
|
schema: DockerToolManagerSchema,
|
||||||
description: `Software Engineer Manager Tool.
|
description: `Software Engineer Manager Tool.
|
||||||
His name is Cody. He is a software engineer, and someone who loves technology.
|
His name is Cody. He is a software engineer, and someone who loves technology.
|
||||||
He specializes in linux and devops.
|
He specializes in linux and devops.
|
||||||
|
|
||||||
|
@ -485,28 +487,28 @@ This manager is like a whole other user that you are talking to.
|
||||||
|
|
||||||
When talking to this manager, you can inform the user that you asked cody for this query etc.
|
When talking to this manager, you can inform the user that you asked cody for this query etc.
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "restart",
|
name: "restart",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
function: (args) => restart_self(args, context_message),
|
function: (args) => restart_self(args, context_message),
|
||||||
name: "restart_self",
|
name: "restart_self",
|
||||||
schema: ExitProcessParams,
|
schema: ExitProcessParams,
|
||||||
description:
|
description:
|
||||||
"Restart yourself. do this only when the user explicitly asks you to restart yourself.",
|
"Restart yourself. do this only when the user explicitly asks you to restart yourself.",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// name: "eventTools",
|
// name: "eventTools",
|
||||||
// tool: event_tools(context_message),
|
// tool: event_tools(context_message),
|
||||||
// },
|
// },
|
||||||
// Period tools
|
// Period tools
|
||||||
{
|
{
|
||||||
name: "periodTools",
|
name: "periodTools",
|
||||||
tool: getPeriodTools(context_message),
|
tool: getPeriodTools(context_message),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const manager_tools = manager_id
|
const manager_tools = manager_id
|
||||||
? [memory_manager_init(context_message, manager_id)]
|
? [memory_manager_init(context_message, manager_id)]
|
||||||
|
|
|
@ -13,80 +13,59 @@ export const IssueParams = z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
assigneeId: z.string().optional(),
|
assigneeId: z.string().optional(),
|
||||||
|
projectId: z.string().optional(),
|
||||||
priority: z.number().optional(),
|
priority: z.number().optional(),
|
||||||
labelIds: z.array(z.string()).optional(),
|
labelIds: z.array(z.string()).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UpdateIssueParams = z.object({
|
export const UpdateIssueParams = z.object({
|
||||||
issueId: z.string().describe("The ID of the issue to update"),
|
issueId: z.string().describe("The ID of the issue to update"),
|
||||||
|
// Basic fields
|
||||||
title: z.string().optional().describe("The issue title"),
|
title: z.string().optional().describe("The issue title"),
|
||||||
description: z
|
description: z.string().optional().describe("The issue description in markdown format"),
|
||||||
.string()
|
descriptionData: z.any().optional().describe("The issue description as a Prosemirror document"),
|
||||||
.optional()
|
priority: z.number().min(0).max(4).optional()
|
||||||
.describe("The issue description in markdown format"),
|
.describe("The priority of the issue. 0 = No priority, 1 = Urgent, 2 = High, 3 = Normal, 4 = Low"),
|
||||||
stateId: z.string().optional().describe("The team state/status of the issue"),
|
|
||||||
assigneeId: z
|
// Assignee and subscribers
|
||||||
.string()
|
assigneeId: z.string().optional().describe("The identifier of the user to assign the issue to"),
|
||||||
.optional()
|
subscriberIds: z.array(z.string()).optional().describe("The identifiers of the users subscribing to this ticket"),
|
||||||
.describe("The identifier of the user to assign the issue to"),
|
|
||||||
priority: z
|
// Labels
|
||||||
.number()
|
labelIds: z.array(z.string()).optional()
|
||||||
.min(0)
|
.describe("The complete set of label IDs to set on the issue (replaces existing labels)"),
|
||||||
.max(4)
|
addedLabelIds: z.array(z.string()).optional()
|
||||||
.optional()
|
|
||||||
.describe(
|
|
||||||
"The priority of the issue. 0 = No priority, 1 = Urgent, 2 = High, 3 = Normal, 4 = Low"
|
|
||||||
),
|
|
||||||
addedLabelIds: z
|
|
||||||
.array(z.string())
|
|
||||||
.optional()
|
|
||||||
.describe("The identifiers of the issue labels to be added to this issue"),
|
.describe("The identifiers of the issue labels to be added to this issue"),
|
||||||
removedLabelIds: z
|
removedLabelIds: z.array(z.string()).optional()
|
||||||
.array(z.string())
|
.describe("The identifiers of the issue labels to be removed from this issue"),
|
||||||
.optional()
|
|
||||||
.describe(
|
// Status and workflow
|
||||||
"The identifiers of the issue labels to be removed from this issue"
|
stateId: z.string().optional().describe("The team state of the issue"),
|
||||||
),
|
estimate: z.number().optional().describe("The estimated complexity of the issue"),
|
||||||
labelIds: z
|
|
||||||
.array(z.string())
|
// Dates and scheduling
|
||||||
.optional()
|
dueDate: z.string().optional().describe("The date at which the issue is due (YYYY-MM-DD format)"),
|
||||||
.describe(
|
snoozedById: z.string().optional().describe("The identifier of the user who snoozed the issue"),
|
||||||
"The complete set of label IDs to set on the issue (replaces existing labels)"
|
snoozedUntilAt: z.string().optional().describe("The time until an issue will be snoozed in Triage view"),
|
||||||
),
|
|
||||||
autoClosedByParentClosing: z
|
// Relationships
|
||||||
.boolean()
|
parentId: z.string().optional().describe("The identifier of the parent issue"),
|
||||||
.optional()
|
projectId: z.string().optional().describe("The project associated with the issue"),
|
||||||
.describe(
|
projectMilestoneId: z.string().optional().describe("The project milestone associated with the issue"),
|
||||||
"Whether the issue was automatically closed because its parent issue was closed"
|
teamId: z.string().optional().describe("The identifier of the team associated with the issue"),
|
||||||
),
|
cycleId: z.string().optional().describe("The cycle associated with the issue"),
|
||||||
boardOrder: z
|
|
||||||
.number()
|
// Sorting and positioning
|
||||||
.optional()
|
sortOrder: z.number().optional().describe("The position of the issue related to other issues"),
|
||||||
.describe("The position of the issue in its column on the board view"),
|
boardOrder: z.number().optional().describe("The position of the issue in its column on the board view"),
|
||||||
dueDate: z
|
subIssueSortOrder: z.number().optional().describe("The position of the issue in parent's sub-issue list"),
|
||||||
.string()
|
prioritySortOrder: z.number().optional().describe("[ALPHA] The position of the issue when ordered by priority"),
|
||||||
.optional()
|
|
||||||
.describe("The date at which the issue is due (TimelessDate format)"),
|
// Templates and automation
|
||||||
parentId: z
|
lastAppliedTemplateId: z.string().optional().describe("The ID of the last template applied to the issue"),
|
||||||
.string()
|
autoClosedByParentClosing: z.boolean().optional()
|
||||||
.optional()
|
.describe("Whether the issue was automatically closed because its parent issue was closed"),
|
||||||
.describe("The identifier of the parent issue"),
|
trashed: z.boolean().optional().describe("Whether the issue has been trashed"),
|
||||||
projectId: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe("The project associated with the issue"),
|
|
||||||
sortOrder: z
|
|
||||||
.number()
|
|
||||||
.optional()
|
|
||||||
.describe("The position of the issue related to other issues"),
|
|
||||||
subIssueSortOrder: z
|
|
||||||
.number()
|
|
||||||
.optional()
|
|
||||||
.describe("The position of the issue in parent's sub-issue list"),
|
|
||||||
teamId: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe("The identifier of the team associated with the issue"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GetIssueParams = z.object({
|
export const GetIssueParams = z.object({
|
||||||
|
@ -103,31 +82,134 @@ export const ListTeamsParams = z.object({
|
||||||
limit: z.number().max(20).describe("Number of teams to return (default 3)"),
|
limit: z.number().max(20).describe("Number of teams to return (default 3)"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AdvancedSearchIssuesParams = z.object({
|
// Add these type definitions before the parameter schemas
|
||||||
query: z.string().optional(),
|
export const StringComparator = z.object({
|
||||||
teamId: z.string().optional(),
|
eq: z.string().optional(),
|
||||||
assigneeId: z.string().optional(),
|
neq: z.string().optional(),
|
||||||
status: z
|
in: z.array(z.string()).optional(),
|
||||||
.enum(["backlog", "todo", "in_progress", "done", "canceled"])
|
nin: z.array(z.string()).optional(),
|
||||||
.optional(),
|
contains: z.string().optional(),
|
||||||
priority: z.number().min(0).max(4).optional(),
|
notContains: z.string().optional(),
|
||||||
orderBy: z
|
startsWith: z.string().optional(),
|
||||||
.enum(["createdAt", "updatedAt"])
|
notStartsWith: z.string().optional(),
|
||||||
.optional()
|
endsWith: z.string().optional(),
|
||||||
.describe("Order by, defaults to updatedAt"),
|
notEndsWith: z.string().optional(),
|
||||||
limit: z
|
containsIgnoreCase: z.string().optional(),
|
||||||
.number()
|
notContainsIgnoreCase: z.string().optional(),
|
||||||
.max(10)
|
startsWithIgnoreCase: z.string().optional(),
|
||||||
.describe("Number of results to return (default: 5)"),
|
notStartsWithIgnoreCase: z.string().optional(),
|
||||||
|
endsWithIgnoreCase: z.string().optional(),
|
||||||
|
notEndsWithIgnoreCase: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const DateComparator = z.object({
|
||||||
|
eq: z.string().optional(),
|
||||||
|
neq: z.string().optional(),
|
||||||
|
gt: z.string().optional(),
|
||||||
|
gte: z.string().optional(),
|
||||||
|
lt: z.string().optional(),
|
||||||
|
lte: z.string().optional(),
|
||||||
|
in: z.array(z.string()).optional(),
|
||||||
|
nin: z.array(z.string()).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const NumberComparator = z.object({
|
||||||
|
eq: z.number().optional(),
|
||||||
|
neq: z.number().optional(),
|
||||||
|
gt: z.number().optional(),
|
||||||
|
gte: z.number().optional(),
|
||||||
|
lt: z.number().optional(),
|
||||||
|
lte: z.number().optional(),
|
||||||
|
in: z.array(z.number()).optional(),
|
||||||
|
nin: z.array(z.number()).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const IdComparator = z.object({
|
||||||
|
eq: z.string().optional(),
|
||||||
|
neq: z.string().optional(),
|
||||||
|
in: z.array(z.string()).optional(),
|
||||||
|
nin: z.array(z.string()).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const WorkflowStateFilter = z.object({
|
||||||
|
createdAt: DateComparator.optional(),
|
||||||
|
description: StringComparator.optional(),
|
||||||
|
id: IdComparator.optional(),
|
||||||
|
name: StringComparator.optional(),
|
||||||
|
position: NumberComparator.optional(),
|
||||||
|
type: StringComparator.optional(),
|
||||||
|
updatedAt: DateComparator.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AdvancedSearchIssuesParams = z.object({
|
||||||
|
// Text search
|
||||||
|
query: z.string().optional().describe("Search in title and description"),
|
||||||
|
title: z.string().optional().describe("Filter by exact or partial title match"),
|
||||||
|
description: z.string().optional().describe("Filter by description content"),
|
||||||
|
|
||||||
|
// Basic filters
|
||||||
|
teamId: z.string().optional().describe("Filter by team ID"),
|
||||||
|
assigneeId: z.string().optional().describe("Filter by assignee user ID"),
|
||||||
|
creatorId: z.string().optional().describe("Filter by creator user ID"),
|
||||||
|
priority: z.number().min(0).max(4).optional()
|
||||||
|
.describe("0 = No priority, 1 = Urgent, 2 = High, 3 = Normal, 4 = Low"),
|
||||||
|
|
||||||
|
// Status and state
|
||||||
|
stateId: z.string().optional().describe("Filter by specific workflow state ID (simplified)"),
|
||||||
|
|
||||||
|
// Dates
|
||||||
|
createdAfter: z.string().optional().describe("Issues created after this ISO datetime"),
|
||||||
|
createdBefore: z.string().optional().describe("Issues created before this ISO datetime"),
|
||||||
|
updatedAfter: z.string().optional().describe("Issues updated after this ISO datetime"),
|
||||||
|
updatedBefore: z.string().optional().describe("Issues updated before this ISO datetime"),
|
||||||
|
completedAfter: z.string().optional().describe("Issues completed after this ISO datetime"),
|
||||||
|
completedBefore: z.string().optional().describe("Issues completed before this ISO datetime"),
|
||||||
|
dueDate: z.string().optional().describe("Filter by due date (YYYY-MM-DD format)"),
|
||||||
|
dueDateAfter: z.string().optional().describe("Due date after (YYYY-MM-DD format)"),
|
||||||
|
dueDateBefore: z.string().optional().describe("Due date before (YYYY-MM-DD format)"),
|
||||||
|
startedAfter: z.string().optional().describe("Issues started after this ISO datetime"),
|
||||||
|
startedBefore: z.string().optional().describe("Issues started before this ISO datetime"),
|
||||||
|
|
||||||
|
// Relationships
|
||||||
|
projectId: z.string().optional().describe("Filter by project ID"),
|
||||||
|
parentId: z.string().optional().describe("Filter by parent issue ID"),
|
||||||
|
subscriberId: z.string().optional().describe("Filter by subscriber user ID"),
|
||||||
|
hasBlockedBy: z.boolean().optional().describe("Issues that are blocked by others"),
|
||||||
|
hasBlocking: z.boolean().optional().describe("Issues that are blocking others"),
|
||||||
|
hasDuplicates: z.boolean().optional().describe("Issues that have duplicates"),
|
||||||
|
|
||||||
|
// Labels and estimates
|
||||||
|
labelIds: z.array(z.string()).optional().describe("Filter by one or more label IDs"),
|
||||||
|
estimate: z.number().optional().describe("Filter by issue estimate points"),
|
||||||
|
|
||||||
|
// Other filters
|
||||||
|
number: z.number().optional().describe("Filter by issue number"),
|
||||||
|
snoozedById: z.string().optional().describe("Filter by user who snoozed the issue"),
|
||||||
|
snoozedUntilAfter: z.string().optional().describe("Issues snoozed until after this ISO datetime"),
|
||||||
|
snoozedUntilBefore: z.string().optional().describe("Issues snoozed until before this ISO datetime"),
|
||||||
|
|
||||||
|
// Result options
|
||||||
|
orderBy: z.enum(["createdAt", "updatedAt", "priority", "dueDate"])
|
||||||
|
.optional()
|
||||||
|
.describe("Sort order for results"),
|
||||||
|
limit: z.number().max(10)
|
||||||
|
.describe("Number of results to return (default: 2, max: 10)"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Modify SearchUsersParams schema to allow more specific search parameters
|
||||||
export const SearchUsersParams = z.object({
|
export const SearchUsersParams = z.object({
|
||||||
query: z.string().describe("Search query for user names"),
|
email: z.string().optional().describe("Search by exact email address"),
|
||||||
|
displayName: z.string().optional().describe("Search by display name"),
|
||||||
limit: z
|
limit: z
|
||||||
.number()
|
.number()
|
||||||
.max(10)
|
.max(10)
|
||||||
.describe("Number of results to return (default: 5)"),
|
.describe("Number of results to return (default: 5)"),
|
||||||
});
|
}).refine(
|
||||||
|
data => (data.email && !data.displayName) || (!data.email && data.displayName),
|
||||||
|
{
|
||||||
|
message: "Provide either email OR displayName, not both"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Add new Project Parameter Schemas
|
// Add new Project Parameter Schemas
|
||||||
export const ProjectParams = z.object({
|
export const ProjectParams = z.object({
|
||||||
|
@ -207,9 +289,45 @@ export const GetProjectParams = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SearchProjectsParams = z.object({
|
export const SearchProjectsParams = z.object({
|
||||||
query: z.string().describe("Search query string"),
|
// Text search
|
||||||
teamId: z.string().optional(),
|
query: z.string().optional().describe("Search in project name and content"),
|
||||||
limit: z.number().max(5).describe("Number of results to return (default: 1)"),
|
name: z.string().optional().describe("Filter by exact or partial project name"),
|
||||||
|
|
||||||
|
// Basic filters
|
||||||
|
teamId: z.string().optional().describe("Filter by team ID"),
|
||||||
|
creatorId: z.string().optional().describe("Filter by creator user ID"),
|
||||||
|
leadId: z.string().optional().describe("Filter by lead user ID"),
|
||||||
|
priority: z.number().min(0).max(4).optional()
|
||||||
|
.describe("0 = No priority, 1 = Urgent, 2 = High, 3 = Normal, 4 = Low"),
|
||||||
|
|
||||||
|
// Status and state
|
||||||
|
health: z.string().optional().describe("Filter by project health status"),
|
||||||
|
state: z.string().optional().describe("[DEPRECATED] Filter by project state"),
|
||||||
|
status: z.string().optional().describe("Filter by project status ID"),
|
||||||
|
|
||||||
|
// Dates
|
||||||
|
startDate: z.string().optional().describe("Filter by start date"),
|
||||||
|
targetDate: z.string().optional().describe("Filter by target date"),
|
||||||
|
createdAfter: z.string().optional().describe("Projects created after this ISO datetime"),
|
||||||
|
createdBefore: z.string().optional().describe("Projects created before this ISO datetime"),
|
||||||
|
updatedAfter: z.string().optional().describe("Projects updated after this ISO datetime"),
|
||||||
|
updatedBefore: z.string().optional().describe("Projects updated before this ISO datetime"),
|
||||||
|
completedAfter: z.string().optional().describe("Projects completed after this ISO datetime"),
|
||||||
|
completedBefore: z.string().optional().describe("Projects completed before this ISO datetime"),
|
||||||
|
canceledAfter: z.string().optional().describe("Projects canceled after this ISO datetime"),
|
||||||
|
canceledBefore: z.string().optional().describe("Projects canceled before this ISO datetime"),
|
||||||
|
|
||||||
|
// Relationships
|
||||||
|
hasBlockedBy: z.boolean().optional().describe("Projects that are blocked by others"),
|
||||||
|
hasBlocking: z.boolean().optional().describe("Projects that are blocking others"),
|
||||||
|
hasRelated: z.boolean().optional().describe("Projects that have related items"),
|
||||||
|
hasViolatedDependencies: z.boolean().optional().describe("Projects with violated dependencies"),
|
||||||
|
|
||||||
|
// Result options
|
||||||
|
orderBy: z.enum(["createdAt", "updatedAt", "priority", "targetDate"])
|
||||||
|
.optional()
|
||||||
|
.describe("Sort order for results"),
|
||||||
|
limit: z.number().max(10).describe("Number of results to return (default: 1)"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add new ListProjectsParams schema after other params
|
// Add new ListProjectsParams schema after other params
|
||||||
|
@ -225,10 +343,49 @@ export const ListProjectsParams = z.object({
|
||||||
.describe("Filter projects by state"),
|
.describe("Filter projects by state"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add after other parameter schemas
|
||||||
|
export const CreateCommentParams = z.object({
|
||||||
|
issueId: z.string().describe("The ID of the issue to comment on"),
|
||||||
|
body: z.string().describe("The comment text in markdown format"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ListCommentsParams = z.object({
|
||||||
|
issueId: z.string().describe("The ID of the issue to get comments from"),
|
||||||
|
limit: z.number().max(20).describe("Number of comments to return (default: 10)"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add new document parameter schemas
|
||||||
|
export const CreateDocumentParams = z.object({
|
||||||
|
title: z.string().describe("The title of the document"),
|
||||||
|
content: z.string().describe("The content of the document in markdown format"),
|
||||||
|
icon: z.string().optional().describe("The icon of the document"),
|
||||||
|
organizationId: z.string().optional().describe("The organization ID"),
|
||||||
|
projectId: z.string().optional().describe("The project ID to link the document to"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateDocumentParams = z.object({
|
||||||
|
documentId: z.string().describe("The ID of the document to update"),
|
||||||
|
title: z.string().optional().describe("The new title of the document"),
|
||||||
|
content: z.string().optional().describe("The new content in markdown format"),
|
||||||
|
icon: z.string().optional().describe("The new icon of the document"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetDocumentParams = z.object({
|
||||||
|
documentId: z.string().describe("The ID of the document to retrieve"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SearchDocumentsParams = z.object({
|
||||||
|
query: z.string().describe("Search query string"),
|
||||||
|
projectId: z.string().optional().describe("Filter by project ID"),
|
||||||
|
limit: z.number().max(10).describe("Number of results to return (default: 5)"),
|
||||||
|
});
|
||||||
|
|
||||||
interface SimpleIssue {
|
interface SimpleIssue {
|
||||||
id: string;
|
id: string; // The internal UUID of the issue (e.g., "123e4567-e89b-12d3-a456-426614174000")
|
||||||
|
identifier: string; // The human-readable identifier (e.g., "XCE-205")
|
||||||
title: string;
|
title: string;
|
||||||
status: string;
|
status: string;
|
||||||
|
statusId: string;
|
||||||
priority: number;
|
priority: number;
|
||||||
assignee?: string;
|
assignee?: string;
|
||||||
dueDate?: string;
|
dueDate?: string;
|
||||||
|
@ -265,15 +422,42 @@ interface SimpleProject {
|
||||||
statusId?: string;
|
statusId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add new document interface
|
||||||
|
interface SimpleDocument {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
icon?: string;
|
||||||
|
url: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add after other interfaces
|
||||||
|
interface SimpleComment {
|
||||||
|
id: string;
|
||||||
|
body: string;
|
||||||
|
user?: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
function formatIssue(issue: any): SimpleIssue {
|
function formatIssue(issue: any): SimpleIssue {
|
||||||
return {
|
return {
|
||||||
id: issue.id,
|
id: issue.id,
|
||||||
|
identifier: issue.identifier,
|
||||||
title: issue.title,
|
title: issue.title,
|
||||||
status: issue.state?.name || "Unknown",
|
status: issue.state?.name || "Unknown",
|
||||||
|
statusId: issue.state?.id,
|
||||||
priority: issue.priority,
|
priority: issue.priority,
|
||||||
assignee: issue.assignee?.name,
|
assignee: issue.assignee?.name,
|
||||||
dueDate: issue.dueDate,
|
dueDate: issue.dueDate,
|
||||||
labels: issue.labels?.nodes?.map((l: any) => l.name) || [],
|
labels: issue.labels?.nodes?.map((l: any) => ({
|
||||||
|
name: l.name,
|
||||||
|
id: l.id
|
||||||
|
})) || [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,6 +480,32 @@ function formatProject(project: any): SimpleProject {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add new document formatting function
|
||||||
|
function formatDocument(doc: any): SimpleDocument {
|
||||||
|
return {
|
||||||
|
id: doc.id,
|
||||||
|
title: doc.title,
|
||||||
|
content: doc.content,
|
||||||
|
icon: doc.icon,
|
||||||
|
url: doc.url,
|
||||||
|
createdAt: doc.createdAt,
|
||||||
|
updatedAt: doc.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add after other formatting functions
|
||||||
|
function formatComment(comment: any): SimpleComment {
|
||||||
|
return {
|
||||||
|
id: comment.id,
|
||||||
|
body: comment.body,
|
||||||
|
user: comment.user ? {
|
||||||
|
id: comment.user.id,
|
||||||
|
name: comment.user.name,
|
||||||
|
} : undefined,
|
||||||
|
createdAt: comment.createdAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// API Functions
|
// API Functions
|
||||||
async function createIssue(
|
async function createIssue(
|
||||||
client: LinearClient,
|
client: LinearClient,
|
||||||
|
@ -314,7 +524,29 @@ async function updateIssue(
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const { issueId, ...updateData } = params;
|
const { issueId, ...updateData } = params;
|
||||||
return await client.updateIssue(issueId, updateData);
|
|
||||||
|
// Create a new object for the properly typed data
|
||||||
|
const formattedData: any = { ...updateData };
|
||||||
|
|
||||||
|
// Convert date strings to proper format if provided
|
||||||
|
if (formattedData.dueDate) {
|
||||||
|
// Due date should be YYYY-MM-DD format
|
||||||
|
formattedData.dueDate = formattedData.dueDate.split('T')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formattedData.snoozedUntilAt) {
|
||||||
|
// Convert to Date object for the API
|
||||||
|
formattedData.snoozedUntilAt = new Date(formattedData.snoozedUntilAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any undefined values to avoid API errors
|
||||||
|
Object.keys(formattedData).forEach(key => {
|
||||||
|
if (formattedData[key] === undefined) {
|
||||||
|
delete formattedData[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return await client.updateIssue(issueId, formattedData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return `Error: ${error}`;
|
return `Error: ${error}`;
|
||||||
}
|
}
|
||||||
|
@ -380,21 +612,67 @@ async function advancedSearchIssues(
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const filter: any = {};
|
const filter: any = {};
|
||||||
if (params.teamId) filter.team = { id: { eq: params.teamId } };
|
|
||||||
if (params.assigneeId) filter.assignee = { id: { eq: params.assigneeId } };
|
// Text search filters
|
||||||
if (params.status) filter.state = { type: { eq: params.status } };
|
|
||||||
if (params.priority) filter.priority = { eq: params.priority };
|
|
||||||
if (params.query) {
|
if (params.query) {
|
||||||
filter.or = [
|
filter.or = [
|
||||||
{ title: { containsIgnoreCase: params.query } },
|
{ title: { containsIgnoreCase: params.query } },
|
||||||
{ description: { containsIgnoreCase: params.query } },
|
{ description: { containsIgnoreCase: params.query } },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
if (params.title) filter.title = { containsIgnoreCase: params.title };
|
||||||
|
if (params.description) filter.description = { containsIgnoreCase: params.description };
|
||||||
|
|
||||||
|
// Basic filters
|
||||||
|
if (params.teamId) filter.team = { id: { eq: params.teamId } };
|
||||||
|
if (params.assigneeId) filter.assignee = { id: { eq: params.assigneeId } };
|
||||||
|
if (params.creatorId) filter.creator = { id: { eq: params.creatorId } };
|
||||||
|
if (params.priority !== undefined) filter.priority = { eq: params.priority };
|
||||||
|
|
||||||
|
// Status and state
|
||||||
|
if (params.stateId) {
|
||||||
|
filter.state = { id: { eq: params.stateId } };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date filters
|
||||||
|
if (params.createdAfter) filter.createdAt = { gt: params.createdAfter };
|
||||||
|
if (params.createdBefore) filter.createdAt = { lt: params.createdBefore };
|
||||||
|
if (params.updatedAfter) filter.updatedAt = { gt: params.updatedAfter };
|
||||||
|
if (params.updatedBefore) filter.updatedAt = { lt: params.updatedBefore };
|
||||||
|
if (params.completedAfter) filter.completedAt = { gt: params.completedAfter };
|
||||||
|
if (params.completedBefore) filter.completedAt = { lt: params.completedBefore };
|
||||||
|
if (params.startedAfter) filter.startedAt = { gt: params.startedAfter };
|
||||||
|
if (params.startedBefore) filter.startedAt = { lt: params.startedBefore };
|
||||||
|
|
||||||
|
// Due date filters
|
||||||
|
if (params.dueDate) filter.dueDate = { eq: params.dueDate };
|
||||||
|
if (params.dueDateAfter) filter.dueDate = { gt: params.dueDateAfter };
|
||||||
|
if (params.dueDateBefore) filter.dueDate = { lt: params.dueDateBefore };
|
||||||
|
|
||||||
|
// Relationship filters
|
||||||
|
if (params.projectId) filter.project = { id: { eq: params.projectId } };
|
||||||
|
if (params.parentId) filter.parent = { id: { eq: params.parentId } };
|
||||||
|
if (params.subscriberId) filter.subscribers = { some: { id: { eq: params.subscriberId } } };
|
||||||
|
if (params.hasBlockedBy) filter.hasBlockedByRelations = { eq: true };
|
||||||
|
if (params.hasBlocking) filter.hasBlockingRelations = { eq: true };
|
||||||
|
if (params.hasDuplicates) filter.hasDuplicateRelations = { eq: true };
|
||||||
|
|
||||||
|
// Labels
|
||||||
|
if (params.labelIds?.length) {
|
||||||
|
filter.labels = { some: { id: { in: params.labelIds } } };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other filters
|
||||||
|
if (params.estimate !== undefined) filter.estimate = { eq: params.estimate };
|
||||||
|
if (params.number !== undefined) filter.number = { eq: params.number };
|
||||||
|
if (params.snoozedById) filter.snoozedBy = { id: { eq: params.snoozedById } };
|
||||||
|
if (params.snoozedUntilAfter) filter.snoozedUntilAt = { gt: params.snoozedUntilAfter };
|
||||||
|
if (params.snoozedUntilBefore) filter.snoozedUntilAt = { lt: params.snoozedUntilBefore };
|
||||||
|
|
||||||
const issues = await client.issues({
|
const issues = await client.issues({
|
||||||
first: params.limit,
|
first: params.limit,
|
||||||
filter,
|
filter,
|
||||||
orderBy: params.orderBy || ("updatedAt" as any),
|
orderBy: params.orderBy || "updatedAt" as any,
|
||||||
});
|
});
|
||||||
|
|
||||||
return issues.nodes.map(formatIssue);
|
return issues.nodes.map(formatIssue);
|
||||||
|
@ -403,20 +681,23 @@ async function advancedSearchIssues(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Modify searchUsers function to allow more specific search parameters
|
||||||
async function searchUsers(
|
async function searchUsers(
|
||||||
client: LinearClient,
|
client: LinearClient,
|
||||||
{ query, limit }: z.infer<typeof SearchUsersParams>
|
params: z.infer<typeof SearchUsersParams>
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
let filter: any = {};
|
||||||
|
|
||||||
|
if (params.email) {
|
||||||
|
filter = { email: { eq: params.email } };
|
||||||
|
} else if (params.displayName) {
|
||||||
|
filter = { displayName: { containsIgnoreCase: params.displayName } };
|
||||||
|
}
|
||||||
|
|
||||||
const users = await client.users({
|
const users = await client.users({
|
||||||
filter: {
|
filter,
|
||||||
or: [
|
first: params.limit,
|
||||||
{ name: { containsIgnoreCase: query } },
|
|
||||||
{ displayName: { containsIgnoreCase: query } },
|
|
||||||
{ email: { containsIgnoreCase: query } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
first: limit,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return users.nodes.map(
|
return users.nodes.map(
|
||||||
|
@ -471,25 +752,99 @@ async function getProject(
|
||||||
// Modify searchProjects function to handle empty queries
|
// Modify searchProjects function to handle empty queries
|
||||||
async function searchProjects(
|
async function searchProjects(
|
||||||
client: LinearClient,
|
client: LinearClient,
|
||||||
{ query, teamId, limit }: z.infer<typeof SearchProjectsParams>
|
params: z.infer<typeof SearchProjectsParams>
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const searchParams: any = { first: limit };
|
|
||||||
const filter: any = {};
|
const filter: any = {};
|
||||||
|
|
||||||
if (teamId) {
|
// Text search filters
|
||||||
filter.team = { id: { eq: teamId } };
|
if (params.query) {
|
||||||
|
filter.or = [
|
||||||
|
{ name: { containsIgnoreCase: params.query } },
|
||||||
|
{ searchableContent: { contains: params.query } }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (params.name) {
|
||||||
|
filter.name = { containsIgnoreCase: params.name };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query) {
|
// Basic filters
|
||||||
filter.or = [{ name: { containsIgnoreCase: query } }];
|
if (params.teamId) {
|
||||||
|
filter.accessibleTeams = { some: { id: { eq: params.teamId } } };
|
||||||
|
}
|
||||||
|
if (params.creatorId) {
|
||||||
|
filter.creator = { id: { eq: params.creatorId } };
|
||||||
|
}
|
||||||
|
if (params.leadId) {
|
||||||
|
filter.lead = { id: { eq: params.leadId } };
|
||||||
|
}
|
||||||
|
if (params.priority !== undefined) {
|
||||||
|
filter.priority = { eq: params.priority };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(filter).length > 0) {
|
// Status and state filters
|
||||||
searchParams.filter = filter;
|
if (params.health) {
|
||||||
|
filter.health = { eq: params.health };
|
||||||
|
}
|
||||||
|
if (params.state) {
|
||||||
|
filter.state = { eq: params.state };
|
||||||
|
}
|
||||||
|
if (params.status) {
|
||||||
|
filter.status = { id: { eq: params.status } };
|
||||||
}
|
}
|
||||||
|
|
||||||
const projects = await client.projects(searchParams);
|
// Date filters
|
||||||
|
if (params.startDate) {
|
||||||
|
filter.startDate = { eq: params.startDate };
|
||||||
|
}
|
||||||
|
if (params.targetDate) {
|
||||||
|
filter.targetDate = { eq: params.targetDate };
|
||||||
|
}
|
||||||
|
if (params.createdAfter || params.createdBefore) {
|
||||||
|
filter.createdAt = {
|
||||||
|
...(params.createdAfter && { gt: params.createdAfter }),
|
||||||
|
...(params.createdBefore && { lt: params.createdBefore })
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (params.updatedAfter || params.updatedBefore) {
|
||||||
|
filter.updatedAt = {
|
||||||
|
...(params.updatedAfter && { gt: params.updatedAfter }),
|
||||||
|
...(params.updatedBefore && { lt: params.updatedBefore })
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (params.completedAfter || params.completedBefore) {
|
||||||
|
filter.completedAt = {
|
||||||
|
...(params.completedAfter && { gt: params.completedAfter }),
|
||||||
|
...(params.completedBefore && { lt: params.completedBefore })
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (params.canceledAfter || params.canceledBefore) {
|
||||||
|
filter.canceledAt = {
|
||||||
|
...(params.canceledAfter && { gt: params.canceledAfter }),
|
||||||
|
...(params.canceledBefore && { lt: params.canceledBefore })
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relationship filters
|
||||||
|
if (params.hasBlockedBy) {
|
||||||
|
filter.hasBlockedByRelations = { eq: true };
|
||||||
|
}
|
||||||
|
if (params.hasBlocking) {
|
||||||
|
filter.hasBlockingRelations = { eq: true };
|
||||||
|
}
|
||||||
|
if (params.hasRelated) {
|
||||||
|
filter.hasRelatedRelations = { eq: true };
|
||||||
|
}
|
||||||
|
if (params.hasViolatedDependencies) {
|
||||||
|
filter.hasViolatedRelations = { eq: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const projects = await client.projects({
|
||||||
|
first: params.limit,
|
||||||
|
filter,
|
||||||
|
orderBy: params.orderBy || "updatedAt" as any,
|
||||||
|
});
|
||||||
|
|
||||||
return projects.nodes.map(formatProject);
|
return projects.nodes.map(formatProject);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return `Error: ${error}`;
|
return `Error: ${error}`;
|
||||||
|
@ -524,6 +879,106 @@ async function listProjects(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add new comment API functions before the main manager function
|
||||||
|
async function createComment(
|
||||||
|
client: LinearClient,
|
||||||
|
params: z.infer<typeof CreateCommentParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { issueId, body } = params;
|
||||||
|
const comment = await client.createComment({
|
||||||
|
issueId,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
return formatComment(comment);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listComments(
|
||||||
|
client: LinearClient,
|
||||||
|
params: z.infer<typeof ListCommentsParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { issueId, limit } = params;
|
||||||
|
const issue = await client.issue(issueId);
|
||||||
|
const comments = await issue.comments({
|
||||||
|
first: limit,
|
||||||
|
orderBy: "createdAt" as any,
|
||||||
|
});
|
||||||
|
return comments.nodes.map(formatComment);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new document API functions before the main manager function
|
||||||
|
async function createDocument(
|
||||||
|
client: LinearClient,
|
||||||
|
params: z.infer<typeof CreateDocumentParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const document = await client.createDocument(params);
|
||||||
|
return formatDocument(document);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateDocument(
|
||||||
|
client: LinearClient,
|
||||||
|
params: z.infer<typeof UpdateDocumentParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { documentId, ...updateData } = params;
|
||||||
|
const document = await client.updateDocument(documentId, updateData);
|
||||||
|
return formatDocument(document);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getDocument(
|
||||||
|
client: LinearClient,
|
||||||
|
{ documentId }: z.infer<typeof GetDocumentParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const document = await client.document(documentId);
|
||||||
|
return formatDocument(document);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchDocuments(
|
||||||
|
client: LinearClient,
|
||||||
|
params: z.infer<typeof SearchDocumentsParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const filter: any = {
|
||||||
|
or: [
|
||||||
|
{ title: { containsIgnoreCase: params.query } },
|
||||||
|
{ content: { containsIgnoreCase: params.query } },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (params.projectId) {
|
||||||
|
filter.project = { id: { eq: params.projectId } };
|
||||||
|
}
|
||||||
|
|
||||||
|
const documents = await client.documents({
|
||||||
|
first: params.limit,
|
||||||
|
filter,
|
||||||
|
orderBy: "updatedAt" as any,
|
||||||
|
});
|
||||||
|
|
||||||
|
return documents.nodes.map(formatDocument);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Main manager function
|
// Main manager function
|
||||||
export const LinearManagerParams = z.object({
|
export const LinearManagerParams = z.object({
|
||||||
request: z
|
request: z
|
||||||
|
@ -536,11 +991,7 @@ export async function linearManager(
|
||||||
{ request }: LinearManagerParams,
|
{ request }: LinearManagerParams,
|
||||||
context_message: Message
|
context_message: Message
|
||||||
) {
|
) {
|
||||||
console.log(
|
|
||||||
"Context message",
|
|
||||||
context_message.author,
|
|
||||||
context_message.getUserRoles()
|
|
||||||
);
|
|
||||||
const userConfig = context_message.author.config;
|
const userConfig = context_message.author.config;
|
||||||
|
|
||||||
// console.log("User config", userConfig);
|
// console.log("User config", userConfig);
|
||||||
|
@ -563,6 +1014,7 @@ export async function linearManager(
|
||||||
|
|
||||||
const client = new LinearClient({ apiKey: linearApiKey });
|
const client = new LinearClient({ apiKey: linearApiKey });
|
||||||
|
|
||||||
|
|
||||||
const linear_tools: RunnableToolFunction<any>[] = [
|
const linear_tools: RunnableToolFunction<any>[] = [
|
||||||
zodFunction({
|
zodFunction({
|
||||||
function: (params) => createIssue(client, params),
|
function: (params) => createIssue(client, params),
|
||||||
|
@ -606,8 +1058,16 @@ export async function linearManager(
|
||||||
function: (params) => advancedSearchIssues(client, params),
|
function: (params) => advancedSearchIssues(client, params),
|
||||||
name: "linearAdvancedSearchIssues",
|
name: "linearAdvancedSearchIssues",
|
||||||
schema: AdvancedSearchIssuesParams,
|
schema: AdvancedSearchIssuesParams,
|
||||||
description:
|
description: `Search for issues with advanced filters including:
|
||||||
"Search for issues with advanced filters including status, assignee, and priority",
|
- Status (backlog, todo, in_progress, done, canceled)
|
||||||
|
- Assignee
|
||||||
|
- Priority
|
||||||
|
- Date ranges for:
|
||||||
|
* Updated time
|
||||||
|
* Created time
|
||||||
|
* Completed time
|
||||||
|
Use ISO datetime format (e.g., "2024-01-18T00:00:00Z") for date filters.
|
||||||
|
Can find issues updated, created, or completed within specific time periods.`,
|
||||||
}),
|
}),
|
||||||
zodFunction({
|
zodFunction({
|
||||||
function: (params) => createProject(client, params),
|
function: (params) => createProject(client, params),
|
||||||
|
@ -641,8 +1101,48 @@ export async function linearManager(
|
||||||
description:
|
description:
|
||||||
"List projects in Linear, optionally filtered by team and state. Returns most recently updated projects first.",
|
"List projects in Linear, optionally filtered by team and state. Returns most recently updated projects first.",
|
||||||
}),
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => createComment(client, params),
|
||||||
|
name: "linearCreateComment",
|
||||||
|
schema: CreateCommentParams,
|
||||||
|
description: "Create a new comment on a Linear issue",
|
||||||
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => listComments(client, params),
|
||||||
|
name: "linearListComments",
|
||||||
|
schema: ListCommentsParams,
|
||||||
|
description: "List comments on a Linear issue",
|
||||||
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => createDocument(client, params),
|
||||||
|
name: "linearCreateDocument",
|
||||||
|
schema: CreateDocumentParams,
|
||||||
|
description: "Create a new document in Linear",
|
||||||
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => updateDocument(client, params),
|
||||||
|
name: "linearUpdateDocument",
|
||||||
|
schema: UpdateDocumentParams,
|
||||||
|
description: "Update an existing document in Linear",
|
||||||
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => getDocument(client, params),
|
||||||
|
name: "linearGetDocument",
|
||||||
|
schema: GetDocumentParams,
|
||||||
|
description: "Get details of a specific document",
|
||||||
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => searchDocuments(client, params),
|
||||||
|
name: "linearSearchDocuments",
|
||||||
|
schema: SearchDocumentsParams,
|
||||||
|
description: "Search for documents in Linear using a query string",
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const organization = await client.organization
|
||||||
|
const workspace = organization?.name
|
||||||
|
|
||||||
// fetch all labels available in each team
|
// fetch all labels available in each team
|
||||||
const teams = await client.teams({ first: 10 });
|
const teams = await client.teams({ first: 10 });
|
||||||
const teamLabels = await client.issueLabels();
|
const teamLabels = await client.issueLabels();
|
||||||
|
@ -654,54 +1154,65 @@ export async function linearManager(
|
||||||
name: state.name,
|
name: state.name,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const organizationContext = `Organization:
|
||||||
|
Name: ${workspace}
|
||||||
|
Id: ${organization?.id}
|
||||||
|
`;
|
||||||
|
|
||||||
// Only include teams and labels in the context if they exist
|
// Only include teams and labels in the context if they exist
|
||||||
const teamsContext =
|
const teamsContext =
|
||||||
teams.nodes.length > 0
|
teams.nodes.length > 0
|
||||||
? `Teams:\n${teams.nodes.map((team) => ` - ${team.name}`).join("\n")}`
|
? `Teams:\n${teams.nodes.map((team) => ` - ${team.name} id: ${team.id}`).join("\n")}`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const labelsContext =
|
const labelsContext =
|
||||||
teamLabels.nodes.length > 0
|
teamLabels.nodes.length > 0
|
||||||
? `All Labels:\n${teamLabels.nodes
|
? `All Labels:\n${teamLabels.nodes
|
||||||
.map((label) => ` - ${label.name} (${label.color})`)
|
.map((label) => ` - ${label.name} (${label.color}) id: ${label.id}`)
|
||||||
.join("\n")}`
|
.join("\n")}`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const issueStateContext =
|
const issueStateContext =
|
||||||
state_values.length > 0
|
state_values.length > 0
|
||||||
? `All Issue States:\n${state_values
|
? `All Issue States:\n${state_values
|
||||||
.map((state) => ` - ${state.name}`)
|
.map((state) => ` - ${state.name} id: ${state.id}`)
|
||||||
.join("\n")}`
|
.join("\n")}`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const workspaceContext = [teamsContext, labelsContext, issueStateContext]
|
const workspaceContext = [organizationContext, teamsContext, labelsContext, issueStateContext]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join("\n\n");
|
.join("\n\n");
|
||||||
|
|
||||||
|
const userDetails = await client.users({ filter: { email: { eq: linearEmail } } });
|
||||||
|
|
||||||
const response = await ask({
|
const response = await ask({
|
||||||
model: "gpt-4o-mini",
|
model: "gpt-4o",
|
||||||
prompt: `You are a Linear project manager.
|
prompt: `You are a Linear project manager.
|
||||||
|
|
||||||
Your job is to understand the user's request and manage issues, teams, and projects using the available tools.
|
Your job is to understand the user's request and manage issues, teams, and projects using the available tools.
|
||||||
|
|
||||||
|
Important note about Linear issue identification:
|
||||||
|
- issueId: A UUID that uniquely identifies an issue internally (e.g., "123e4567-e89b-12d3-a456-426614174000")
|
||||||
|
- identifier: A human-readable issue reference (e.g., "XCE-205", "ENG-123")
|
||||||
|
When referring to issues in responses, always use the identifier format for better readability.
|
||||||
|
|
||||||
----
|
----
|
||||||
${memory_manager_guide("linear_manager", context_message.author.id)}
|
${memory_manager_guide("linear_manager", context_message.author.id)}
|
||||||
----
|
----
|
||||||
|
|
||||||
${
|
${workspaceContext
|
||||||
workspaceContext
|
? `Here is some more context on current linear workspace:\n${workspaceContext}`
|
||||||
? `Here is some more context on current linear workspace:\n${workspaceContext}`
|
: ""
|
||||||
: ""
|
}
|
||||||
}
|
|
||||||
|
|
||||||
The user you are currently assisting has the following details:
|
The user you are currently assisting has the following details (No need to search if the user is asking for their own related issues/projects):
|
||||||
- Name: ${userConfig?.name}
|
- Name: ${userConfig?.name}
|
||||||
- Linear Email: ${linearEmail}
|
- Linear Email: ${linearEmail}
|
||||||
|
- Linear User ID: ${userDetails.nodes[0]?.id}
|
||||||
|
|
||||||
When responding make sure to link the issues when returning the value.
|
When responding make sure to link the issues when returning the value.
|
||||||
linear issue links look like: \`https://linear.app/xcelerator/issue/XCE-205\`
|
linear issue links look like: \`https://linear.app/xcelerator/issue/XCE-205\`
|
||||||
Where \`XCE-205\` is the issue ID and \`xcelerator\` is the team name.
|
Where \`XCE-205\` is the identifier (not the issueId) and \`xcelerator\` is the team name.
|
||||||
|
|
||||||
`,
|
`,
|
||||||
message: request,
|
message: request,
|
||||||
seed: `linear-${context_message.channelId}`,
|
seed: `linear-${context_message.channelId}`,
|
||||||
|
|
|
@ -416,7 +416,7 @@ ${memory_manager_guide("links_manager", context_message.author.id)}
|
||||||
----
|
----
|
||||||
`,
|
`,
|
||||||
message: request,
|
message: request,
|
||||||
seed: "link-${context_message.channelId}",
|
seed: `link-${context_message.channelId}`,
|
||||||
tools: link_tools.concat(
|
tools: link_tools.concat(
|
||||||
memory_manager_init(context_message, "links_manager")
|
memory_manager_init(context_message, "links_manager")
|
||||||
) as any,
|
) as any,
|
||||||
|
|
Loading…
Reference in New Issue