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 ยท 447 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448import { monitorStatusSchema } from "@openstatus/db/src/schema";import { z } from "zod";
import { type MonitorTimeRange, getMonitor, getMonitorStatus, getMonitorSummary, getResponseLog, listMonitors, listResponseLogs, monitorTimeRange,} from "../monitor";import type { AgentTool } from "./types";
const LIST_PER_PAGE_DEFAULT = 25;const LIST_PER_PAGE_MAX = 50;
const LIST_RESPONSE_LOGS_LIMIT_DEFAULT = 25;const LIST_RESPONSE_LOGS_LIMIT_MAX = 100;
const ListMonitorsInputShape = z.object({ page: z .number() .int() .min(1) .default(1) .describe("1-indexed page number (default 1)."), perPage: z .number() .int() .min(1) .max(LIST_PER_PAGE_MAX) .default(LIST_PER_PAGE_DEFAULT) .describe( `Items per page (default ${LIST_PER_PAGE_DEFAULT}, max ${LIST_PER_PAGE_MAX}).`, ),});
const ListMonitorsOutput = z.object({ items: z.array( z.object({ id: z.number().int(), name: z.string(), url: z.string(), jobType: z.string(), active: z.boolean(), periodicity: z.string(), regions: z.array(z.string()), tags: z.array(z.string()), activeIncidentCount: z.number().int(), }), ), pagination: z.object({ page: z.number().int(), perPage: z.number().int(), totalSize: z.number().int(), totalPages: z.number().int(), }),});
export const listMonitorsTool: AgentTool< z.infer<typeof ListMonitorsInputShape>, z.infer<typeof ListMonitorsOutput>> = { name: "list_monitors", description: "List monitors in this workspace, newest first. Use to discover the numeric monitorId required by get_monitor, get_monitor_status, get_monitor_summary, list_response_logs. Paginated via `page` (1-indexed) and `perPage`.", scope: "read", destructive: false, inputSchema: ListMonitorsInputShape, outputSchema: ListMonitorsOutput, async run({ ctx, input }) { const { page, perPage } = input; const result = await listMonitors({ ctx, input: { limit: perPage, offset: (page - 1) * perPage, order: "desc", }, }); return { items: result.items.map((m) => ({ id: m.id, name: m.name, url: m.url, jobType: m.jobType, active: m.active ?? false, periodicity: m.periodicity, regions: m.regions, tags: m.tags.map((t) => t.name), activeIncidentCount: m.incidents.filter((i) => i.resolvedAt === null) .length, })), pagination: { page, perPage, totalSize: result.totalSize, totalPages: Math.max(1, Math.ceil(result.totalSize / perPage)), }, }; },};
const GetMonitorInputShape = z.object({ monitorId: z .number() .int() .describe("Monitor id (numeric). Resolve via list_monitors โ never guess."),});
const GetMonitorOutput = z.object({ id: z.number().int(), name: z.string(), url: z.string(), jobType: z.string(), method: z.string().nullable(), active: z.boolean(), periodicity: z.string(), regions: z.array(z.string()), timeout: z.number().int(), degradedAfter: z.number().int().nullable(), retry: z.number().int(), followRedirects: z.boolean(), public: z.boolean(), tags: z.array(z.object({ id: z.number().int(), name: z.string() })), notifications: z.array( z.object({ id: z.number().int(), name: z.string(), provider: z.string(), }), ), privateLocationIds: z.array(z.number().int()),});
export const getMonitorTool: AgentTool< z.infer<typeof GetMonitorInputShape>, z.infer<typeof GetMonitorOutput>> = { name: "get_monitor", description: "Get the full configuration for a single monitor: URL, regions, periodicity, retry/timeout, notification channels, tags. Use after list_monitors when the user asks for detail on a specific monitor. Does NOT return latency or status โ use get_monitor_summary or get_monitor_status for that.", scope: "read", destructive: false, inputSchema: GetMonitorInputShape, outputSchema: GetMonitorOutput, async run({ ctx, input }) { const m = await getMonitor({ ctx, input: { id: input.monitorId } }); return { id: m.id, name: m.name, url: m.url, jobType: m.jobType, method: m.method ?? null, active: m.active ?? false, periodicity: m.periodicity, regions: m.regions, timeout: m.timeout, degradedAfter: m.degradedAfter ?? null, retry: m.retry, followRedirects: m.followRedirects ?? false, public: m.public ?? false, tags: m.tags.map((t) => ({ id: t.id, name: t.name })), notifications: m.notifications.map((n) => ({ id: n.id, name: n.name, provider: n.provider, })), privateLocationIds: m.privateLocations.map((p) => p.id), }; },};
const GetMonitorStatusInputShape = z.object({ monitorId: z .number() .int() .describe("Monitor id (numeric). Resolve via list_monitors โ never guess."),});
const GetMonitorStatusOutput = z.object({ monitorId: z.number().int(), regions: z.array( z.object({ region: z.string(), status: monitorStatusSchema, }), ),});
export const getMonitorStatusTool: AgentTool< z.infer<typeof GetMonitorStatusInputShape>, z.infer<typeof GetMonitorStatusOutput>> = { name: "get_monitor_status", description: "Per-region health for a monitor (one row per configured region: active/degraded/error). Report at the worst region's level โ DO NOT invent a composite 'overall' label. Use before drafting a status report so the public message reflects the actual blast radius.", scope: "read", destructive: false, inputSchema: GetMonitorStatusInputShape, outputSchema: GetMonitorStatusOutput, async run({ ctx, input }) { const result = await getMonitorStatus({ ctx, input: { monitorId: input.monitorId }, }); return { monitorId: result.id, regions: result.regions.map((r) => ({ region: r.region, status: r.status, })), }; },};
const GetMonitorSummaryInputShape = z.object({ monitorId: z .number() .int() .describe("Monitor id (numeric). Resolve via list_monitors โ never guess."), timeRange: z .enum(monitorTimeRange) .default("1d") .describe("Aggregation window: 1d (default), 7d, 14d."), regions: z .array(z.string()) .optional() .describe( "Optional region filter. Defaults to the monitor's currently configured regions.", ),});
const GetMonitorSummaryOutput = z.object({ monitorId: z.number().int(), timeRange: z.enum(monitorTimeRange), regions: z.array(z.string()), totalSuccessful: z.number().int(), totalDegraded: z.number().int(), totalFailed: z.number().int(), p50: z.number().int(), p75: z.number().int(), p90: z.number().int(), p95: z.number().int(), p99: z.number().int(), lastPingAt: z.string(),});
export const getMonitorSummaryTool: AgentTool< z.infer<typeof GetMonitorSummaryInputShape>, z.infer<typeof GetMonitorSummaryOutput>> = { name: "get_monitor_summary", description: "Aggregate health metrics for a monitor over a time window: success/degraded/error counts and p50โp99 latency. Default 1d. Pair with get_monitor_status for per-region detail.", scope: "read", destructive: false, inputSchema: GetMonitorSummaryInputShape, outputSchema: GetMonitorSummaryOutput, async run({ ctx, input }) { const result = await getMonitorSummary({ ctx, input: { monitorId: input.monitorId, timeRange: input.timeRange, regions: input.regions, }, }); return { monitorId: result.monitorId, timeRange: input.timeRange, regions: result.regions, totalSuccessful: result.totalSuccessful, totalDegraded: result.totalDegraded, totalFailed: result.totalFailed, p50: result.p50, p75: result.p75, p90: result.p90, p95: result.p95, p99: result.p99, lastPingAt: result.lastPingAt, }; },};
const ListResponseLogsInputShape = z.object({ monitorId: z .number() .int() .describe("Monitor id (numeric). Resolve via list_monitors โ never guess."), timeRange: z .enum(monitorTimeRange) .default("1d") .describe("Lookback window: 1d (default), 7d, 14d. Anchored at now."), limit: z .number() .int() .min(1) .max(LIST_RESPONSE_LOGS_LIMIT_MAX) .default(LIST_RESPONSE_LOGS_LIMIT_DEFAULT) .describe( `Items per page (default ${LIST_RESPONSE_LOGS_LIMIT_DEFAULT}, max ${LIST_RESPONSE_LOGS_LIMIT_MAX}).`, ), offset: z.number().int().min(0).default(0),});
const ResponseLogTimingSchema = z .object({ dns: z.number(), connect: z.number(), tls: z.number(), ttfb: z.number(), transfer: z.number(), }) .nullable();
const ResponseLogListItemSchema = z.object({ id: z.string().nullable(), monitorId: z.string(), region: z.string(), requestStatus: z.enum(["success", "error", "degraded"]).nullable(), trigger: z.enum(["cron", "api"]).nullable(), statusCode: z.number().int().nullable(), latency: z.number(), cronTimestamp: z.number(), timestamp: z.number(), timing: ResponseLogTimingSchema,});
const ListResponseLogsOutput = z.object({ logs: z.array(ResponseLogListItemSchema), limit: z.number().int(), offset: z.number().int(), hasMore: z.boolean(), nextOffset: z.number().int().optional(),});
export const listResponseLogsTool: AgentTool< z.infer<typeof ListResponseLogsInputShape>, z.infer<typeof ListResponseLogsOutput>> = { name: "list_response_logs", description: "Recent HTTP check results for a monitor (per-region, with status code, latency, and request status). Use to diagnose 'what's failing?' over the last 1d (default), 7d, or 14d. HTTP monitors only. Pair with get_response_log for the full detail of a specific check.", scope: "read", destructive: false, inputSchema: ListResponseLogsInputShape, outputSchema: ListResponseLogsOutput, async run({ ctx, input }) { const { from, to } = agentTimeRangeToTimestampWindow(input.timeRange); const result = await listResponseLogs({ ctx, input: { monitorId: input.monitorId, fromTimestamp: from, toTimestamp: to, limit: input.limit, offset: input.offset, }, }); return { logs: result.logs.map((log) => ({ id: log.id, monitorId: log.monitorId, region: log.region, requestStatus: log.requestStatus, trigger: log.trigger, statusCode: log.statusCode, latency: log.latency, cronTimestamp: log.cronTimestamp, timestamp: log.timestamp, timing: log.timing, })), limit: result.limit, offset: result.offset, hasMore: result.hasMore, nextOffset: result.nextOffset, }; },};
const GetResponseLogInputShape = z.object({ monitorId: z .number() .int() .describe("Monitor id (numeric). Resolve via list_monitors โ never guess."), logId: z .string() .min(1) .describe("Response log id (string). Resolve via list_response_logs."),});
const GetResponseLogOutput = ResponseLogListItemSchema.extend({ url: z.string(), error: z.boolean(), message: z.string().nullable(), headers: z.record(z.string(), z.string()), assertions: z.string().nullable(),});
export const getResponseLogTool: AgentTool< z.infer<typeof GetResponseLogInputShape>, z.infer<typeof GetResponseLogOutput>> = { name: "get_response_log", description: "Full detail of a single HTTP response log: URL, response headers (sensitive values redacted), error message, assertion results. Use after list_response_logs to drill into one specific failure. Body content is intentionally not exposed.", scope: "read", destructive: false, inputSchema: GetResponseLogInputShape, outputSchema: GetResponseLogOutput, async run({ ctx, input }) { const log = await getResponseLog({ ctx, input: { monitorId: input.monitorId, logId: input.logId }, }); return { id: log.id, monitorId: log.monitorId, region: log.region, requestStatus: log.requestStatus, trigger: log.trigger, statusCode: log.statusCode, latency: log.latency, cronTimestamp: log.cronTimestamp, timestamp: log.timestamp, timing: log.timing, url: log.url, error: log.error, message: log.message, headers: log.headers, assertions: log.assertions, }; },};
function agentTimeRangeToTimestampWindow(value: MonitorTimeRange): { from: number; to: number;} { const day = 24 * 60 * 60_000; const to = Date.now(); const ms = value === "1d" ? day : value === "7d" ? 7 * day : 14 * day; return { from: to - ms, to };}