diff --git a/packages/shared/package.json b/packages/shared/package.json index 6786984..08d2b13 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -5,7 +5,6 @@ "./kv": "./src/kv.ts", "./utils": "./src/utils.ts", "./log": "./src/log.ts", - "./reminders": "./src/reminders.ts", "./errors": "./src/errors.ts" }, "devDependencies": { diff --git a/packages/shared/src/kv.ts b/packages/shared/src/kv.ts index 7349886..1715e97 100644 --- a/packages/shared/src/kv.ts +++ b/packages/shared/src/kv.ts @@ -1,6 +1,5 @@ import { mkdir } from "node:fs/promises" import { dirname, join } from "node:path" -import type { Reminder } from "./reminders" const set = async (key: T, value: Keys[T]) => { try { @@ -87,7 +86,6 @@ export default { set, get, remove, update } export type Keys = { threads: Record // threadId: thread metadata - reminders: Reminder[] todos: Record // todoId: todoText evaluations: EvalData[] // evaluation data for dashboard import } @@ -99,7 +97,6 @@ export type TrackedThread = { const keyVersions: Record = { threads: 1, - reminders: 1, todos: 1, evaluations: 1, } as const diff --git a/packages/shared/src/reminders.ts b/packages/shared/src/reminders.ts deleted file mode 100644 index cc88ef0..0000000 --- a/packages/shared/src/reminders.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { DateTime } from "luxon" -import KV from "./kv" -import { ensure, zone } from "./utils.ts" - -export type Reminder = { - id: string - title: string - dueDate: string // ISO string - assignee?: User - status: "pending" | "completed" | "ignored" | "overdue" -} - -export const users = ["chris", "corey"] as const -export type User = (typeof users)[number] - -export const addReminder = async (title: string, dueDateString: string, assignee?: User) => { - const dueDate = DateTime.fromISO(dueDateString, { zone }) - ensure(dueDate.isValid, `Invalid due date "${dueDateString}"`) - - const guid = crypto.randomUUID() - const newReminder: Reminder = { - id: guid, - title, - dueDate: dueDate.toISO(), - status: "pending", - assignee: assignee || undefined, - } - - await KV.update("reminders", [], (reminders: Reminder[]) => { - return [...reminders, newReminder] - }) - - return newReminder -} - -export const getPendingReminders = async (assignee?: User): Promise => { - let reminders = await KV.get("reminders", []) - reminders = reminders.filter((reminder) => { - if (reminder.status !== "pending") return false - if (assignee && reminder.assignee !== assignee) return false - return true - }) - reminders.sort((a, b) => DateTime.fromISO(a.dueDate).toMillis() - DateTime.fromISO(b.dueDate).toMillis()) - - return reminders -} - -type ReminderUpdate = { - title?: string - assignee?: User - dueDateString?: string - status?: Reminder["status"] -} -export const updateReminder = async (id: string, updates: ReminderUpdate) => { - let reminder - await KV.update("reminders", [], async (reminders: Reminder[]) => { - reminder = reminders.find((r) => r.id === id) - ensure(reminder, `Reminder with id "${id}" not found`) - - reminder.title = updates.title ?? reminder.title - reminder.status = updates.status ?? reminder.status - reminder.assignee = updates.assignee ?? reminder.assignee - - if (updates.dueDateString) { - const dueDate = DateTime.fromISO(updates.dueDateString, { zone }) - ensure(dueDate.isValid, `Invalid due date "${updates.dueDateString}"`) - reminder.dueDate = dueDate.toISO() - } - - return reminders - }) - - return reminder -} - -export const deleteReminder = async (id: string) => { - let deletedReminder: Reminder | undefined - - await KV.update("reminders", [], (reminders: Reminder[]) => { - const reminderIndex = reminders.findIndex((r) => r.id === id) - ensure(reminderIndex !== -1, `Reminder with id "${id}" not found`) - reminders.splice(reminderIndex, 1) - return reminders - }) - - return deletedReminder -} diff --git a/packages/spike/src/discord/commands/log.ts b/packages/spike/src/discord/commands/log.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/spike/src/discord/cron.ts b/packages/spike/src/discord/cron.ts index 535acb4..5a53292 100644 --- a/packages/spike/src/discord/cron.ts +++ b/packages/spike/src/discord/cron.ts @@ -1,9 +1,4 @@ -import { getPendingReminders, updateReminder } from "@workshop/shared/reminders" -import { DateTime } from "luxon" import { Client } from "discord.js" -import { buildInstructions } from "../instructions" -import { respondToSystemMessage } from "../ai" -import { ensure } from "@workshop/shared/utils" const channelId = process.env.CHANNEL_ID ?? "1382121375619223594" @@ -13,32 +8,6 @@ export const runCronJobs = async (client: Client) => { setInterval(async () => { try { - const nextDueDate = DateTime.now().plus({ minutes: minuteBetweenCronJobs }) - const reminders = await getPendingReminders() - // show reminders that were due after the last checked time and before the next due date - const upcomingReminders = reminders.filter((reminder) => { - const reminderDueDate = DateTime.fromISO(reminder.dueDate) - return reminderDueDate <= nextDueDate - }) - - if (upcomingReminders.length > 0) { - console.log(`Found ${upcomingReminders.length} upcoming reminders to notify.`) - const content = `These reminders are due soon, let them know!: ${JSON.stringify(upcomingReminders)}` - - const output = await respondToSystemMessage(content, channelId) - ensure(output, "The response to reminders should not be undefined") - - for (let reminder of upcomingReminders) { - await updateReminder(reminder.id, { status: "overdue" }) - } - - const channel = await client.channels.fetch(channelId) - if (channel?.isSendable()) { - channel.send(output) - } else { - console.warn(`Channel ${channel} not found or not for reminder.`) - } - } } catch (error) { console.error("Error running cron job:", error) } diff --git a/packages/spike/src/render.ts b/packages/spike/src/render.ts new file mode 100644 index 0000000..d8c36de --- /dev/null +++ b/packages/spike/src/render.ts @@ -0,0 +1,33 @@ +import { ensure } from "@workshop/shared/utils" + +export const getLogs = async (type: "app" | "request" | "build" = "app") => { + const ownerId = "tea-d1vamb95pdvs73d1sgtg" + const resourceId = "srv-d1vrdqmuk2gs73eop8o0" + + const url = new URL("https://api.render.com/v1/logs") + url.searchParams.set("type", type) + url.searchParams.set("ownerId", ownerId) + url.searchParams.set("direction", "backward") + url.searchParams.set("resource", resourceId) + url.searchParams.set("limit", "100") + + const response = await fetch(url.toString(), { + method: "GET", + headers: { + accept: "application/json", + authorization: `Bearer ${process.env.RENDER_API_KEY}`, + }, + }) + if (!response.ok) { + throw new Error(`Failed to fetch logs: ${response.statusText}`) + } + + const data = (await response.json()) as any + ensure(data.logs, "Expected logs to be an array") + const logs = data.logs.map((log: any) => { + const { id, labels, ...rest } = log + return rest + }) + + return logs +} diff --git a/packages/spike/src/tools.ts b/packages/spike/src/tools.ts index 569a437..215d2d3 100644 --- a/packages/spike/src/tools.ts +++ b/packages/spike/src/tools.ts @@ -1,188 +1 @@ -import { addReminder, getPendingReminders, updateReminder, users } from "@workshop/shared/reminders" -import kv from "@workshop/shared/kv" -import { z } from "zod" -import { tool } from "@openai/agents" -import { type TodoJSON, type TodoList, todoListToString } from "@workshop/todo" -import { type UserContext } from "./ai" -import { User } from "discord.js" -import { ensure, zone } from "@workshop/shared/utils" -import { DateTime } from "luxon" - -// Recursive Zod schema for TodoJSON -const todoJSONSchema: z.ZodType = z.lazy(() => - z.object({ - done: z.boolean(), - indent: z.string().describe("Indentation string of the todo item"), - text: z.string(), - children: z.array(todoJSONSchema), - }) -) - -// Zod schema for TodoList -const todoListSchema: z.ZodType = z.array( - z.object({ - title: z.string().nullable().optional(), - todos: z - .array(z.union([todoJSONSchema, z.string()])) - .describe("Array of TodoJSON objects or empty lines"), - }) -) - -export const tools = [ - tool({ - name: "getDiscordThreadInformation", - - description: - "Get information about all threads in the current discord guild (including a link, name, and when it will be archived)", - parameters: z.object({}), - execute: async (args, runContext) => { - try { - const guild = runContext?.context.msg.guild - ensure(guild, "Guild is required for getThreadInformation tool") - - const threads = await guild.channels.fetchActiveThreads() - const threadInfo = Array.from(threads.threads.values()).map((thread) => ({ - name: thread.name, - archived: thread.archived, - link: thread.url, - archiveTimestamp: DateTime.fromMillis(thread.archiveTimestamp || 0) - .setZone(zone) - .toISO(), - })) - - return threadInfo - } catch (error) { - console.error(`Tool "getThreadInformation" failed for "${args.channelId}":`, error) - return `toolcall failed. ${error}` - } - }, - }), - tool({ - name: "addReminder", - description: "Add a new reminder to the list", - parameters: z.object({ - title: z.string().describe("The title or description of the reminder"), - dueDate: z - .string() - .describe("The due date for the reminder in ISO format (YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS)"), - assignee: z.enum(users).nullable().describe("The user to assign the reminder to"), - }), - execute: async (args) => { - try { - const reminder = await addReminder(args.title, args.dueDate, args.assignee || undefined) - return reminder - } catch (error) { - console.error(`Tool "addReminder" failed for "${JSON.stringify(args)}":`, error) - return `toolcall failed. ${error}` - } - }, - }), - - tool({ - name: "getReminders", - description: "Get all reminders, optionally filtered by status", - parameters: z.object({ - assignee: z.enum(users).describe("Filter reminders by assignee. If empty, returns all reminders."), - }), - execute: async (args) => { - try { - const reminders = await getPendingReminders(args.assignee) - - return reminders - } catch (error) { - console.error(`Tool "getReminders" failed`, error) - return `toolcall failed. ${error}` - } - }, - }), - - tool({ - name: "updateReminder", - description: "Update an existing reminder's title, due date, or status", - parameters: z.object({ - id: z.string().describe("The id of the reminder to update"), - title: z.string().nullable().describe("The new title for the reminder"), - dueDate: z.string().nullable().describe("The new due date for the reminder in ISO format"), - assignee: z.enum(users).nullable().describe("The new assignee for the reminder"), - status: z - .enum(["pending", "completed", "ignored"]) - .nullable() - .describe("The new status for the reminder"), - }), - execute: async (args) => { - try { - const updatedReminder = await updateReminder(args.id, { - title: args.title || undefined, - dueDateString: args.dueDate || undefined, - status: args.status || undefined, - assignee: args.assignee || undefined, - }) - - return updatedReminder - } catch (error) { - console.error(`Tool "updateReminder" failed`, error) - return `toolcall failed. ${error}` - } - }, - }), - - tool({ - name: "getTodoListNames", - description: "Get the names of all todo lists", - parameters: z.object({}), - execute: async () => { - try { - const lists = await kv.get("todos", {}) - return Object.keys(lists) - } catch (error) { - console.error(`Tool "getTodoListNames" failed`, error) - return `toolcall failed. ${error}` - } - }, - }), - - tool({ - name: "getTodoList", - description: "Get a todo list by name", - parameters: z.object({ - name: z.string().describe("The name of the todo list to retrieve"), - }), - execute: async (args) => { - try { - const lists = await kv.get("todos", {}) - const list = lists[args.name] - - if (!list) { - return `Todo list "${args.name}" not found.` - } - - return list - } catch (error) { - console.error(`Tool "getTodoList" failed for "${args.name}":`, error) - return `toolcall failed. ${error}` - } - }, - }), - - tool({ - name: "updateTodoList", - description: "Update a todo list by name", - parameters: z.object({ - name: z.string().describe("The name of the todo list to update"), - todoList: todoListSchema.describe("The todo list as a TodoList object"), - }), - execute: async (args) => { - try { - await kv.update("todos", {}, (todos) => { - todos[args.name] = todoListToString(args.todoList) - return todos - }) - - return `Todo list "${args.name}" updated successfully.` - } catch (error) { - console.error(`Tool "updateTodoList" failed for "${args.name}":`, error) - return `toolcall failed. ${error}` - } - }, - }), -] +export const tools = []