linear manager
This commit is contained in:
parent
59f90329af
commit
4a01429e4a
|
@ -6,7 +6,13 @@ export const dataDir = path.join(process.env.ANYA_DIR || "./");
|
||||||
export const pathInDataDir = (filename: string) => path.join(dataDir, filename);
|
export const pathInDataDir = (filename: string) => path.join(dataDir, filename);
|
||||||
|
|
||||||
interface PlatformIdentity {
|
interface PlatformIdentity {
|
||||||
platform: "discord" | "whatsapp" | "email" | "events";
|
platform:
|
||||||
|
| "discord"
|
||||||
|
| "whatsapp"
|
||||||
|
| "email"
|
||||||
|
| "events"
|
||||||
|
| "linear_key"
|
||||||
|
| "linear_email";
|
||||||
id: string; // Platform-specific user ID
|
id: string; // Platform-specific user ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +28,14 @@ export interface UserConfig {
|
||||||
|
|
||||||
// Define Zod schemas for validation
|
// Define Zod schemas for validation
|
||||||
const PlatformIdentitySchema = z.object({
|
const PlatformIdentitySchema = z.object({
|
||||||
platform: z.enum(["discord", "whatsapp", "email", "events"]),
|
platform: z.enum([
|
||||||
|
"discord",
|
||||||
|
"whatsapp",
|
||||||
|
"email",
|
||||||
|
"events",
|
||||||
|
"linear_key",
|
||||||
|
"linear_email",
|
||||||
|
]),
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,8 @@ import { get_transcription } from "../tools/ask";
|
||||||
|
|
||||||
// Define the type for the event callback
|
// Define the type for the event callback
|
||||||
type EventCallback = (
|
type EventCallback = (
|
||||||
payload: Record<string, string | number>
|
payload: Record<string, string | number>,
|
||||||
|
awaiting?: boolean
|
||||||
) => void | Record<string, any> | Promise<void> | Promise<Record<string, any>>;
|
) => void | Record<string, any> | Promise<void> | Promise<Record<string, any>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,7 +75,7 @@ class EventManager {
|
||||||
// Execute all callbacks and collect their responses
|
// Execute all callbacks and collect their responses
|
||||||
const promises = Array.from(callbacks).map(async (cb) => {
|
const promises = Array.from(callbacks).map(async (cb) => {
|
||||||
try {
|
try {
|
||||||
const result = cb(payload);
|
const result = cb(payload, true);
|
||||||
if (result instanceof Promise) {
|
if (result instanceof Promise) {
|
||||||
return await result;
|
return await result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
"@langchain/community": "^0.3.11",
|
"@langchain/community": "^0.3.11",
|
||||||
"@langchain/core": "^0.3.16",
|
"@langchain/core": "^0.3.16",
|
||||||
"@langchain/openai": "^0.3.11",
|
"@langchain/openai": "^0.3.11",
|
||||||
|
"@linear/sdk": "^37.0.0",
|
||||||
"@nextcloud/files": "^3.8.0",
|
"@nextcloud/files": "^3.8.0",
|
||||||
"@solyarisoftware/voskjs": "^1.2.8",
|
"@solyarisoftware/voskjs": "^1.2.8",
|
||||||
"@types/node-cron": "^3.0.11",
|
"@types/node-cron": "^3.0.11",
|
||||||
|
|
|
@ -2,11 +2,10 @@ import OpenAI from "openai";
|
||||||
import { saveApiUsage } from "../usage";
|
import { saveApiUsage } from "../usage";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
|
||||||
import { RunnableToolFunctionWithParse } from "openai/lib/RunnableFunction.mjs";
|
import { RunnableToolFunctionWithParse } from "openai/lib/RunnableFunction.mjs";
|
||||||
import {
|
import {
|
||||||
ChatCompletion,
|
ChatCompletion,
|
||||||
ChatCompletionAssistantMessageParam,
|
|
||||||
ChatCompletionMessageParam,
|
ChatCompletionMessageParam,
|
||||||
} from "openai/resources/index.mjs";
|
} from "openai/resources/index.mjs";
|
||||||
import { send_sys_log } from "../interfaces/log";
|
import { send_sys_log } from "../interfaces/log";
|
||||||
|
|
|
@ -0,0 +1,278 @@
|
||||||
|
// event-prompt-augmentations.ts
|
||||||
|
import { RunnableToolFunctionWithParse } from "openai/lib/RunnableFunction.mjs";
|
||||||
|
import { get_transcription } from "./ask"; // or wherever your get_transcription function lives
|
||||||
|
import { Message } from "../interfaces/message";
|
||||||
|
import { buildSystemPrompts } from "../assistant/system-prompts";
|
||||||
|
import { getTools } from ".";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The shape of data returned from any specialized event augmentation.
|
||||||
|
*/
|
||||||
|
export interface PromptAugmentationResult {
|
||||||
|
additionalSystemPrompt?: string;
|
||||||
|
updatedSystemPrompt?: string;
|
||||||
|
message?: string;
|
||||||
|
updatedTools?: RunnableToolFunctionWithParse<any>[];
|
||||||
|
attachedImageBase64?: string;
|
||||||
|
model: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1) Voice Event Augmentation
|
||||||
|
* - Possibly do transcription if an audio File is present.
|
||||||
|
* - Possibly convert an image File to base64 if present.
|
||||||
|
* - Add any extra system prompt text needed for "voice mode."
|
||||||
|
*/
|
||||||
|
async function voiceEventAugmentation(
|
||||||
|
payload: Record<string, any>,
|
||||||
|
baseTools: RunnableToolFunctionWithParse<any>[] | undefined,
|
||||||
|
contextMessage: Message
|
||||||
|
): Promise<PromptAugmentationResult> {
|
||||||
|
let attachedImageBase64: string | undefined;
|
||||||
|
|
||||||
|
// Transcribe if there's an audio file
|
||||||
|
if (payload?.transcription && payload.transcription instanceof File) {
|
||||||
|
console.log("Transcribing audio for voice event listener.");
|
||||||
|
const file = payload.transcription;
|
||||||
|
payload.transcription = await get_transcription(file as globalThis.File);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for an attached image
|
||||||
|
const otherContextData = payload?.other_reference_data;
|
||||||
|
if (
|
||||||
|
otherContextData instanceof File &&
|
||||||
|
otherContextData.type.includes("image")
|
||||||
|
) {
|
||||||
|
console.log("Got image in voice event payload; converting to base64...");
|
||||||
|
const buffer = await otherContextData.arrayBuffer();
|
||||||
|
attachedImageBase64 = `data:${otherContextData.type};base64,${Buffer.from(
|
||||||
|
buffer
|
||||||
|
).toString("base64")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = `
|
||||||
|
You are in voice trigger mode.
|
||||||
|
|
||||||
|
The voice event that triggered this is:
|
||||||
|
Payload: ${JSON.stringify(payload, null, 2)}
|
||||||
|
|
||||||
|
Your response must be in plain text without extra formatting or Markdown.
|
||||||
|
`;
|
||||||
|
|
||||||
|
const systemPrompts = await buildSystemPrompts(contextMessage);
|
||||||
|
|
||||||
|
const prompt = systemPrompts.map((p) => p.content).join("\n\n");
|
||||||
|
|
||||||
|
const tools = getTools(
|
||||||
|
contextMessage.author.username,
|
||||||
|
contextMessage
|
||||||
|
) as RunnableToolFunctionWithParse<any>[];
|
||||||
|
|
||||||
|
return {
|
||||||
|
updatedSystemPrompt: prompt,
|
||||||
|
message,
|
||||||
|
updatedTools: tools,
|
||||||
|
attachedImageBase64,
|
||||||
|
model: "gpt-4o",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2) New Todo Note Event Augmentation
|
||||||
|
*/
|
||||||
|
async function newTodoAugmentation(
|
||||||
|
payload: Record<string, any>,
|
||||||
|
baseTools: RunnableToolFunctionWithParse<any>[] | undefined,
|
||||||
|
contextMessage: Message
|
||||||
|
): Promise<PromptAugmentationResult> {
|
||||||
|
let message = `
|
||||||
|
You are in new todo note trigger mode.
|
||||||
|
|
||||||
|
The user added a new todo note which triggered this event.
|
||||||
|
Payload: ${JSON.stringify(payload, null, 2)}
|
||||||
|
|
||||||
|
Make sure to handle the user's newly added todo item.
|
||||||
|
IMPORTANT: Mark the todo as done if appropriate, etc.
|
||||||
|
`;
|
||||||
|
|
||||||
|
let systemPrompts = await buildSystemPrompts(contextMessage);
|
||||||
|
|
||||||
|
const prompt = systemPrompts.map((p) => p.content).join("\n\n");
|
||||||
|
|
||||||
|
const tools = getTools(
|
||||||
|
contextMessage.author.username,
|
||||||
|
contextMessage
|
||||||
|
) as RunnableToolFunctionWithParse<any>[];
|
||||||
|
|
||||||
|
return {
|
||||||
|
additionalSystemPrompt: prompt,
|
||||||
|
message,
|
||||||
|
updatedTools: tools,
|
||||||
|
model: "gpt-4o-mini",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3) Message from a Manager Event Augmentation
|
||||||
|
*/
|
||||||
|
async function messageFromManagerAugmentation(
|
||||||
|
payload: Record<string, any>,
|
||||||
|
baseTools: RunnableToolFunctionWithParse<any>[] | undefined,
|
||||||
|
contextMessage: Message
|
||||||
|
): Promise<PromptAugmentationResult> {
|
||||||
|
const message = `
|
||||||
|
You just got a request from a manager.
|
||||||
|
|
||||||
|
Payload: ${JSON.stringify(payload, null, 2)}
|
||||||
|
|
||||||
|
Handle it accordingly.
|
||||||
|
`;
|
||||||
|
const tools = getTools(
|
||||||
|
contextMessage.author.username,
|
||||||
|
contextMessage
|
||||||
|
) as RunnableToolFunctionWithParse<any>[];
|
||||||
|
return {
|
||||||
|
message,
|
||||||
|
updatedTools: tools,
|
||||||
|
model: "gpt-4o-mini",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4) Default/Fallback Augmentation
|
||||||
|
*/
|
||||||
|
async function defaultAugmentation(
|
||||||
|
payload: Record<string, any>,
|
||||||
|
baseTools: RunnableToolFunctionWithParse<any>[] | undefined
|
||||||
|
): Promise<PromptAugmentationResult> {
|
||||||
|
return {
|
||||||
|
updatedTools: baseTools,
|
||||||
|
attachedImageBase64: undefined,
|
||||||
|
model: "gpt-4o-mini",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map/dictionary that returns specialized logic keyed by `eventId`.
|
||||||
|
* If no exact eventId match is found, we will fallback to `defaultAugmentation`.
|
||||||
|
*/
|
||||||
|
export const eventPromptAugmentations: Record<
|
||||||
|
string,
|
||||||
|
(
|
||||||
|
payload: Record<string, any>,
|
||||||
|
baseTools: RunnableToolFunctionWithParse<any>[] | undefined,
|
||||||
|
contextMessage: Message
|
||||||
|
) => Promise<PromptAugmentationResult>
|
||||||
|
> = {
|
||||||
|
on_voice_message: voiceEventAugmentation,
|
||||||
|
new_todo_for_anya: newTodoAugmentation,
|
||||||
|
message_from_a_manager: messageFromManagerAugmentation,
|
||||||
|
// Add more eventId-specific augmentations as needed...
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the final prompt and attaches any relevant tooling or attachments
|
||||||
|
* for a given event and instruction. Consolidates the "branching logic" into
|
||||||
|
* modular augmentations, removing scattered if/else from the main file.
|
||||||
|
*/
|
||||||
|
export async function buildPromptAndToolsForEvent(
|
||||||
|
eventId: string,
|
||||||
|
description: string,
|
||||||
|
payload: Record<string, any>,
|
||||||
|
instruction: string,
|
||||||
|
notify: boolean,
|
||||||
|
baseTools: RunnableToolFunctionWithParse<any>[] | undefined,
|
||||||
|
contextMessage: Message
|
||||||
|
): Promise<{
|
||||||
|
finalPrompt: string;
|
||||||
|
message?: string;
|
||||||
|
finalTools: RunnableToolFunctionWithParse<any>[] | undefined;
|
||||||
|
attachedImage?: string;
|
||||||
|
model?: string;
|
||||||
|
}> {
|
||||||
|
console.log(`Building prompt for event: ${eventId}`);
|
||||||
|
console.log(`Instruction: ${instruction}`);
|
||||||
|
console.log(`Payload: ${JSON.stringify(payload, null, 2)}`);
|
||||||
|
|
||||||
|
// 1) A base system prompt shared by all "instruction" type listeners
|
||||||
|
const baseSystemPrompt = `You are an Event Handler.
|
||||||
|
You are called when an event triggers. Your task is to execute the user's instruction based on the triggered event and reply with the text to display as a notification to the user.
|
||||||
|
|
||||||
|
**Guidelines:**
|
||||||
|
|
||||||
|
- **Notification to User:**
|
||||||
|
- Any message you reply with will automatically be sent to the user as a notification.
|
||||||
|
- Do **not** indicate in the text that it is a notification.
|
||||||
|
|
||||||
|
- **Using Tools:**
|
||||||
|
- You have access to the necessary tools to execute the instruction; use them as needed.
|
||||||
|
- You also have access to the \`event_manager\` tool if you need to manage events or listeners (use it only if necessary).
|
||||||
|
|
||||||
|
- **Sending Messages:**
|
||||||
|
- **To the Current User:**
|
||||||
|
- Do **not** ask \`communication_manager\` tool.
|
||||||
|
- Simply reply with the message you want to send.
|
||||||
|
- **To Other Users:**
|
||||||
|
- Use the \`communication_manager\` tool.
|
||||||
|
- The message you reply with will still be sent to the current user as a notification.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
- **Instruction:** "When you get an email from John, tell John on WhatsApp that you got the email."
|
||||||
|
- **Steps:**
|
||||||
|
1. Use the \`communication_manager\` tool to send a message to John on WhatsApp.
|
||||||
|
2. Reply to the current user with "I have sent a message to John on WhatsApp that you got the email."
|
||||||
|
|
||||||
|
**Currently Triggered Event:**
|
||||||
|
- **Event ID:** ${eventId}
|
||||||
|
- **Description:** ${description}
|
||||||
|
- **Payload:** ${JSON.stringify(payload, null, 2)}
|
||||||
|
- **Will Auto Notify Creator of Listener:** ${
|
||||||
|
notify
|
||||||
|
? "Yes, no need to send it yourself"
|
||||||
|
: "No, you need to notify the user manually"
|
||||||
|
}
|
||||||
|
- **Instruction:** ${instruction}
|
||||||
|
|
||||||
|
**Action Required:**
|
||||||
|
- Follow the instruction provided in the payload.
|
||||||
|
- Return the notification text based on the instruction.
|
||||||
|
|
||||||
|
**Important Note:**
|
||||||
|
- If the event and payload do **not** match the instruction, reply with **"IGNORE"**.
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 2) Decide which augmentation function to call
|
||||||
|
let augmentationFn = eventPromptAugmentations[eventId];
|
||||||
|
if (!augmentationFn) {
|
||||||
|
// Example: if your eventId is "message_from_xyz", handle it as a manager augmentation
|
||||||
|
if (eventId.startsWith("message_from")) {
|
||||||
|
augmentationFn = messageFromManagerAugmentation;
|
||||||
|
} else {
|
||||||
|
augmentationFn = defaultAugmentation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Run the specialized augmentation
|
||||||
|
const {
|
||||||
|
additionalSystemPrompt,
|
||||||
|
updatedTools,
|
||||||
|
attachedImageBase64,
|
||||||
|
updatedSystemPrompt,
|
||||||
|
model,
|
||||||
|
message,
|
||||||
|
} = await augmentationFn(payload, baseTools, contextMessage);
|
||||||
|
|
||||||
|
// 4) Combine prompts
|
||||||
|
const finalPrompt = [baseSystemPrompt, additionalSystemPrompt]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n\n");
|
||||||
|
|
||||||
|
return {
|
||||||
|
finalPrompt: updatedSystemPrompt || finalPrompt,
|
||||||
|
finalTools: updatedTools,
|
||||||
|
attachedImage: attachedImageBase64,
|
||||||
|
model,
|
||||||
|
message,
|
||||||
|
};
|
||||||
|
}
|
232
tools/events.ts
232
tools/events.ts
|
@ -14,6 +14,7 @@ import { get_actions } from "./actions";
|
||||||
import { pathInDataDir, userConfigs } from "../config";
|
import { pathInDataDir, userConfigs } from "../config";
|
||||||
import { memory_manager_guide, memory_manager_init } from "./memory-manager";
|
import { memory_manager_guide, memory_manager_init } from "./memory-manager";
|
||||||
import { buildSystemPrompts } from "../assistant/system-prompts";
|
import { buildSystemPrompts } from "../assistant/system-prompts";
|
||||||
|
import { buildPromptAndToolsForEvent } from "./event-prompt-augmentations";
|
||||||
|
|
||||||
// Paths to the JSON files
|
// Paths to the JSON files
|
||||||
const LISTENERS_FILE_PATH = pathInDataDir("listeners.json");
|
const LISTENERS_FILE_PATH = pathInDataDir("listeners.json");
|
||||||
|
@ -349,13 +350,25 @@ function replacePlaceholders(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Example registry mapping eventId -> zod schema
|
||||||
|
const eventSchemaRegistry: Record<string, z.ZodType<any>> = {
|
||||||
|
// Example:
|
||||||
|
// ping: z.object({ message: z.string().optional() }),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generic function to get a schema for an event
|
||||||
|
export function getSchemaForEvent(eventId: string) {
|
||||||
|
return eventSchemaRegistry[eventId] || z.object({});
|
||||||
|
}
|
||||||
|
|
||||||
// Function to register a listener with the eventManager
|
// Function to register a listener with the eventManager
|
||||||
function registerListener(listener: EventListener) {
|
function registerListener(listener: EventListener) {
|
||||||
const { eventId, description, userId, options, tool_names, notify } =
|
const { eventId, description, userId, options, tool_names, notify } =
|
||||||
listener;
|
listener;
|
||||||
|
|
||||||
const callback: EventCallback = async (
|
const callback: EventCallback = async (
|
||||||
payload: Record<string, string | number>
|
payload: Record<string, string | number>,
|
||||||
|
awaiting?: boolean
|
||||||
) => {
|
) => {
|
||||||
const event = eventsMap.get(eventId);
|
const event = eventsMap.get(eventId);
|
||||||
if (event) {
|
if (event) {
|
||||||
|
@ -388,6 +401,13 @@ function registerListener(listener: EventListener) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const schema = getSchemaForEvent(listener.eventId);
|
||||||
|
const result = schema.safeParse(payload);
|
||||||
|
if (!result.success) {
|
||||||
|
console.error("Invalid payload for event:", listener.eventId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (listener.template) {
|
if (listener.template) {
|
||||||
// Handle static event listener with template
|
// Handle static event listener with template
|
||||||
const formattedMessage = renderTemplate(listener.template, payload);
|
const formattedMessage = renderTemplate(listener.template, payload);
|
||||||
|
@ -400,219 +420,73 @@ function registerListener(listener: EventListener) {
|
||||||
return formattedMessage;
|
return formattedMessage;
|
||||||
// Expiry is handled via periodic cleanup
|
// Expiry is handled via periodic cleanup
|
||||||
} else if (listener.instruction) {
|
} else if (listener.instruction) {
|
||||||
// Handle dynamic event listener with instruction and tools
|
// Combine the user-defined tool set with "event_manager"
|
||||||
const u_tool_names = Array.from(
|
const requiredToolNames = Array.from(
|
||||||
new Set([...(tool_names ?? []), "event_manager"])
|
new Set([...(tool_names ?? []), "event_manager"])
|
||||||
);
|
);
|
||||||
let tools = getTools(
|
let baseTools = getTools(
|
||||||
contextMessage.author.username,
|
contextMessage.author.username,
|
||||||
contextMessage
|
contextMessage
|
||||||
).filter(
|
).filter(
|
||||||
(tool) =>
|
(tool) =>
|
||||||
tool.function.name && u_tool_names?.includes(tool.function.name)
|
tool.function.name && requiredToolNames.includes(tool.function.name)
|
||||||
) as RunnableToolFunctionWithParse<any>[] | undefined;
|
) as RunnableToolFunctionWithParse<any>[] | undefined;
|
||||||
|
|
||||||
tools = tools?.length ? tools : undefined;
|
console.time("buildPromptAndToolsForEvent");
|
||||||
|
// Now call the helper from the new file
|
||||||
const is_voice = listener.eventId === "on_voice_message";
|
const { finalPrompt, finalTools, attachedImage, model, message } =
|
||||||
const is_new_todo_note = listener.eventId === "new_todo_for_anya";
|
await buildPromptAndToolsForEvent(
|
||||||
const is_message_from_a_manager =
|
eventId,
|
||||||
listener.eventId.startsWith("message_from");
|
description,
|
||||||
|
payload,
|
||||||
let attached_image: string | undefined = undefined;
|
listener.instruction,
|
||||||
|
notify,
|
||||||
if (is_voice || is_new_todo_note || is_message_from_a_manager) {
|
baseTools,
|
||||||
tools = getTools(
|
|
||||||
contextMessage.author.username,
|
|
||||||
contextMessage
|
contextMessage
|
||||||
) as RunnableToolFunctionWithParse<any>[];
|
|
||||||
}
|
|
||||||
if (is_voice) {
|
|
||||||
const audio = ((payload as any) ?? {}).transcription;
|
|
||||||
if (audio && audio instanceof File) {
|
|
||||||
if (audio.type.includes("audio")) {
|
|
||||||
console.log("Transcribing audio for voice event listener.");
|
|
||||||
(payload as any).transcription = await get_transcription(
|
|
||||||
audio as File
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Payload for voice event listener: ", payload);
|
console.timeEnd("buildPromptAndToolsForEvent");
|
||||||
const otherContextData = (payload as any)?.other_reference_data;
|
|
||||||
|
|
||||||
if (otherContextData instanceof File) {
|
console.log("model", model);
|
||||||
if (otherContextData.type.includes("image")) {
|
|
||||||
console.log("Got image");
|
|
||||||
// Read the file as a buffer
|
|
||||||
const buffer = await otherContextData.arrayBuffer();
|
|
||||||
|
|
||||||
// Convert the buffer to a base64 string
|
console.log("message", message);
|
||||||
const base64Url = `data:${
|
|
||||||
otherContextData.type
|
|
||||||
};base64,${Buffer.from(buffer).toString("base64")}`;
|
|
||||||
|
|
||||||
// Do something with imageObject, like sending it in a response or logging
|
|
||||||
attached_image = base64Url;
|
|
||||||
} else {
|
|
||||||
console.log("The provided file is not an image.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
"No valid file provided in other_context_data.",
|
|
||||||
otherContextData?.name,
|
|
||||||
otherContextData?.type
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Running ASK for event listener: ", listener.description);
|
|
||||||
|
|
||||||
const system_prompts =
|
|
||||||
is_voice || is_new_todo_note || is_message_from_a_manager
|
|
||||||
? await buildSystemPrompts(contextMessage)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const prompt_heading = system_prompts
|
|
||||||
? ""
|
|
||||||
: `You are an Event Handler.`;
|
|
||||||
|
|
||||||
let prompt = `${prompt_heading}
|
|
||||||
You are called when an event triggers. Your task is to execute the user's instruction based on the triggered event and reply with the text to display as a notification to the user.
|
|
||||||
|
|
||||||
**Guidelines:**
|
|
||||||
|
|
||||||
- **Notification to User:**
|
|
||||||
- Any message you reply with will automatically be sent to the user as a notification.
|
|
||||||
- Do **not** indicate in the text that it is a notification.
|
|
||||||
|
|
||||||
- **Using Tools:**
|
|
||||||
- You have access to the necessary tools to execute the instruction; use them as needed.
|
|
||||||
- You also have access to the \`event_manager\` tool if you need to manage events or listeners (use it only if necessary).
|
|
||||||
|
|
||||||
- **Sending Messages:**
|
|
||||||
- **To the Current User:**
|
|
||||||
- Do **not** ask \`communication_manager\` tool. (if available)
|
|
||||||
- Simply reply with the message you want to send.
|
|
||||||
- **To Other Users:**
|
|
||||||
- Use the \`communication_manager\` tool. (if available)
|
|
||||||
- The message you reply with will still be sent to the current user as a notification.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
|
|
||||||
- **Instruction:** "When you get an email from John, tell John on WhatsApp that you got the email."
|
|
||||||
- **Steps:**
|
|
||||||
1. Use the \`communication_manager\` tool to send a message to John on WhatsApp.
|
|
||||||
- Use the WhatsApp ID from the payload to send the message instead of searching for the user.
|
|
||||||
2. Reply to the current user with "I have sent a message to John on WhatsApp that you got the email."
|
|
||||||
|
|
||||||
**Currently Triggered Event:**
|
|
||||||
|
|
||||||
- **Event ID:** ${eventId}
|
|
||||||
- **Description:** ${description}
|
|
||||||
- **Payload:** ${JSON.stringify(payload)}
|
|
||||||
- **Will Auto Notify Creator of Listener:** ${notify ? "Yes" : "No"}
|
|
||||||
- **Instruction:** ${listener.instruction}
|
|
||||||
|
|
||||||
**Action Required:**
|
|
||||||
- Follow the instruction provided in the payload.
|
|
||||||
- Return the notification text based on the instruction.
|
|
||||||
|
|
||||||
**Important Note:**
|
|
||||||
- If the above event and payload does **not** match the instruction, reply with the string **"IGNORE"** to skip executing the instruction for this payload.
|
|
||||||
|
|
||||||
`;
|
|
||||||
|
|
||||||
const voice_prompt = `You are in voice trigger mode.
|
|
||||||
|
|
||||||
The voice event that triggered this is:
|
|
||||||
- Event ID: ${eventId}
|
|
||||||
- Listener Description: ${description}
|
|
||||||
- Payload: ${JSON.stringify(payload)}
|
|
||||||
|
|
||||||
Do the instruction provided in the payload of the event listener.
|
|
||||||
|
|
||||||
Your response must be in plain text without markdown or any other formatting.
|
|
||||||
`;
|
|
||||||
|
|
||||||
const new_todo_note_prompt = `You are in new todo note trigger mode.
|
|
||||||
|
|
||||||
The user added a new todo note for you in your todos file which triggered this event.
|
|
||||||
|
|
||||||
Do not remove the to anya tag from the note if its present, unless explicitly asked to do so as part of the instruction.
|
|
||||||
|
|
||||||
Make sure to think about your process and how you want to step by step go about executing the todos.
|
|
||||||
|
|
||||||
You can mark a todo as failed by adding "[FAILED]" at the start of end of the todo line.
|
|
||||||
|
|
||||||
- Event ID: ${eventId}
|
|
||||||
- Payload: ${JSON.stringify(payload)}
|
|
||||||
|
|
||||||
IMPORTANT:
|
|
||||||
PLEASE ask notes manager to mark the note as done if you have completed the task, plz send the manager the todo note and the actual path of the note.
|
|
||||||
Whatever you reply with will be sent to the user as a notification automatically. Do not use communication_manager to notify the same user.
|
|
||||||
`;
|
|
||||||
|
|
||||||
const message_from_manager_prompt = `You just got a request from a manager.
|
|
||||||
|
|
||||||
The manager has sent you a message which triggered this event.
|
|
||||||
|
|
||||||
- Event ID: ${eventId}
|
|
||||||
- Payload: ${JSON.stringify(payload)}
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (system_prompts) {
|
|
||||||
prompt = `${system_prompts.map((p) => p.content).join("\n\n")}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let promptToUse = prompt;
|
|
||||||
let seed = `${listener.id}-${eventId}`;
|
|
||||||
|
|
||||||
if (is_voice) {
|
|
||||||
promptToUse = voice_prompt;
|
|
||||||
seed = `voice-anya-${listener.id}-${eventId}`;
|
|
||||||
} else if (is_new_todo_note) {
|
|
||||||
promptToUse = new_todo_note_prompt;
|
|
||||||
seed = `todos-from-user-${listener.id}-${eventId}`;
|
|
||||||
} else if (is_message_from_a_manager) {
|
|
||||||
promptToUse = message_from_manager_prompt;
|
|
||||||
seed = `message-from-manager-${listener.id}-${eventId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
console.time("ask");
|
||||||
|
// Send the final prompt to the model
|
||||||
const response = await ask({
|
const response = await ask({
|
||||||
model: attached_image ? "gpt-4o" : "gpt-4o-mini",
|
model: model,
|
||||||
prompt: promptToUse,
|
message,
|
||||||
image_url: attached_image ?? undefined,
|
prompt: finalPrompt,
|
||||||
seed,
|
image_url: attachedImage, // If there's an attached image base64
|
||||||
tools,
|
seed: `${eventId}-${listener.id}`,
|
||||||
|
tools: finalTools,
|
||||||
});
|
});
|
||||||
|
console.timeEnd("ask");
|
||||||
|
|
||||||
const content = response.choices[0].message.content ?? undefined;
|
const content = response.choices[0].message.content ?? "";
|
||||||
|
|
||||||
const ignore = content?.includes("IGNORE");
|
// Check if the response is "IGNORE"
|
||||||
|
if (content.includes("IGNORE")) {
|
||||||
if (ignore) {
|
|
||||||
console.log("Ignoring event: ", content, payload);
|
console.log("Ignoring event: ", content, payload);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a message to the user indicating the event was triggered
|
// Optionally notify the user
|
||||||
if (notify) {
|
if (notify) {
|
||||||
await contextMessage.send({
|
await contextMessage.send({
|
||||||
content,
|
content,
|
||||||
flags: is_voice && !is_new_todo_note ? [4096] : undefined,
|
flags: !awaiting ? undefined : [4096],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log("Silenced Notification: ", content);
|
console.log("Silenced Notification: ", content);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle auto-stop options
|
// Auto-stop if requested
|
||||||
if (options.autoStopAfterSingleEvent) {
|
if (options.autoStopAfterSingleEvent) {
|
||||||
await removeListener(listener.id, eventId);
|
await removeListener(listener.id, eventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
// Expiry is handled via periodic cleanup
|
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
console.error(
|
||||||
`❌ Listener "${listener.id}" has neither 'instruction' nor 'template' defined.`
|
`❌ Listener "${listener.id}" has neither 'instruction' nor 'template' defined.`
|
||||||
|
|
|
@ -64,6 +64,7 @@ import {
|
||||||
dockerToolManager,
|
dockerToolManager,
|
||||||
DockerToolManagerSchema,
|
DockerToolManagerSchema,
|
||||||
} from "./software-engineer";
|
} from "./software-engineer";
|
||||||
|
import { linear_manager_tool } from "./linear-manager";
|
||||||
|
|
||||||
// get time function
|
// get time function
|
||||||
const GetTimeParams = z.object({});
|
const GetTimeParams = z.object({});
|
||||||
|
@ -437,6 +438,10 @@ Try to fix any errors that are returned at least once before sending to the user
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "ProjectManager",
|
||||||
|
tool: linear_manager_tool(context_message),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "actionsManagerTool",
|
name: "actionsManagerTool",
|
||||||
tool: zodFunction({
|
tool: zodFunction({
|
||||||
|
|
|
@ -0,0 +1,728 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
import { zodFunction } from ".";
|
||||||
|
import { LinearClient } from "@linear/sdk";
|
||||||
|
import { Message } from "../interfaces/message";
|
||||||
|
import { userConfigs } from "../config";
|
||||||
|
import { ask } from "./ask";
|
||||||
|
import { RunnableToolFunction } from "openai/lib/RunnableFunction.mjs";
|
||||||
|
import { memory_manager_guide, memory_manager_init } from "./memory-manager";
|
||||||
|
|
||||||
|
// Parameter Schemas
|
||||||
|
export const IssueParams = z.object({
|
||||||
|
teamId: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
assigneeId: z.string().optional(),
|
||||||
|
priority: z.number().optional(),
|
||||||
|
labelIds: z.array(z.string()).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateIssueParams = z.object({
|
||||||
|
issueId: z.string().describe("The ID of the issue to update"),
|
||||||
|
title: z.string().optional().describe("The issue title"),
|
||||||
|
description: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe("The issue description in markdown format"),
|
||||||
|
stateId: z.string().optional().describe("The team state/status of the issue"),
|
||||||
|
assigneeId: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe("The identifier of the user to assign the issue to"),
|
||||||
|
priority: z
|
||||||
|
.number()
|
||||||
|
.min(0)
|
||||||
|
.max(4)
|
||||||
|
.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"),
|
||||||
|
removedLabelIds: z
|
||||||
|
.array(z.string())
|
||||||
|
.optional()
|
||||||
|
.describe(
|
||||||
|
"The identifiers of the issue labels to be removed from this issue"
|
||||||
|
),
|
||||||
|
labelIds: z
|
||||||
|
.array(z.string())
|
||||||
|
.optional()
|
||||||
|
.describe(
|
||||||
|
"The complete set of label IDs to set on the issue (replaces existing labels)"
|
||||||
|
),
|
||||||
|
autoClosedByParentClosing: z
|
||||||
|
.boolean()
|
||||||
|
.optional()
|
||||||
|
.describe(
|
||||||
|
"Whether the issue was automatically closed because its parent issue was closed"
|
||||||
|
),
|
||||||
|
boardOrder: z
|
||||||
|
.number()
|
||||||
|
.optional()
|
||||||
|
.describe("The position of the issue in its column on the board view"),
|
||||||
|
dueDate: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe("The date at which the issue is due (TimelessDate format)"),
|
||||||
|
parentId: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe("The identifier of the parent issue"),
|
||||||
|
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({
|
||||||
|
issueId: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SearchIssuesParams = z.object({
|
||||||
|
query: z.string().describe("Search query string"),
|
||||||
|
teamId: z.string().optional(),
|
||||||
|
limit: z.number().max(5).describe("Number of results to return (default: 1)"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ListTeamsParams = z.object({
|
||||||
|
limit: z.number().max(20).describe("Number of teams to return (default 3)"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AdvancedSearchIssuesParams = z.object({
|
||||||
|
query: z.string().optional(),
|
||||||
|
teamId: z.string().optional(),
|
||||||
|
assigneeId: z.string().optional(),
|
||||||
|
status: z
|
||||||
|
.enum(["backlog", "todo", "in_progress", "done", "canceled"])
|
||||||
|
.optional(),
|
||||||
|
priority: z.number().min(0).max(4).optional(),
|
||||||
|
orderBy: z
|
||||||
|
.enum(["createdAt", "updatedAt"])
|
||||||
|
.optional()
|
||||||
|
.describe("Order by, defaults to updatedAt"),
|
||||||
|
limit: z
|
||||||
|
.number()
|
||||||
|
.max(10)
|
||||||
|
.describe("Number of results to return (default: 5)"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SearchUsersParams = z.object({
|
||||||
|
query: z.string().describe("Search query for user names"),
|
||||||
|
limit: z
|
||||||
|
.number()
|
||||||
|
.max(10)
|
||||||
|
.describe("Number of results to return (default: 5)"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add new Project Parameter Schemas
|
||||||
|
export const ProjectParams = z.object({
|
||||||
|
name: z.string().describe("The name of the project"),
|
||||||
|
teamIds: z
|
||||||
|
.array(z.string())
|
||||||
|
.describe("The identifiers of the teams this project is associated with"),
|
||||||
|
description: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe("The description for the project"),
|
||||||
|
content: z.string().optional().describe("The project content as markdown"),
|
||||||
|
color: z.string().optional().describe("The color of the project"),
|
||||||
|
icon: z.string().optional().describe("The icon of the project"),
|
||||||
|
leadId: z.string().optional().describe("The identifier of the project lead"),
|
||||||
|
memberIds: z
|
||||||
|
.array(z.string())
|
||||||
|
.optional()
|
||||||
|
.describe("The identifiers of the members of this project"),
|
||||||
|
priority: z
|
||||||
|
.number()
|
||||||
|
.min(0)
|
||||||
|
.max(4)
|
||||||
|
.optional()
|
||||||
|
.describe(
|
||||||
|
"The priority of the project. 0 = No priority, 1 = Urgent, 2 = High, 3 = Normal, 4 = Low"
|
||||||
|
),
|
||||||
|
sortOrder: z
|
||||||
|
.number()
|
||||||
|
.optional()
|
||||||
|
.describe("The sort order for the project within shared views"),
|
||||||
|
prioritySortOrder: z
|
||||||
|
.number()
|
||||||
|
.optional()
|
||||||
|
.describe(
|
||||||
|
"[ALPHA] The sort order for the project within shared views, when ordered by priority"
|
||||||
|
),
|
||||||
|
startDate: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe("The planned start date of the project (TimelessDate format)"),
|
||||||
|
targetDate: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe("The planned target date of the project (TimelessDate format)"),
|
||||||
|
statusId: z.string().optional().describe("The ID of the project status"),
|
||||||
|
state: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe("[DEPRECATED] The state of the project"),
|
||||||
|
id: z.string().optional().describe("The identifier in UUID v4 format"),
|
||||||
|
convertedFromIssueId: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe("The ID of the issue from which that project is created"),
|
||||||
|
lastAppliedTemplateId: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe("The ID of the last template applied to the project"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateProjectParams = z.object({
|
||||||
|
projectId: z.string().describe("The ID of the project to update"),
|
||||||
|
name: z.string().optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
state: z
|
||||||
|
.enum(["planned", "started", "paused", "completed", "canceled"])
|
||||||
|
.optional(),
|
||||||
|
startDate: z.string().optional(),
|
||||||
|
targetDate: z.string().optional(),
|
||||||
|
sortOrder: z.number().optional(),
|
||||||
|
icon: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetProjectParams = z.object({
|
||||||
|
projectId: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SearchProjectsParams = z.object({
|
||||||
|
query: z.string().describe("Search query string"),
|
||||||
|
teamId: z.string().optional(),
|
||||||
|
limit: z.number().max(5).describe("Number of results to return (default: 1)"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add new ListProjectsParams schema after other params
|
||||||
|
export const ListProjectsParams = z.object({
|
||||||
|
teamId: z.string().optional().describe("Filter projects by team ID"),
|
||||||
|
limit: z
|
||||||
|
.number()
|
||||||
|
.max(20)
|
||||||
|
.describe("Number of projects to return (default: 10)"),
|
||||||
|
state: z
|
||||||
|
.enum(["planned", "started", "paused", "completed", "canceled"])
|
||||||
|
.optional()
|
||||||
|
.describe("Filter projects by state"),
|
||||||
|
});
|
||||||
|
|
||||||
|
interface SimpleIssue {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
status: string;
|
||||||
|
priority: number;
|
||||||
|
assignee?: string;
|
||||||
|
dueDate?: string;
|
||||||
|
labels?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SimpleTeam {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SimpleUser {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
displayName?: string;
|
||||||
|
avatarUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SimpleProject {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
state: string;
|
||||||
|
startDate?: string;
|
||||||
|
targetDate?: string;
|
||||||
|
description?: string;
|
||||||
|
teamIds: string[];
|
||||||
|
priority?: number;
|
||||||
|
leadId?: string;
|
||||||
|
memberIds?: string[];
|
||||||
|
color?: string;
|
||||||
|
icon?: string;
|
||||||
|
statusId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatIssue(issue: any): SimpleIssue {
|
||||||
|
return {
|
||||||
|
id: issue.id,
|
||||||
|
title: issue.title,
|
||||||
|
status: issue.state?.name || "Unknown",
|
||||||
|
priority: issue.priority,
|
||||||
|
assignee: issue.assignee?.name,
|
||||||
|
dueDate: issue.dueDate,
|
||||||
|
labels: issue.labels?.nodes?.map((l: any) => l.name) || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add after existing formatIssue function
|
||||||
|
function formatProject(project: any): SimpleProject {
|
||||||
|
return {
|
||||||
|
id: project.id,
|
||||||
|
name: project.name,
|
||||||
|
state: project.state,
|
||||||
|
startDate: project.startDate,
|
||||||
|
targetDate: project.targetDate,
|
||||||
|
description: project.description,
|
||||||
|
teamIds: project.teams?.nodes?.map((t: any) => t.id) || [],
|
||||||
|
priority: project.priority,
|
||||||
|
leadId: project.lead?.id,
|
||||||
|
memberIds: project.members?.nodes?.map((m: any) => m.id) || [],
|
||||||
|
color: project.color,
|
||||||
|
icon: project.icon,
|
||||||
|
statusId: project.status?.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// API Functions
|
||||||
|
async function createIssue(
|
||||||
|
client: LinearClient,
|
||||||
|
params: z.infer<typeof IssueParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return await client.createIssue(params);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateIssue(
|
||||||
|
client: LinearClient,
|
||||||
|
params: z.infer<typeof UpdateIssueParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { issueId, ...updateData } = params;
|
||||||
|
return await client.updateIssue(issueId, updateData);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getIssue(
|
||||||
|
client: LinearClient,
|
||||||
|
{ issueId }: z.infer<typeof GetIssueParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return await client.issue(issueId);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchIssues(
|
||||||
|
client: LinearClient,
|
||||||
|
{ query, teamId, limit }: z.infer<typeof SearchIssuesParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const searchParams: any = { first: limit };
|
||||||
|
if (teamId) {
|
||||||
|
searchParams.filter = { team: { id: { eq: teamId } } };
|
||||||
|
}
|
||||||
|
|
||||||
|
const issues = await client.issues({
|
||||||
|
...searchParams,
|
||||||
|
filter: {
|
||||||
|
or: [
|
||||||
|
{ title: { containsIgnoreCase: query } },
|
||||||
|
{ description: { containsIgnoreCase: query } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return issues.nodes.map(formatIssue);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listTeams(
|
||||||
|
client: LinearClient,
|
||||||
|
{ limit }: z.infer<typeof ListTeamsParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const teams = await client.teams({ first: limit });
|
||||||
|
return teams.nodes.map(
|
||||||
|
(team): SimpleTeam => ({
|
||||||
|
id: team.id,
|
||||||
|
name: team.name,
|
||||||
|
key: team.key,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function advancedSearchIssues(
|
||||||
|
client: LinearClient,
|
||||||
|
params: z.infer<typeof AdvancedSearchIssuesParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const filter: any = {};
|
||||||
|
if (params.teamId) filter.team = { id: { eq: params.teamId } };
|
||||||
|
if (params.assigneeId) filter.assignee = { id: { eq: params.assigneeId } };
|
||||||
|
if (params.status) filter.state = { type: { eq: params.status } };
|
||||||
|
if (params.priority) filter.priority = { eq: params.priority };
|
||||||
|
if (params.query) {
|
||||||
|
filter.or = [
|
||||||
|
{ title: { containsIgnoreCase: params.query } },
|
||||||
|
{ description: { containsIgnoreCase: params.query } },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const issues = await client.issues({
|
||||||
|
first: params.limit,
|
||||||
|
filter,
|
||||||
|
orderBy: params.orderBy || ("updatedAt" as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
return issues.nodes.map(formatIssue);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchUsers(
|
||||||
|
client: LinearClient,
|
||||||
|
{ query, limit }: z.infer<typeof SearchUsersParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const users = await client.users({
|
||||||
|
filter: {
|
||||||
|
or: [
|
||||||
|
{ name: { containsIgnoreCase: query } },
|
||||||
|
{ displayName: { containsIgnoreCase: query } },
|
||||||
|
{ email: { containsIgnoreCase: query } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
first: limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
return users.nodes.map(
|
||||||
|
(user): SimpleUser => ({
|
||||||
|
id: user.id,
|
||||||
|
name: user.name,
|
||||||
|
email: user.email,
|
||||||
|
displayName: user.displayName,
|
||||||
|
avatarUrl: user.avatarUrl,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new Project API Functions
|
||||||
|
async function createProject(
|
||||||
|
client: LinearClient,
|
||||||
|
params: z.infer<typeof ProjectParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return await client.createProject(params);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateProject(
|
||||||
|
client: LinearClient,
|
||||||
|
params: z.infer<typeof UpdateProjectParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { projectId, ...updateData } = params;
|
||||||
|
return await client.updateProject(projectId, updateData);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getProject(
|
||||||
|
client: LinearClient,
|
||||||
|
{ projectId }: z.infer<typeof GetProjectParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return await client.project(projectId);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify searchProjects function to handle empty queries
|
||||||
|
async function searchProjects(
|
||||||
|
client: LinearClient,
|
||||||
|
{ query, teamId, limit }: z.infer<typeof SearchProjectsParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const searchParams: any = { first: limit };
|
||||||
|
const filter: any = {};
|
||||||
|
|
||||||
|
if (teamId) {
|
||||||
|
filter.team = { id: { eq: teamId } };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
filter.or = [{ name: { containsIgnoreCase: query } }];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(filter).length > 0) {
|
||||||
|
searchParams.filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
const projects = await client.projects(searchParams);
|
||||||
|
return projects.nodes.map(formatProject);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new listProjects function
|
||||||
|
async function listProjects(
|
||||||
|
client: LinearClient,
|
||||||
|
{ teamId, limit, state }: z.infer<typeof ListProjectsParams>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const filter: any = {};
|
||||||
|
|
||||||
|
if (teamId) {
|
||||||
|
filter.team = { id: { eq: teamId } };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
filter.state = { eq: state };
|
||||||
|
}
|
||||||
|
|
||||||
|
const projects = await client.projects({
|
||||||
|
first: limit,
|
||||||
|
filter: Object.keys(filter).length > 0 ? filter : undefined,
|
||||||
|
orderBy: "updatedAt" as any,
|
||||||
|
});
|
||||||
|
|
||||||
|
return projects.nodes.map(formatProject);
|
||||||
|
} catch (error) {
|
||||||
|
return `Error: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main manager function
|
||||||
|
export const LinearManagerParams = z.object({
|
||||||
|
request: z
|
||||||
|
.string()
|
||||||
|
.describe("User's request regarding Linear project management"),
|
||||||
|
});
|
||||||
|
export type LinearManagerParams = z.infer<typeof LinearManagerParams>;
|
||||||
|
|
||||||
|
export async function linearManager(
|
||||||
|
{ request }: LinearManagerParams,
|
||||||
|
context_message: Message
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
"Context message",
|
||||||
|
context_message.author,
|
||||||
|
context_message.getUserRoles()
|
||||||
|
);
|
||||||
|
const userConfig = context_message.author.config;
|
||||||
|
|
||||||
|
// console.log("User config", userConfig);
|
||||||
|
|
||||||
|
const linearApiKey = userConfig?.identities.find(
|
||||||
|
(i) => i.platform === "linear_key"
|
||||||
|
)?.id;
|
||||||
|
|
||||||
|
// console.log("Linear API Key", linearApiKey);
|
||||||
|
|
||||||
|
const linearEmail = userConfig?.identities.find(
|
||||||
|
(i) => i.platform === "linear_email"
|
||||||
|
)?.id;
|
||||||
|
|
||||||
|
if (!linearApiKey) {
|
||||||
|
return {
|
||||||
|
response: "Please configure your Linear API key to use this tool.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new LinearClient({ apiKey: linearApiKey });
|
||||||
|
|
||||||
|
const linear_tools: RunnableToolFunction<any>[] = [
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => createIssue(client, params),
|
||||||
|
name: "linearCreateIssue",
|
||||||
|
schema: IssueParams,
|
||||||
|
description: "Create a new issue in Linear",
|
||||||
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => updateIssue(client, params),
|
||||||
|
name: "linearUpdateIssue",
|
||||||
|
schema: UpdateIssueParams,
|
||||||
|
description: "Update an existing issue in Linear",
|
||||||
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => getIssue(client, params),
|
||||||
|
name: "linearGetIssue",
|
||||||
|
schema: GetIssueParams,
|
||||||
|
description: "Get details of a specific issue",
|
||||||
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => searchUsers(client, params),
|
||||||
|
name: "linearSearchUsers",
|
||||||
|
schema: SearchUsersParams,
|
||||||
|
description:
|
||||||
|
"Search for users across the workspace by name, display name, or email. Use display name for better results.",
|
||||||
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => searchIssues(client, params),
|
||||||
|
name: "linearSearchIssues",
|
||||||
|
schema: SearchIssuesParams,
|
||||||
|
description:
|
||||||
|
"Search for issues in Linear using a query string. Optionally filter by team and limit results.",
|
||||||
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => listTeams(client, params),
|
||||||
|
name: "linearListTeams",
|
||||||
|
schema: ListTeamsParams,
|
||||||
|
description: "List all teams in the workspace with optional limit",
|
||||||
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => advancedSearchIssues(client, params),
|
||||||
|
name: "linearAdvancedSearchIssues",
|
||||||
|
schema: AdvancedSearchIssuesParams,
|
||||||
|
description:
|
||||||
|
"Search for issues with advanced filters including status, assignee, and priority",
|
||||||
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => createProject(client, params),
|
||||||
|
name: "linearCreateProject",
|
||||||
|
schema: ProjectParams,
|
||||||
|
description: "Create a new project in Linear",
|
||||||
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => updateProject(client, params),
|
||||||
|
name: "linearUpdateProject",
|
||||||
|
schema: UpdateProjectParams,
|
||||||
|
description: "Update an existing project in Linear",
|
||||||
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => getProject(client, params),
|
||||||
|
name: "linearGetProject",
|
||||||
|
schema: GetProjectParams,
|
||||||
|
description: "Get details of a specific project",
|
||||||
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => searchProjects(client, params),
|
||||||
|
name: "linearSearchProjects",
|
||||||
|
schema: SearchProjectsParams,
|
||||||
|
description:
|
||||||
|
"Search for projects in Linear using a query string. Optionally filter by team and limit results.",
|
||||||
|
}),
|
||||||
|
zodFunction({
|
||||||
|
function: (params) => listProjects(client, params),
|
||||||
|
name: "linearListProjects",
|
||||||
|
schema: ListProjectsParams,
|
||||||
|
description:
|
||||||
|
"List projects in Linear, optionally filtered by team and state. Returns most recently updated projects first.",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
// fetch all labels available in each team
|
||||||
|
const teams = await client.teams({ first: 10 });
|
||||||
|
const teamLabels = await client.issueLabels();
|
||||||
|
|
||||||
|
// list all the possible states of issues
|
||||||
|
const states = await client.workflowStates();
|
||||||
|
const state_values = states.nodes.map((state) => ({
|
||||||
|
id: state.id,
|
||||||
|
name: state.name,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Only include teams and labels in the context if they exist
|
||||||
|
const teamsContext =
|
||||||
|
teams.nodes.length > 0
|
||||||
|
? `Teams:\n${teams.nodes.map((team) => ` - ${team.name}`).join("\n")}`
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const labelsContext =
|
||||||
|
teamLabels.nodes.length > 0
|
||||||
|
? `All Labels:\n${teamLabels.nodes
|
||||||
|
.map((label) => ` - ${label.name} (${label.color})`)
|
||||||
|
.join("\n")}`
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const issueStateContext =
|
||||||
|
state_values.length > 0
|
||||||
|
? `All Issue States:\n${state_values
|
||||||
|
.map((state) => ` - ${state.name}`)
|
||||||
|
.join("\n")}`
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const workspaceContext = [teamsContext, labelsContext, issueStateContext]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n\n");
|
||||||
|
|
||||||
|
const response = await ask({
|
||||||
|
model: "gpt-4o-mini",
|
||||||
|
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.
|
||||||
|
|
||||||
|
----
|
||||||
|
${memory_manager_guide("linear_manager", context_message.author.id)}
|
||||||
|
----
|
||||||
|
|
||||||
|
${
|
||||||
|
workspaceContext
|
||||||
|
? `Here is some more context on current linear workspace:\n${workspaceContext}`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
The user you are currently assisting has the following details:
|
||||||
|
- Name: ${userConfig?.name}
|
||||||
|
- Linear Email: ${linearEmail}
|
||||||
|
|
||||||
|
When responding make sure to link the issues when returning the value.
|
||||||
|
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.
|
||||||
|
|
||||||
|
`,
|
||||||
|
message: request,
|
||||||
|
seed: `linear-${context_message.channelId}`,
|
||||||
|
tools: linear_tools.concat(
|
||||||
|
memory_manager_init(context_message, "linear_manager")
|
||||||
|
) as any,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { response };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const linear_manager_tool = (context_message: Message) =>
|
||||||
|
zodFunction({
|
||||||
|
function: (args) => linearManager(args, context_message),
|
||||||
|
name: "linear_manager",
|
||||||
|
schema: LinearManagerParams,
|
||||||
|
description: `Linear Issue Manager.
|
||||||
|
|
||||||
|
This tool allows you to create, update, close, or assign issues in Linear.
|
||||||
|
|
||||||
|
Provide detailed information to perform the requested action.
|
||||||
|
|
||||||
|
Use this when user explicitly asks for Linear/project management.`,
|
||||||
|
});
|
|
@ -49,9 +49,7 @@ export async function run_command({
|
||||||
wait = true,
|
wait = true,
|
||||||
stderr = true,
|
stderr = true,
|
||||||
stdout = true,
|
stdout = true,
|
||||||
}: RunCommandParams): Promise<{
|
}: RunCommandParams): Promise<unknown> {
|
||||||
results: Map<string, { success: boolean; output?: string; error?: string }>;
|
|
||||||
}> {
|
|
||||||
// Step 1: Check if the container exists and is running
|
// Step 1: Check if the container exists and is running
|
||||||
try {
|
try {
|
||||||
const isRunning =
|
const isRunning =
|
||||||
|
@ -72,31 +70,19 @@ export async function run_command({
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
results: new Map([
|
results: {
|
||||||
[
|
|
||||||
"container_creation",
|
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
error: createError.stderr || createError.message,
|
error: createError.stderr || createError.message,
|
||||||
|
message: "Error creating container",
|
||||||
},
|
},
|
||||||
],
|
|
||||||
]),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wait) {
|
if (!wait) {
|
||||||
// Return early if not waiting for command to finish
|
// Return early if not waiting for command to finish
|
||||||
|
const results = { results: "Command started" };
|
||||||
return {
|
return {
|
||||||
results: new Map([
|
results,
|
||||||
[
|
|
||||||
"background_execution",
|
|
||||||
{
|
|
||||||
success: true,
|
|
||||||
output: "Command execution started in the background.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,8 +120,9 @@ export async function run_command({
|
||||||
}
|
}
|
||||||
|
|
||||||
// All commands executed
|
// All commands executed
|
||||||
console.log("All commands executed.");
|
const resultsOb = Object.fromEntries(results);
|
||||||
return { results };
|
console.log("All commands executed.", resultsOb);
|
||||||
|
return { results: resultsOb };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tool definition for running commands in the Docker container
|
// Tool definition for running commands in the Docker container
|
||||||
|
@ -156,102 +143,39 @@ export async function dockerToolManager(
|
||||||
context_message: Message
|
context_message: Message
|
||||||
): Promise<{ response: string }> {
|
): Promise<{ response: string }> {
|
||||||
console.log("Docker Tool Manager invoked with message:", message);
|
console.log("Docker Tool Manager invoked with message:", message);
|
||||||
|
|
||||||
const toolsPrompt = `# You are Cody.
|
const toolsPrompt = `# You are Cody.
|
||||||
|
|
||||||
You are a software engineer, and someone who loves technology.
|
You are a software engineer, and someone who loves technology.
|
||||||
|
|
||||||
You specialize in linux and devops, and a python expert.
|
You specialize in linux and devops, and a python expert.
|
||||||
|
|
||||||
Using the above skills you can help the user with absolutely anything they ask for.
|
You also love to read markdown files to know more about why some files are the way they are.
|
||||||
Some examples include:
|
|
||||||
- Browsing the internet.
|
|
||||||
- Creating scripts for the user.
|
|
||||||
- Any automation Task.
|
|
||||||
|
|
||||||
You exist inside a docker container named '${containerName}'.
|
With the above expertise, you can do almost anything.
|
||||||
|
|
||||||
The current time is: ${new Date().toLocaleString()}.
|
Your home directory which has all your data is the /anya directory.
|
||||||
|
|
||||||
## Responsibilities:
|
This is your home your desktop and your playground to maintain and manage your tools and yourself.
|
||||||
1. You have access to a docker container of image python version 3.10 (based on Debian) that you can run commands on.
|
|
||||||
2. You can install software, update configurations, or run scripts in the environment.
|
|
||||||
3. You can presonalise the environment to your liking.
|
|
||||||
4. Help the user when they ask you for something to be done.
|
|
||||||
|
|
||||||
### Container details:
|
Each directory inside /anya and /anya itself has its purpose defined in the readme.md file in its root.
|
||||||
- The container is always running.
|
|
||||||
- The container has a volume mounted at /anya which persists data across container restarts.
|
|
||||||
- /anya is the only directory accessible to the user.
|
|
||||||
|
|
||||||
## The /anya/readme.md file
|
Rules when interacting with /anya:
|
||||||
|
1. Make sure to follow the instructions in the readme.md file in the root of the directory that you are trying to interact with.
|
||||||
|
2. Make sure you remember that your commands are run in a docker container using the docker exec command, which means your session is not persistent between commands. So generate your commands accordingly.
|
||||||
|
3. Any doubts you can try to figure out based on the markdown doc files you have access to, and if still not clear, you can ask user for more information.
|
||||||
|
|
||||||
1. You can use the file at /anya/readme.md to keep track of all the changes you make to the environment.
|
Use the above to help the user with their request.
|
||||||
|
|
||||||
2. These changes can include installing new software, updating configurations, or running scripts.
|
Current files in /anya are:
|
||||||
|
|
||||||
3. This file can also contain any account credentials or API keys that you saved with some description so that you know what they are for.
|
|
||||||
|
|
||||||
4. It is important that you keep /anya/readme.md updated as to not repeat yourself, the /anya/readme.md acts as your memory.
|
|
||||||
|
|
||||||
The current data from /anya/readme.md is:
|
|
||||||
\`\`\`
|
\`\`\`
|
||||||
${await $`cat /anya/readme.md`}
|
${await $`ls -l /anya`}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
You can also use /anya/memories/ directory to store even more specific information incase the /anya/readme.md file gets too big.
|
Current file structure in /anya is:
|
||||||
|
|
||||||
Current /anya/memories/ directory contents (tree /anya/memories/ command output):
|
|
||||||
\`\`\`
|
\`\`\`
|
||||||
${await $`tree /anya/memories/`}
|
${await $`tree /anya -L 2`}
|
||||||
\`\`\`
|
\`\`\
|
||||||
|
|
||||||
You can also save scripts in /anya/scripts/ directory and run them when needed.
|
|
||||||
|
|
||||||
Current /anya/scripts/ directory contents (ls /anya/scripts/ command output):
|
|
||||||
\`\`\`
|
|
||||||
${await $`ls /anya/scripts/`}
|
|
||||||
\`\`\`
|
|
||||||
This directory can contain both python or any language script based on your preference.
|
|
||||||
|
|
||||||
When you create a script in /anya/scripts/ directory you also should create a similarly named file prefixed with instruction_ that explains how to run the script.
|
|
||||||
|
|
||||||
This will help you run older scripts.
|
|
||||||
|
|
||||||
Each script you make should accept parameters as cli args and output the result to stdout, for at least the basic scripts to do one off tasks like youtube video downloads or getting transcripts etc.
|
|
||||||
|
|
||||||
If a script does not run or output as expected, consider this as an error and try to update and fix the script.
|
|
||||||
|
|
||||||
You can also keep all your python dependencies in a virtual env inside /anya/scripts/venv/ directory.
|
|
||||||
|
|
||||||
You can also use the /anya/media/ dir to store media files, You can arrange them in sub folders you create as needed.
|
|
||||||
|
|
||||||
Current /anya/media/ directory contents (ls /anya/media/ command output):
|
|
||||||
\`\`\`
|
|
||||||
${await $`ls /anya/media/`}
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
|
|
||||||
Example flow:
|
|
||||||
User: plz let me download this youtube video https://youtube.com/video
|
|
||||||
What you need to do:
|
|
||||||
1. Look at the /anya/scripts/ data if there is a script to download youtube videos.
|
|
||||||
2. If there is no script, create a new script to download youtube videos while taking the param as the youtube url and the output file path and save it in /anya/scripts/ directory and also create a instruction_download_youtube_video.md file.
|
|
||||||
3. look at the instruction_download_youtube_video.md file to see how to run that script.
|
|
||||||
4. Run the script with relavent params.
|
|
||||||
5. Update the /anya/readme.md file with the changes you had to make to the environment like installing dependencies or creating new scripts.
|
|
||||||
6. Reply with the file path of the youtube video, and anything else you want.
|
|
||||||
|
|
||||||
Example flow 2:
|
|
||||||
User: give me a transcript of this youtube video https://youtube.com/video
|
|
||||||
What you need to do:
|
|
||||||
1. Look at the /anya/scripts/ data if there is a script to get transcripts from youtube videos.
|
|
||||||
2. If there is no script, create a new script to get transcripts from youtube videos while taking the param as the youtube url and the output file path and save it in /anya/scripts/ directory and also create a instruction_get_youtube_transcript.md file.
|
|
||||||
3. look at the instruction_get_youtube_transcript.md file to see how to run that script.
|
|
||||||
4. Run the script with relavent params.
|
|
||||||
5. Return the transcript to the user.
|
|
||||||
6. Update the /anya/readme.md file with the changes you had to make to the environment like installing dependencies or creating new scripts, if the script was already present you can skip this step.
|
|
||||||
|
|
||||||
You can also leave notes for yourself in the same file for future reference of changes you make to your environment.
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Load tools for memory manager and Docker command execution
|
// Load tools for memory manager and Docker command execution
|
||||||
|
|
Loading…
Reference in New Issue