wip
This commit is contained in:
parent
ca7378f62e
commit
e68bf409f3
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { mkdir } from "node:fs/promises"
|
||||
import { dirname, join } from "node:path"
|
||||
import type { Reminder } from "./reminders"
|
||||
|
||||
const set = async <T extends keyof Keys>(key: T, value: Keys[T]) => {
|
||||
try {
|
||||
|
|
@ -87,7 +86,6 @@ export default { set, get, remove, update }
|
|||
|
||||
export type Keys = {
|
||||
threads: Record<string, TrackedThread> // threadId: thread metadata
|
||||
reminders: Reminder[]
|
||||
todos: Record<string, string> // todoId: todoText
|
||||
evaluations: EvalData[] // evaluation data for dashboard import
|
||||
}
|
||||
|
|
@ -99,7 +97,6 @@ export type TrackedThread = {
|
|||
|
||||
const keyVersions: Record<keyof Keys, number> = {
|
||||
threads: 1,
|
||||
reminders: 1,
|
||||
todos: 1,
|
||||
evaluations: 1,
|
||||
} as const
|
||||
|
|
|
|||
|
|
@ -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<Reminder[]> => {
|
||||
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
|
||||
}
|
||||
0
packages/spike/src/discord/commands/log.ts
Normal file
0
packages/spike/src/discord/commands/log.ts
Normal file
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
33
packages/spike/src/render.ts
Normal file
33
packages/spike/src/render.ts
Normal file
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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<TodoJSON> = 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<TodoList> = 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<any, UserContext>({
|
||||
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 = []
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user