Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
13 kB ยท 427 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428import { z } from "zod";
import { attributedUserSchema, toAgentUser } from "../attribution";import { addMaintenanceUpdate, createMaintenance, deleteMaintenanceUpdate, listMaintenances, notifyMaintenance, updateMaintenanceUpdate,} from "../maintenance";import type { AgentTool } from "./types";
const PER_PAGE_DEFAULT = 50;const PER_PAGE_MAX = 200;
function formatMaintenanceDate(iso: string): string { const d = new Date(iso); if (Number.isNaN(d.getTime())) return iso; return d.toLocaleString("en-US", { weekday: "short", year: "numeric", month: "short", day: "numeric", hour: "numeric", minute: "2-digit", timeZoneName: "short", });}
const ListMaintenancesInputShape = z.object({ pageId: z .number() .int() .optional() .describe("If set, only maintenances attached to this page id."), page: z .number() .int() .min(1) .default(1) .describe("1-indexed page number (default 1)."), perPage: z .number() .int() .min(1) .max(PER_PAGE_MAX) .default(PER_PAGE_DEFAULT) .describe( `Items per page (default ${PER_PAGE_DEFAULT}, max ${PER_PAGE_MAX}).`, ),});
const ListMaintenancesOutput = z.object({ items: z.array( z.object({ id: z.number().int(), title: z.string(), message: z.string(), from: z.string(), to: z.string(), pageId: z.number().int().nullable(), pageComponentIds: z.array(z.number().int()), updates: z.array( z.object({ id: z.number().int(), message: z.string(), date: z.string(), }), ), createdBy: attributedUserSchema.nullable(), updatedBy: attributedUserSchema.nullable(), }), ), pagination: z.object({ page: z.number().int(), perPage: z.number().int(), totalSize: z.number().int(), totalPages: z.number().int(), }),});
export const listMaintenancesTool: AgentTool< z.infer<typeof ListMaintenancesInputShape>, z.infer<typeof ListMaintenancesOutput>> = { name: "list_maintenances", description: "List maintenance windows in this workspace, newest first. Returns timeline update ids so edit/delete can target a specific entry. Paginated via `page` (1-indexed) and `perPage`.", scope: "read", destructive: false, inputSchema: ListMaintenancesInputShape, outputSchema: ListMaintenancesOutput, async run({ ctx, input }) { const { pageId, page, perPage } = input; const result = await listMaintenances({ ctx, input: { limit: perPage, offset: (page - 1) * perPage, pageId, order: "desc", }, }); return { items: result.items.map((m) => ({ id: m.id, title: m.title, message: m.message, from: m.from.toISOString(), to: m.to.toISOString(), pageId: m.pageId, pageComponentIds: m.pageComponentIds, updates: m.updates.map((u) => ({ id: u.id, message: u.message, date: u.date.toISOString(), })), createdBy: toAgentUser(m.createdByUser), updatedBy: toAgentUser(m.updatedByUser), })), pagination: { page, perPage, totalSize: result.totalSize, totalPages: Math.max(1, Math.ceil(result.totalSize / perPage)), }, }; },};
const CreateMaintenanceInputShape = z.object({ title: z.string().min(1).max(256).describe("Short, public-facing title."), message: z .string() .min(1) .describe( "Public message describing the work; posted as the first timeline update.", ), from: z.iso .datetime() .describe("Start time, ISO 8601 (e.g. 2026-04-30T14:00:00Z)."), to: z.iso .datetime() .describe("End time, ISO 8601. Must be strictly after `from`."), pageId: z .number() .int() .describe( "Status page to attach to. Resolve via list_status_pages โ never guess.", ), pageComponentIds: z .array(z.number().int()) .default([]) .describe( "Optional component ids affected by the maintenance. Resolve via list_page_components({ pageId }) โ never guess. Must belong to pageId.", ), notify: z .boolean() .describe( "Whether to dispatch subscriber notifications. No retroactive notify โ false means subscribers will never hear about this window.", ),});
const CreateMaintenanceOutput = z.object({ id: z.number().int(), title: z.string(), from: z.string(), to: z.string(), pageId: z.number().int().nullable(), notified: z.boolean(),});
export const createMaintenanceTool: AgentTool< z.infer<typeof CreateMaintenanceInputShape>, z.infer<typeof CreateMaintenanceOutput>> = { name: "create_maintenance", description: "Schedule a maintenance window on a status page. PUBLIC, AUDIT-LOGGED, AND POTENTIALLY NOTIFIES SUBSCRIBERS โ irreversible side effects. Subscriber notifications dispatch as part of this call only โ no retroactive notify. The maintenance row persists even if the notify dispatch fails; `notified` reports the actual outcome. pageId MUST come from list_status_pages โ never guess. pageComponentIds (if supplied) MUST come from list_page_components({ pageId }) and belong to the same page.", scope: "write", destructive: true, inputSchema: CreateMaintenanceInputShape, outputSchema: CreateMaintenanceOutput, approval: { extraFlags: [{ id: "notify", label: "Notify subscribers" }], applyFlags: (input, flags) => ({ ...input, notify: flags.notify ?? false }), summarize: (input) => ({ title: `Schedule Maintenance: ${input.title}`, lines: [ { label: "Title", value: input.title }, { label: "Page ID", value: String(input.pageId), ref: { kind: "page", pageId: input.pageId }, }, { label: "From", value: formatMaintenanceDate(input.from) }, { label: "To", value: formatMaintenanceDate(input.to) }, ...(input.pageComponentIds?.length ? [ { label: "Components", value: input.pageComponentIds.join(", "), ref: { kind: "components" as const, componentIds: input.pageComponentIds, }, }, ] : []), { label: "Message", value: input.message }, ], }), verb: "scheduled", }, async run({ ctx, input }) { const { maintenance: record, initialUpdate } = await createMaintenance({ ctx, input: { title: input.title, message: input.message, from: new Date(input.from), to: new Date(input.to), pageId: input.pageId, pageComponentIds: input.pageComponentIds ?? [], }, }); let notified = false; if (input.notify) { try { notified = await notifyMaintenance({ ctx, input: { maintenanceUpdateId: initialUpdate.id }, }); } catch (err) { console.warn("notifyMaintenance failed after create_maintenance", err); } } return { id: record.id, title: record.title, from: record.from.toISOString(), to: record.to.toISOString(), pageId: record.pageId, notified, }; },};
const AddMaintenanceUpdateInputShape = z.object({ maintenanceId: z .number() .int() .describe("Maintenance id from list_maintenances โ never guess."), message: z.string().min(1).describe("Public update message."), date: z.iso .datetime() .optional() .describe("Update time in ISO 8601. Defaults to now."), notify: z .boolean() .describe("Whether to dispatch subscriber notifications for this update."),});
const MaintenanceUpdateOutput = z.object({ id: z.number().int(), maintenanceId: z.number().int(), message: z.string(), date: z.string(), notified: z.boolean().optional(),});
export const addMaintenanceUpdateTool: AgentTool< z.infer<typeof AddMaintenanceUpdateInputShape>, z.infer<typeof MaintenanceUpdateOutput>> = { name: "add_maintenance_update", description: "Append a public update to an existing maintenance. PUBLIC, AUDIT-LOGGED, AND POTENTIALLY NOTIFIES SUBSCRIBERS. The maintenanceId MUST come from list_maintenances. Subscriber notification is only available during this call.", scope: "write", destructive: true, inputSchema: AddMaintenanceUpdateInputShape, outputSchema: MaintenanceUpdateOutput, approval: { extraFlags: [{ id: "notify", label: "Notify subscribers" }], applyFlags: (input, flags) => ({ ...input, notify: flags.notify ?? false }), summarize: (input) => ({ title: `Publish Maintenance Update #${input.maintenanceId}`, lines: [ { label: "Maintenance ID", value: String(input.maintenanceId) }, ...(input.date ? [{ label: "Date", value: formatMaintenanceDate(input.date) }] : []), { label: "Message", value: input.message }, ], }), verb: "published", }, async run({ ctx, input }) { const update = await addMaintenanceUpdate({ ctx, input: { maintenanceId: input.maintenanceId, message: input.message, date: input.date ? new Date(input.date) : undefined, }, }); let notified = false; if (input.notify) { try { notified = await notifyMaintenance({ ctx, input: { maintenanceUpdateId: update.id }, }); } catch (err) { console.warn( "notifyMaintenance failed after add_maintenance_update", err, ); } } return { id: update.id, maintenanceId: update.maintenanceId, message: update.message, date: update.date.toISOString(), notified, }; },};
const UpdateMaintenanceUpdateInputShape = z .object({ id: z .number() .int() .describe( "Maintenance update id from list_maintenances (or create/add result) โ never guess.", ), message: z .string() .min(1) .optional() .describe("Replacement public message."), date: z.iso.datetime().optional().describe("Replacement ISO 8601 date."), }) .refine((input) => input.message !== undefined || input.date !== undefined, { message: "At least one field must be provided.", });
export const updateMaintenanceUpdateTool: AgentTool< z.infer<typeof UpdateMaintenanceUpdateInputShape>, z.infer<typeof MaintenanceUpdateOutput>> = { name: "update_maintenance_update", description: "Edit an existing maintenance timeline entry. PUBLIC and AUDIT-LOGGED. Does not notify subscribers. The update id MUST come from list_maintenances (or a prior create/add result).", scope: "write", destructive: true, inputSchema: UpdateMaintenanceUpdateInputShape, outputSchema: MaintenanceUpdateOutput, approval: { summarize: (input) => ({ title: `Edit Maintenance Update #${input.id}`, lines: [ ...(input.date ? [{ label: "Date", value: formatMaintenanceDate(input.date) }] : []), ...(input.message ? [{ label: "Message", value: input.message }] : []), ], }), verb: "updated", }, async run({ ctx, input }) { const update = await updateMaintenanceUpdate({ ctx, input: { id: input.id, message: input.message, date: input.date ? new Date(input.date) : undefined, }, }); return { id: update.id, maintenanceId: update.maintenanceId, message: update.message, date: update.date.toISOString(), }; },};
const DeleteMaintenanceUpdateInputShape = z.object({ id: z .number() .int() .describe( "Maintenance update id from list_maintenances (or create/add result) โ never guess.", ),});const DeleteMaintenanceUpdateOutput = z.object({ id: z.number().int(), success: z.boolean(),});
export const deleteMaintenanceUpdateTool: AgentTool< z.infer<typeof DeleteMaintenanceUpdateInputShape>, z.infer<typeof DeleteMaintenanceUpdateOutput>> = { name: "delete_maintenance_update", description: "Delete a maintenance timeline entry. PUBLIC, AUDIT-LOGGED, AND IRREVERSIBLE. A maintenance must retain at least one update. The update id MUST come from list_maintenances (or a prior create/add result).", scope: "write", destructive: true, inputSchema: DeleteMaintenanceUpdateInputShape, outputSchema: DeleteMaintenanceUpdateOutput, approval: { summarize: (input) => ({ title: `Delete Maintenance Update #${input.id}`, lines: [{ label: "Update ID", value: String(input.id) }], }), verb: "deleted", }, async run({ ctx, input }) { await deleteMaintenanceUpdate({ ctx, input }); return { id: input.id, success: true }; },};