From 01ae7cd697e04b76cfe5f90e3d3f133a4e0fbdd0 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 6 Jul 2026 14:55:00 -0700 Subject: [PATCH 1/5] feat(pagerduty): validate integration + add 9 tools for deeper API coverage - fix list tools' total field to null-default (matches PagerDuty spec, was wrong 0 default) - add pagination offset across all list tools - add incidentKey, resolution, urgencies, triggered status support - add get_incident, get_service, list_escalation_policies, list_incident_alerts, list_schedules, list_users, merge_incidents, snooze_incident tools (REST API v2) - add send_event tool (Events API v2) for trigger/acknowledge/resolve via routing key - add 2 new BlockMeta skills grounded in documented PagerDuty workflows --- apps/sim/blocks/blocks/pagerduty.ts | 582 +++++++++++++++++- apps/sim/tools/pagerduty/create_incident.ts | 8 + apps/sim/tools/pagerduty/get_incident.ts | 93 +++ apps/sim/tools/pagerduty/get_service.ts | 90 +++ apps/sim/tools/pagerduty/index.ts | 18 + .../pagerduty/list_escalation_policies.ts | 119 ++++ .../tools/pagerduty/list_incident_alerts.ts | 134 ++++ apps/sim/tools/pagerduty/list_incidents.ts | 29 +- apps/sim/tools/pagerduty/list_oncalls.ts | 18 +- apps/sim/tools/pagerduty/list_schedules.ts | 114 ++++ apps/sim/tools/pagerduty/list_services.ts | 18 +- apps/sim/tools/pagerduty/list_users.ts | 111 ++++ apps/sim/tools/pagerduty/merge_incidents.ts | 88 +++ apps/sim/tools/pagerduty/send_event.ts | 122 ++++ apps/sim/tools/pagerduty/snooze_incident.ts | 83 +++ apps/sim/tools/pagerduty/types.ts | 228 ++++++- apps/sim/tools/pagerduty/update_incident.ts | 8 + apps/sim/tools/registry.ts | 18 + 18 files changed, 1859 insertions(+), 22 deletions(-) create mode 100644 apps/sim/tools/pagerduty/get_incident.ts create mode 100644 apps/sim/tools/pagerduty/get_service.ts create mode 100644 apps/sim/tools/pagerduty/list_escalation_policies.ts create mode 100644 apps/sim/tools/pagerduty/list_incident_alerts.ts create mode 100644 apps/sim/tools/pagerduty/list_schedules.ts create mode 100644 apps/sim/tools/pagerduty/list_users.ts create mode 100644 apps/sim/tools/pagerduty/merge_incidents.ts create mode 100644 apps/sim/tools/pagerduty/send_event.ts create mode 100644 apps/sim/tools/pagerduty/snooze_incident.ts diff --git a/apps/sim/blocks/blocks/pagerduty.ts b/apps/sim/blocks/blocks/pagerduty.ts index 2ed88a7b96c..f15b6db1fc7 100644 --- a/apps/sim/blocks/blocks/pagerduty.ts +++ b/apps/sim/blocks/blocks/pagerduty.ts @@ -8,7 +8,7 @@ export const PagerDutyBlock: BlockConfig = { description: 'Manage incidents and on-call schedules with PagerDuty', triggerAllowed: true, longDescription: - 'Integrate PagerDuty into your workflow to list, create, and update incidents, add notes, list services, and check on-call schedules.', + 'Integrate PagerDuty into your workflow to list, get, create, update, snooze, and merge incidents, add notes and list alerts, look up services and escalation policies, check on-call schedules, list users, and send monitoring events through the Events API v2.', docsLink: 'https://docs.sim.ai/integrations/pagerduty', category: 'tools', integrationType: IntegrationType.Observability, @@ -24,11 +24,20 @@ export const PagerDutyBlock: BlockConfig = { type: 'dropdown', options: [ { label: 'List Incidents', id: 'list_incidents' }, + { label: 'Get Incident', id: 'get_incident' }, { label: 'Create Incident', id: 'create_incident' }, { label: 'Update Incident', id: 'update_incident' }, + { label: 'Snooze Incident', id: 'snooze_incident' }, + { label: 'Merge Incidents', id: 'merge_incidents' }, { label: 'Add Note', id: 'add_note' }, + { label: 'List Incident Alerts', id: 'list_incident_alerts' }, { label: 'List Services', id: 'list_services' }, + { label: 'Get Service', id: 'get_service' }, { label: 'List On-Calls', id: 'list_oncalls' }, + { label: 'List Escalation Policies', id: 'list_escalation_policies' }, + { label: 'List Schedules', id: 'list_schedules' }, + { label: 'List Users', id: 'list_users' }, + { label: 'Send Event', id: 'send_event' }, ], value: () => 'list_incidents', }, @@ -37,9 +46,20 @@ export const PagerDutyBlock: BlockConfig = { id: 'apiKey', title: 'API Key', type: 'short-input', - required: true, + required: { field: 'operation', value: 'send_event', not: true }, placeholder: 'Enter your PagerDuty REST API Key', password: true, + condition: { field: 'operation', value: 'send_event', not: true }, + }, + + { + id: 'routingKey', + title: 'Integration Key', + type: 'short-input', + required: { field: 'operation', value: 'send_event' }, + placeholder: 'Events API v2 integration key for the target service', + password: true, + condition: { field: 'operation', value: 'send_event' }, }, { @@ -48,12 +68,24 @@ export const PagerDutyBlock: BlockConfig = { type: 'short-input', required: { field: 'operation', - value: ['create_incident', 'update_incident', 'add_note'], + value: [ + 'create_incident', + 'update_incident', + 'add_note', + 'snooze_incident', + 'merge_incidents', + ], }, placeholder: 'Valid PagerDuty user email (required for write operations)', condition: { field: 'operation', - value: ['create_incident', 'update_incident', 'add_note'], + value: [ + 'create_incident', + 'update_incident', + 'add_note', + 'snooze_incident', + 'merge_incidents', + ], }, }, @@ -71,6 +103,19 @@ export const PagerDutyBlock: BlockConfig = { value: () => '', condition: { field: 'operation', value: 'list_incidents' }, }, + { + id: 'listUrgencies', + title: 'Urgencies', + type: 'dropdown', + options: [ + { label: 'All', id: '' }, + { label: 'High', id: 'high' }, + { label: 'Low', id: 'low' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_incidents' }, + mode: 'advanced', + }, { id: 'listServiceIds', title: 'Service IDs', @@ -127,6 +172,24 @@ export const PagerDutyBlock: BlockConfig = { condition: { field: 'operation', value: 'list_incidents' }, mode: 'advanced', }, + { + id: 'listOffset', + title: 'Offset', + type: 'short-input', + placeholder: '0', + condition: { field: 'operation', value: 'list_incidents' }, + mode: 'advanced', + }, + + // --- Get Incident fields --- + { + id: 'getIncidentId', + title: 'Incident ID', + type: 'short-input', + required: { field: 'operation', value: 'get_incident' }, + placeholder: 'ID of the incident to fetch', + condition: { field: 'operation', value: 'get_incident' }, + }, // --- Create Incident fields --- { @@ -179,6 +242,14 @@ export const PagerDutyBlock: BlockConfig = { condition: { field: 'operation', value: 'create_incident' }, mode: 'advanced', }, + { + id: 'incidentKey', + title: 'De-duplication Key', + type: 'short-input', + placeholder: 'Idempotency key to avoid duplicate incidents (optional)', + condition: { field: 'operation', value: 'create_incident' }, + mode: 'advanced', + }, // --- Update Incident fields --- { @@ -195,12 +266,21 @@ export const PagerDutyBlock: BlockConfig = { type: 'dropdown', options: [ { label: 'No Change', id: '' }, + { label: 'Triggered (reopen)', id: 'triggered' }, { label: 'Acknowledged', id: 'acknowledged' }, { label: 'Resolved', id: 'resolved' }, ], value: () => '', condition: { field: 'operation', value: 'update_incident' }, }, + { + id: 'updateResolution', + title: 'Resolution Note', + type: 'long-input', + placeholder: 'Note describing the resolution (used when status is resolved)', + condition: { field: 'operation', value: 'update_incident' }, + mode: 'advanced', + }, { id: 'updateTitle', title: 'New Title', @@ -230,6 +310,42 @@ export const PagerDutyBlock: BlockConfig = { condition: { field: 'operation', value: 'update_incident' }, mode: 'advanced', }, + // --- Snooze Incident fields --- + { + id: 'snoozeIncidentId', + title: 'Incident ID', + type: 'short-input', + required: { field: 'operation', value: 'snooze_incident' }, + placeholder: 'ID of the incident to snooze', + condition: { field: 'operation', value: 'snooze_incident' }, + }, + { + id: 'snoozeDuration', + title: 'Duration (seconds)', + type: 'short-input', + required: { field: 'operation', value: 'snooze_incident' }, + placeholder: 'e.g., 3600 for 1 hour (max 604800)', + condition: { field: 'operation', value: 'snooze_incident' }, + }, + + // --- Merge Incidents fields --- + { + id: 'mergeTargetIncidentId', + title: 'Target Incident ID', + type: 'short-input', + required: { field: 'operation', value: 'merge_incidents' }, + placeholder: 'Incident that will absorb the source incidents', + condition: { field: 'operation', value: 'merge_incidents' }, + }, + { + id: 'mergeSourceIncidentIds', + title: 'Source Incident IDs', + type: 'short-input', + required: { field: 'operation', value: 'merge_incidents' }, + placeholder: 'Comma-separated IDs of incidents to merge in', + condition: { field: 'operation', value: 'merge_incidents' }, + }, + // --- Add Note fields --- { id: 'noteIncidentId', @@ -248,6 +364,45 @@ export const PagerDutyBlock: BlockConfig = { condition: { field: 'operation', value: 'add_note' }, }, + // --- List Incident Alerts fields --- + { + id: 'alertsIncidentId', + title: 'Incident ID', + type: 'short-input', + required: { field: 'operation', value: 'list_incident_alerts' }, + placeholder: 'ID of the incident whose alerts to list', + condition: { field: 'operation', value: 'list_incident_alerts' }, + }, + { + id: 'alertsStatuses', + title: 'Statuses', + type: 'dropdown', + options: [ + { label: 'All', id: '' }, + { label: 'Triggered', id: 'triggered' }, + { label: 'Resolved', id: 'resolved' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_incident_alerts' }, + mode: 'advanced', + }, + { + id: 'alertsLimit', + title: 'Limit', + type: 'short-input', + placeholder: '25', + condition: { field: 'operation', value: 'list_incident_alerts' }, + mode: 'advanced', + }, + { + id: 'alertsOffset', + title: 'Offset', + type: 'short-input', + placeholder: '0', + condition: { field: 'operation', value: 'list_incident_alerts' }, + mode: 'advanced', + }, + // --- List Services fields --- { id: 'serviceQuery', @@ -264,6 +419,24 @@ export const PagerDutyBlock: BlockConfig = { condition: { field: 'operation', value: 'list_services' }, mode: 'advanced', }, + { + id: 'serviceOffset', + title: 'Offset', + type: 'short-input', + placeholder: '0', + condition: { field: 'operation', value: 'list_services' }, + mode: 'advanced', + }, + + // --- Get Service fields --- + { + id: 'getServiceId', + title: 'Service ID', + type: 'short-input', + required: { field: 'operation', value: 'get_service' }, + placeholder: 'ID of the service to fetch', + condition: { field: 'operation', value: 'get_service' }, + }, // --- List On-Calls fields --- { @@ -317,6 +490,192 @@ export const PagerDutyBlock: BlockConfig = { generationType: 'timestamp', }, }, + { + id: 'oncallOffset', + title: 'Offset', + type: 'short-input', + placeholder: '0', + condition: { field: 'operation', value: 'list_oncalls' }, + mode: 'advanced', + }, + + // --- List Escalation Policies fields --- + { + id: 'escalationPolicyQuery', + title: 'Search Query', + type: 'short-input', + placeholder: 'Filter escalation policies by name', + condition: { field: 'operation', value: 'list_escalation_policies' }, + }, + { + id: 'escalationPolicyLimit', + title: 'Limit', + type: 'short-input', + placeholder: '25', + condition: { field: 'operation', value: 'list_escalation_policies' }, + mode: 'advanced', + }, + { + id: 'escalationPolicyOffset', + title: 'Offset', + type: 'short-input', + placeholder: '0', + condition: { field: 'operation', value: 'list_escalation_policies' }, + mode: 'advanced', + }, + + // --- List Schedules fields --- + { + id: 'scheduleQuery', + title: 'Search Query', + type: 'short-input', + placeholder: 'Filter schedules by name', + condition: { field: 'operation', value: 'list_schedules' }, + }, + { + id: 'scheduleLimit', + title: 'Limit', + type: 'short-input', + placeholder: '25', + condition: { field: 'operation', value: 'list_schedules' }, + mode: 'advanced', + }, + { + id: 'scheduleOffset', + title: 'Offset', + type: 'short-input', + placeholder: '0', + condition: { field: 'operation', value: 'list_schedules' }, + mode: 'advanced', + }, + + // --- List Users fields --- + { + id: 'userQuery', + title: 'Search Query', + type: 'short-input', + placeholder: 'Filter users by name or email', + condition: { field: 'operation', value: 'list_users' }, + }, + { + id: 'userLimit', + title: 'Limit', + type: 'short-input', + placeholder: '25', + condition: { field: 'operation', value: 'list_users' }, + mode: 'advanced', + }, + { + id: 'userOffset', + title: 'Offset', + type: 'short-input', + placeholder: '0', + condition: { field: 'operation', value: 'list_users' }, + mode: 'advanced', + }, + + // --- Send Event fields --- + { + id: 'eventAction', + title: 'Event Action', + type: 'dropdown', + options: [ + { label: 'Trigger', id: 'trigger' }, + { label: 'Acknowledge', id: 'acknowledge' }, + { label: 'Resolve', id: 'resolve' }, + ], + value: () => 'trigger', + condition: { field: 'operation', value: 'send_event' }, + }, + { + id: 'eventSummary', + title: 'Summary', + type: 'short-input', + required: { field: 'operation', value: 'send_event' }, + placeholder: 'Brief summary of the event', + condition: { + field: 'operation', + value: 'send_event', + and: { field: 'eventAction', value: 'trigger' }, + }, + }, + { + id: 'eventSource', + title: 'Source', + type: 'short-input', + required: { field: 'operation', value: 'send_event' }, + placeholder: 'Affected system, e.g. a hostname', + condition: { + field: 'operation', + value: 'send_event', + and: { field: 'eventAction', value: 'trigger' }, + }, + }, + { + id: 'eventSeverity', + title: 'Severity', + type: 'dropdown', + options: [ + { label: 'Critical', id: 'critical' }, + { label: 'Warning', id: 'warning' }, + { label: 'Error', id: 'error' }, + { label: 'Info', id: 'info' }, + ], + value: () => 'critical', + condition: { + field: 'operation', + value: 'send_event', + and: { field: 'eventAction', value: 'trigger' }, + }, + }, + { + id: 'eventDedupKey', + title: 'De-duplication Key', + type: 'short-input', + required: { + field: 'operation', + value: 'send_event', + and: { field: 'eventAction', value: ['acknowledge', 'resolve'] }, + }, + placeholder: 'Key identifying the alert (required for acknowledge/resolve)', + condition: { field: 'operation', value: 'send_event' }, + }, + { + id: 'eventComponent', + title: 'Component', + type: 'short-input', + placeholder: 'Component of the source responsible for the event', + condition: { + field: 'operation', + value: 'send_event', + and: { field: 'eventAction', value: 'trigger' }, + }, + mode: 'advanced', + }, + { + id: 'eventGroup', + title: 'Group', + type: 'short-input', + placeholder: 'Logical grouping of components', + condition: { + field: 'operation', + value: 'send_event', + and: { field: 'eventAction', value: 'trigger' }, + }, + mode: 'advanced', + }, + { + id: 'eventClass', + title: 'Class', + type: 'short-input', + placeholder: 'Class/type of the event', + condition: { + field: 'operation', + value: 'send_event', + and: { field: 'eventAction', value: 'trigger' }, + }, + mode: 'advanced', + }, ...getTrigger('pagerduty_incident_triggered').subBlocks, ...getTrigger('pagerduty_incident_acknowledged').subBlocks, ...getTrigger('pagerduty_incident_resolved').subBlocks, @@ -328,11 +687,20 @@ export const PagerDutyBlock: BlockConfig = { tools: { access: [ 'pagerduty_list_incidents', + 'pagerduty_get_incident', 'pagerduty_create_incident', 'pagerduty_update_incident', + 'pagerduty_snooze_incident', + 'pagerduty_merge_incidents', 'pagerduty_add_note', + 'pagerduty_list_incident_alerts', 'pagerduty_list_services', + 'pagerduty_get_service', 'pagerduty_list_oncalls', + 'pagerduty_list_escalation_policies', + 'pagerduty_list_schedules', + 'pagerduty_list_users', + 'pagerduty_send_event', ], config: { tool: (params) => `pagerduty_${params.operation}`, @@ -342,11 +710,17 @@ export const PagerDutyBlock: BlockConfig = { switch (params.operation) { case 'list_incidents': if (params.statuses) result.statuses = params.statuses + if (params.listUrgencies) result.urgencies = params.listUrgencies if (params.listServiceIds) result.serviceIds = params.listServiceIds if (params.listSince) result.since = params.listSince if (params.listUntil) result.until = params.listUntil if (params.listSortBy) result.sortBy = params.listSortBy if (params.listLimit) result.limit = params.listLimit + if (params.listOffset) result.offset = params.listOffset + break + + case 'get_incident': + if (params.getIncidentId) result.incidentId = params.getIncidentId break case 'create_incident': @@ -360,6 +734,18 @@ export const PagerDutyBlock: BlockConfig = { if (params.updateTitle) result.title = params.updateTitle if (params.updateUrgency) result.urgency = params.updateUrgency if (params.updateEscalationLevel) result.escalationLevel = params.updateEscalationLevel + if (params.updateResolution) result.resolution = params.updateResolution + break + + case 'snooze_incident': + if (params.snoozeIncidentId) result.incidentId = params.snoozeIncidentId + if (params.snoozeDuration) result.duration = params.snoozeDuration + break + + case 'merge_incidents': + if (params.mergeTargetIncidentId) result.targetIncidentId = params.mergeTargetIncidentId + if (params.mergeSourceIncidentIds) + result.sourceIncidentIds = params.mergeSourceIncidentIds break case 'add_note': @@ -367,9 +753,21 @@ export const PagerDutyBlock: BlockConfig = { if (params.noteContent) result.content = params.noteContent break + case 'list_incident_alerts': + if (params.alertsIncidentId) result.incidentId = params.alertsIncidentId + if (params.alertsStatuses) result.statuses = params.alertsStatuses + if (params.alertsLimit) result.limit = params.alertsLimit + if (params.alertsOffset) result.offset = params.alertsOffset + break + case 'list_services': if (params.serviceQuery) result.query = params.serviceQuery if (params.serviceLimit) result.limit = params.serviceLimit + if (params.serviceOffset) result.offset = params.serviceOffset + break + + case 'get_service': + if (params.getServiceId) result.serviceId = params.getServiceId break case 'list_oncalls': @@ -379,6 +777,36 @@ export const PagerDutyBlock: BlockConfig = { if (params.oncallSince) result.since = params.oncallSince if (params.oncallUntil) result.until = params.oncallUntil if (params.oncallLimit) result.limit = params.oncallLimit + if (params.oncallOffset) result.offset = params.oncallOffset + break + + case 'list_escalation_policies': + if (params.escalationPolicyQuery) result.query = params.escalationPolicyQuery + if (params.escalationPolicyLimit) result.limit = params.escalationPolicyLimit + if (params.escalationPolicyOffset) result.offset = params.escalationPolicyOffset + break + + case 'list_schedules': + if (params.scheduleQuery) result.query = params.scheduleQuery + if (params.scheduleLimit) result.limit = params.scheduleLimit + if (params.scheduleOffset) result.offset = params.scheduleOffset + break + + case 'list_users': + if (params.userQuery) result.query = params.userQuery + if (params.userLimit) result.limit = params.userLimit + if (params.userOffset) result.offset = params.userOffset + break + + case 'send_event': + if (params.eventAction) result.eventAction = params.eventAction + if (params.eventSummary) result.summary = params.eventSummary + if (params.eventSource) result.source = params.eventSource + if (params.eventSeverity) result.severity = params.eventSeverity + if (params.eventDedupKey) result.dedupKey = params.eventDedupKey + if (params.eventComponent) result.component = params.eventComponent + if (params.eventGroup) result.group = params.eventGroup + if (params.eventClass) result.class = params.eventClass break } @@ -390,51 +818,93 @@ export const PagerDutyBlock: BlockConfig = { inputs: { operation: { type: 'string', description: 'Operation to perform' }, apiKey: { type: 'string', description: 'PagerDuty REST API Key' }, + routingKey: { type: 'string', description: 'Events API v2 integration key' }, fromEmail: { type: 'string', description: 'Valid PagerDuty user email' }, statuses: { type: 'string', description: 'Status filter for incidents' }, + listUrgencies: { type: 'string', description: 'Urgency filter for incidents' }, listServiceIds: { type: 'string', description: 'Service IDs filter' }, listSince: { type: 'string', description: 'Start date filter' }, listUntil: { type: 'string', description: 'End date filter' }, + listSortBy: { type: 'string', description: 'Sort field' }, + listLimit: { type: 'string', description: 'Max results for incidents' }, + listOffset: { type: 'string', description: 'Pagination offset for incidents' }, + getIncidentId: { type: 'string', description: 'Incident ID to fetch' }, title: { type: 'string', description: 'Incident title' }, createServiceId: { type: 'string', description: 'Service ID for new incident' }, createUrgency: { type: 'string', description: 'Urgency level' }, body: { type: 'string', description: 'Incident description' }, + incidentKey: { type: 'string', description: 'De-duplication key for new incident' }, updateIncidentId: { type: 'string', description: 'Incident ID to update' }, updateStatus: { type: 'string', description: 'New status' }, + updateResolution: { type: 'string', description: 'Resolution note' }, + snoozeIncidentId: { type: 'string', description: 'Incident ID to snooze' }, + snoozeDuration: { type: 'string', description: 'Snooze duration in seconds' }, + mergeTargetIncidentId: { type: 'string', description: 'Target incident ID for merge' }, + mergeSourceIncidentIds: { type: 'string', description: 'Source incident IDs to merge in' }, noteIncidentId: { type: 'string', description: 'Incident ID for note' }, noteContent: { type: 'string', description: 'Note content' }, + alertsIncidentId: { type: 'string', description: 'Incident ID whose alerts to list' }, + alertsStatuses: { type: 'string', description: 'Status filter for alerts' }, + alertsLimit: { type: 'string', description: 'Max results for alerts' }, + alertsOffset: { type: 'string', description: 'Pagination offset for alerts' }, escalationPolicyId: { type: 'string', description: 'Escalation policy ID' }, assigneeId: { type: 'string', description: 'Assignee user ID' }, updateTitle: { type: 'string', description: 'New incident title' }, updateUrgency: { type: 'string', description: 'New urgency level' }, updateEscalationLevel: { type: 'string', description: 'Escalation level number' }, - listSortBy: { type: 'string', description: 'Sort field' }, - listLimit: { type: 'string', description: 'Max results for incidents' }, serviceQuery: { type: 'string', description: 'Service name filter' }, serviceLimit: { type: 'string', description: 'Max results for services' }, + serviceOffset: { type: 'string', description: 'Pagination offset for services' }, + getServiceId: { type: 'string', description: 'Service ID to fetch' }, oncallEscalationPolicyIds: { type: 'string', description: 'Escalation policy IDs filter' }, oncallScheduleIds: { type: 'string', description: 'Schedule IDs filter' }, oncallSince: { type: 'string', description: 'On-call start time filter' }, oncallUntil: { type: 'string', description: 'On-call end time filter' }, oncallLimit: { type: 'string', description: 'Max results for on-calls' }, + oncallOffset: { type: 'string', description: 'Pagination offset for on-calls' }, + escalationPolicyQuery: { type: 'string', description: 'Escalation policy name filter' }, + escalationPolicyLimit: { type: 'string', description: 'Max results for escalation policies' }, + escalationPolicyOffset: { + type: 'string', + description: 'Pagination offset for escalation policies', + }, + scheduleQuery: { type: 'string', description: 'Schedule name filter' }, + scheduleLimit: { type: 'string', description: 'Max results for schedules' }, + scheduleOffset: { type: 'string', description: 'Pagination offset for schedules' }, + userQuery: { type: 'string', description: 'User name/email filter' }, + userLimit: { type: 'string', description: 'Max results for users' }, + userOffset: { type: 'string', description: 'Pagination offset for users' }, + eventAction: { type: 'string', description: 'Events API action (trigger/acknowledge/resolve)' }, + eventSummary: { type: 'string', description: 'Event summary' }, + eventSource: { type: 'string', description: 'Event source system' }, + eventSeverity: { type: 'string', description: 'Event severity' }, + eventDedupKey: { type: 'string', description: 'Event de-duplication key' }, + eventComponent: { type: 'string', description: 'Event component' }, + eventGroup: { type: 'string', description: 'Event group' }, + eventClass: { type: 'string', description: 'Event class' }, }, outputs: { incidents: { type: 'json', - description: 'Array of incidents (list_incidents)', + description: + '[{id, incidentNumber, title, status, urgency, createdAt, updatedAt, serviceName, serviceId, assigneeName, assigneeId, escalationPolicyName, htmlUrl}] (list_incidents)', }, total: { type: 'number', - description: 'Total count of results', + description: 'Total count of results, null unless requested (list operations)', }, more: { type: 'boolean', - description: 'Whether more results are available', + description: 'Whether more results are available (list operations)', + }, + offset: { + type: 'number', + description: 'Pagination offset for this page of results (list operations)', }, id: { type: 'string', - description: 'Created/updated resource ID', + description: 'Created/updated/fetched resource ID', }, incidentNumber: { type: 'number', @@ -446,7 +916,7 @@ export const PagerDutyBlock: BlockConfig = { }, status: { type: 'string', - description: 'Incident status', + description: 'Incident/event status', }, urgency: { type: 'string', @@ -460,6 +930,14 @@ export const PagerDutyBlock: BlockConfig = { type: 'string', description: 'Last updated timestamp', }, + resolvedAt: { + type: 'string', + description: 'Resolution timestamp (get_incident)', + }, + incidentKey: { + type: 'string', + description: 'De-duplication key (get_incident)', + }, serviceName: { type: 'string', description: 'Service name', @@ -468,6 +946,22 @@ export const PagerDutyBlock: BlockConfig = { type: 'string', description: 'Service ID', }, + assigneeName: { + type: 'string', + description: 'Assignee name (list_incidents, get_incident)', + }, + assigneeId: { + type: 'string', + description: 'Assignee ID (list_incidents, get_incident)', + }, + escalationPolicyName: { + type: 'string', + description: 'Escalation policy name', + }, + escalationPolicyId: { + type: 'string', + description: 'Escalation policy ID (get_incident, get_service)', + }, htmlUrl: { type: 'string', description: 'PagerDuty web URL', @@ -482,11 +976,59 @@ export const PagerDutyBlock: BlockConfig = { }, services: { type: 'json', - description: 'Array of services (list_services)', + description: + '[{id, name, description, status, escalationPolicyName, escalationPolicyId, createdAt, htmlUrl}] (list_services)', + }, + name: { + type: 'string', + description: 'Resource name (get_service, escalation policies, schedules, users)', + }, + description: { + type: 'string', + description: 'Resource description (get_service, escalation policies, schedules)', + }, + autoResolveTimeout: { + type: 'number', + description: 'Seconds before an open incident auto-resolves (get_service)', + }, + acknowledgementTimeout: { + type: 'number', + description: 'Seconds before an acknowledged incident reverts to triggered (get_service)', + }, + lastIncidentTimestamp: { + type: 'string', + description: 'Timestamp of the most recent incident (get_service)', }, oncalls: { type: 'json', - description: 'Array of on-call entries (list_oncalls)', + description: + '[{userName, userId, escalationLevel, escalationPolicyName, escalationPolicyId, scheduleName, scheduleId, start, end}] (list_oncalls)', + }, + escalationPolicies: { + type: 'json', + description: + '[{id, name, description, numLoops, onCallHandoffNotifications, htmlUrl}] (list_escalation_policies)', + }, + schedules: { + type: 'json', + description: '[{id, name, description, timeZone, htmlUrl}] (list_schedules)', + }, + users: { + type: 'json', + description: '[{id, name, email, role, jobTitle, timeZone, htmlUrl}] (list_users)', + }, + alerts: { + type: 'json', + description: + '[{id, summary, status, severity, createdAt, alertKey, serviceName, serviceId, htmlUrl}] (list_incident_alerts)', + }, + message: { + type: 'string', + description: 'Result message (send_event)', + }, + dedupKey: { + type: 'string', + description: 'De-duplication key returned by the Events API (send_event)', }, }, @@ -605,5 +1147,19 @@ export const PagerDutyBlockMeta = { content: '# Check Who Is On Call\n\nFind the right person to reach right now.\n\n## Steps\n1. Use List On-Calls, optionally scoped by Escalation Policy IDs or Schedule IDs.\n2. Set a Since and Until window to look at the current or an upcoming shift.\n3. Map each on-call entry to its escalation level so primary versus backup responders are clear.\n\n## Output\nA concise roster: who is on call at level 1 (primary) and level 2 (backup) per schedule, with the time window covered.', }, + { + name: 'send-monitoring-event', + description: + 'Trigger, acknowledge, or resolve a PagerDuty alert from a monitoring source using the Events API v2 integration key, without a PagerDuty user account.', + content: + "# Send Monitoring Event\n\nPage PagerDuty directly from a monitoring check or script.\n\n## Steps\n1. Use Send Event with the target service's Integration Key and Event Action set to Trigger.\n2. Provide a Summary, Source (the affected host/system), and Severity describing the problem.\n3. Reuse the same De-duplication Key on later Acknowledge/Resolve events to update the same alert instead of opening a new one.\n\n## Output\nReport the resulting status and the de-duplication key so follow-up events can reference the same alert.", + }, + { + name: 'merge-duplicate-incidents', + description: + 'Merge duplicate PagerDuty incidents from the same event into one target incident to reduce noise.', + content: + '# Merge Duplicate Incidents\n\nCollapse near-duplicate pages into a single incident.\n\n## Steps\n1. Use List Incidents to identify incidents that describe the same underlying problem (same service, overlapping time window, similar title).\n2. Pick the incident responders are already working as the Target Incident ID.\n3. Use Merge Incidents with the Target Incident ID and the comma-separated Source Incident IDs to merge in; the sources are resolved automatically.\n4. Provide a valid From Email since this is a write operation.\n\n## Output\nConfirm the target incident number and status, and how many source incidents were merged into it.', + }, ], } as const satisfies BlockMeta diff --git a/apps/sim/tools/pagerduty/create_incident.ts b/apps/sim/tools/pagerduty/create_incident.ts index 6a4c98854f5..60a11bc8ed0 100644 --- a/apps/sim/tools/pagerduty/create_incident.ts +++ b/apps/sim/tools/pagerduty/create_incident.ts @@ -62,6 +62,13 @@ export const createIncidentTool: ToolConfig< visibility: 'user-or-llm', description: 'User ID to assign the incident to', }, + incidentKey: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'De-duplication key. A subsequent request with the same service and incident key updates the existing open incident instead of creating a new one', + }, }, request: { @@ -106,6 +113,7 @@ export const createIncidentTool: ToolConfig< }, ] } + if (params.incidentKey) incident.incident_key = params.incidentKey return { incident } }, diff --git a/apps/sim/tools/pagerduty/get_incident.ts b/apps/sim/tools/pagerduty/get_incident.ts new file mode 100644 index 00000000000..4d4d303b99d --- /dev/null +++ b/apps/sim/tools/pagerduty/get_incident.ts @@ -0,0 +1,93 @@ +import type { + PagerDutyGetIncidentParams, + PagerDutyGetIncidentResponse, +} from '@/tools/pagerduty/types' +import type { ToolConfig } from '@/tools/types' + +export const getIncidentTool: ToolConfig = + { + id: 'pagerduty_get_incident', + name: 'PagerDuty Get Incident', + description: 'Get a single incident from PagerDuty by ID.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'PagerDuty REST API Key', + }, + incidentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the incident to fetch', + }, + }, + + request: { + url: (params) => + `https://api.pagerduty.com/incidents/${params.incidentId.trim()}?include[]=services`, + method: 'GET', + headers: (params) => ({ + Authorization: `Token token=${params.apiKey}`, + Accept: 'application/vnd.pagerduty+json;version=2', + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error?.message || `PagerDuty API error: ${response.status}`) + } + + const inc = data.incident ?? {} + return { + success: true, + output: { + id: inc.id ?? null, + incidentNumber: inc.incident_number ?? null, + title: inc.title ?? null, + status: inc.status ?? null, + urgency: inc.urgency ?? null, + createdAt: inc.created_at ?? null, + updatedAt: inc.updated_at ?? null, + resolvedAt: inc.resolved_at ?? null, + serviceName: inc.service?.summary ?? null, + serviceId: inc.service?.id ?? null, + assigneeName: inc.assignments?.[0]?.assignee?.summary ?? null, + assigneeId: inc.assignments?.[0]?.assignee?.id ?? null, + escalationPolicyName: inc.escalation_policy?.summary ?? null, + escalationPolicyId: inc.escalation_policy?.id ?? null, + incidentKey: inc.incident_key ?? null, + htmlUrl: inc.html_url ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Incident ID' }, + incidentNumber: { type: 'number', description: 'Incident number' }, + title: { type: 'string', description: 'Incident title' }, + status: { type: 'string', description: 'Incident status' }, + urgency: { type: 'string', description: 'Incident urgency' }, + createdAt: { type: 'string', description: 'Creation timestamp' }, + updatedAt: { type: 'string', description: 'Last updated timestamp', optional: true }, + resolvedAt: { type: 'string', description: 'Resolution timestamp', optional: true }, + serviceName: { type: 'string', description: 'Service name', optional: true }, + serviceId: { type: 'string', description: 'Service ID', optional: true }, + assigneeName: { type: 'string', description: 'Assignee name', optional: true }, + assigneeId: { type: 'string', description: 'Assignee ID', optional: true }, + escalationPolicyName: { + type: 'string', + description: 'Escalation policy name', + optional: true, + }, + escalationPolicyId: { type: 'string', description: 'Escalation policy ID', optional: true }, + incidentKey: { type: 'string', description: 'De-duplication key', optional: true }, + htmlUrl: { type: 'string', description: 'PagerDuty web URL' }, + }, + } diff --git a/apps/sim/tools/pagerduty/get_service.ts b/apps/sim/tools/pagerduty/get_service.ts new file mode 100644 index 00000000000..e5c3c21ec8f --- /dev/null +++ b/apps/sim/tools/pagerduty/get_service.ts @@ -0,0 +1,90 @@ +import type { + PagerDutyGetServiceParams, + PagerDutyGetServiceResponse, +} from '@/tools/pagerduty/types' +import type { ToolConfig } from '@/tools/types' + +export const getServiceTool: ToolConfig = { + id: 'pagerduty_get_service', + name: 'PagerDuty Get Service', + description: 'Get a single service from PagerDuty by ID.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'PagerDuty REST API Key', + }, + serviceId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the service to fetch', + }, + }, + + request: { + url: (params) => + `https://api.pagerduty.com/services/${params.serviceId.trim()}?include[]=escalation_policies`, + method: 'GET', + headers: (params) => ({ + Authorization: `Token token=${params.apiKey}`, + Accept: 'application/vnd.pagerduty+json;version=2', + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error?.message || `PagerDuty API error: ${response.status}`) + } + + const svc = data.service ?? {} + return { + success: true, + output: { + id: svc.id ?? null, + name: svc.name ?? null, + description: svc.description ?? null, + status: svc.status ?? null, + autoResolveTimeout: svc.auto_resolve_timeout ?? null, + acknowledgementTimeout: svc.acknowledgement_timeout ?? null, + createdAt: svc.created_at ?? null, + lastIncidentTimestamp: svc.last_incident_timestamp ?? null, + escalationPolicyName: svc.escalation_policy?.summary ?? null, + escalationPolicyId: svc.escalation_policy?.id ?? null, + htmlUrl: svc.html_url ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Service ID' }, + name: { type: 'string', description: 'Service name' }, + description: { type: 'string', description: 'Service description', optional: true }, + status: { type: 'string', description: 'Service status' }, + autoResolveTimeout: { + type: 'number', + description: 'Seconds before an open incident auto-resolves', + optional: true, + }, + acknowledgementTimeout: { + type: 'number', + description: 'Seconds before an acknowledged incident reverts to triggered', + optional: true, + }, + createdAt: { type: 'string', description: 'Creation timestamp', optional: true }, + lastIncidentTimestamp: { + type: 'string', + description: 'Timestamp of the most recent incident', + optional: true, + }, + escalationPolicyName: { type: 'string', description: 'Escalation policy name', optional: true }, + escalationPolicyId: { type: 'string', description: 'Escalation policy ID', optional: true }, + htmlUrl: { type: 'string', description: 'PagerDuty web URL' }, + }, +} diff --git a/apps/sim/tools/pagerduty/index.ts b/apps/sim/tools/pagerduty/index.ts index e6ee2bc34b4..4ee66a335a5 100644 --- a/apps/sim/tools/pagerduty/index.ts +++ b/apps/sim/tools/pagerduty/index.ts @@ -1,13 +1,31 @@ import { addNoteTool } from '@/tools/pagerduty/add_note' import { createIncidentTool } from '@/tools/pagerduty/create_incident' +import { getIncidentTool } from '@/tools/pagerduty/get_incident' +import { getServiceTool } from '@/tools/pagerduty/get_service' +import { listEscalationPoliciesTool } from '@/tools/pagerduty/list_escalation_policies' +import { listIncidentAlertsTool } from '@/tools/pagerduty/list_incident_alerts' import { listIncidentsTool } from '@/tools/pagerduty/list_incidents' import { listOncallsTool } from '@/tools/pagerduty/list_oncalls' +import { listSchedulesTool } from '@/tools/pagerduty/list_schedules' import { listServicesTool } from '@/tools/pagerduty/list_services' +import { listUsersTool } from '@/tools/pagerduty/list_users' +import { mergeIncidentsTool } from '@/tools/pagerduty/merge_incidents' +import { sendEventTool } from '@/tools/pagerduty/send_event' +import { snoozeIncidentTool } from '@/tools/pagerduty/snooze_incident' import { updateIncidentTool } from '@/tools/pagerduty/update_incident' export const pagerdutyListIncidentsTool = listIncidentsTool +export const pagerdutyGetIncidentTool = getIncidentTool export const pagerdutyCreateIncidentTool = createIncidentTool export const pagerdutyUpdateIncidentTool = updateIncidentTool +export const pagerdutySnoozeIncidentTool = snoozeIncidentTool +export const pagerdutyMergeIncidentsTool = mergeIncidentsTool export const pagerdutyAddNoteTool = addNoteTool +export const pagerdutyListIncidentAlertsTool = listIncidentAlertsTool export const pagerdutyListServicesTool = listServicesTool +export const pagerdutyGetServiceTool = getServiceTool export const pagerdutyListOncallsTool = listOncallsTool +export const pagerdutyListEscalationPoliciesTool = listEscalationPoliciesTool +export const pagerdutyListSchedulesTool = listSchedulesTool +export const pagerdutyListUsersTool = listUsersTool +export const pagerdutySendEventTool = sendEventTool diff --git a/apps/sim/tools/pagerduty/list_escalation_policies.ts b/apps/sim/tools/pagerduty/list_escalation_policies.ts new file mode 100644 index 00000000000..b57f626cbdd --- /dev/null +++ b/apps/sim/tools/pagerduty/list_escalation_policies.ts @@ -0,0 +1,119 @@ +import type { + PagerDutyListEscalationPoliciesParams, + PagerDutyListEscalationPoliciesResponse, +} from '@/tools/pagerduty/types' +import type { ToolConfig } from '@/tools/types' + +export const listEscalationPoliciesTool: ToolConfig< + PagerDutyListEscalationPoliciesParams, + PagerDutyListEscalationPoliciesResponse +> = { + id: 'pagerduty_list_escalation_policies', + name: 'PagerDuty List Escalation Policies', + description: 'List escalation policies from PagerDuty with an optional name filter.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'PagerDuty REST API Key', + }, + query: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter escalation policies by name', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results (max 100)', + }, + offset: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Offset to start pagination search results', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (params.query) query.set('query', params.query) + if (params.limit) query.set('limit', params.limit) + if (params.offset) query.set('offset', params.offset) + const qs = query.toString() + return `https://api.pagerduty.com/escalation_policies${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Token token=${params.apiKey}`, + Accept: 'application/vnd.pagerduty+json;version=2', + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error?.message || `PagerDuty API error: ${response.status}`) + } + + return { + success: true, + output: { + escalationPolicies: (data.escalation_policies ?? []).map((ep: Record) => ({ + id: ep.id ?? null, + name: ep.name ?? null, + description: ep.description ?? null, + numLoops: ep.num_loops ?? 0, + onCallHandoffNotifications: ep.on_call_handoff_notifications ?? null, + htmlUrl: ep.html_url ?? null, + })), + total: data.total ?? null, + more: data.more ?? false, + offset: data.offset ?? 0, + }, + } + }, + + outputs: { + escalationPolicies: { + type: 'array', + description: 'Array of escalation policies', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Escalation policy ID' }, + name: { type: 'string', description: 'Escalation policy name' }, + description: { type: 'string', description: 'Escalation policy description' }, + numLoops: { type: 'number', description: 'Number of times the policy repeats' }, + onCallHandoffNotifications: { + type: 'string', + description: 'Handoff notification setting (if_has_services or always)', + }, + htmlUrl: { type: 'string', description: 'PagerDuty web URL' }, + }, + }, + }, + total: { + type: 'number', + description: + 'Total number of matching escalation policies (null unless explicitly requested by PagerDuty)', + optional: true, + }, + more: { + type: 'boolean', + description: 'Whether more results are available', + }, + offset: { + type: 'number', + description: 'Offset used for this page of results', + }, + }, +} diff --git a/apps/sim/tools/pagerduty/list_incident_alerts.ts b/apps/sim/tools/pagerduty/list_incident_alerts.ts new file mode 100644 index 00000000000..98d03eb54d7 --- /dev/null +++ b/apps/sim/tools/pagerduty/list_incident_alerts.ts @@ -0,0 +1,134 @@ +import type { + PagerDutyListIncidentAlertsParams, + PagerDutyListIncidentAlertsResponse, +} from '@/tools/pagerduty/types' +import type { ToolConfig } from '@/tools/types' + +export const listIncidentAlertsTool: ToolConfig< + PagerDutyListIncidentAlertsParams, + PagerDutyListIncidentAlertsResponse +> = { + id: 'pagerduty_list_incident_alerts', + name: 'PagerDuty List Incident Alerts', + description: 'List the individual alerts attached to a PagerDuty incident.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'PagerDuty REST API Key', + }, + incidentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the incident whose alerts to list', + }, + statuses: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated statuses to filter (triggered, resolved)', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results (max 100)', + }, + offset: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Offset to start pagination search results', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (params.statuses) { + for (const s of params.statuses.split(',')) { + query.append('statuses[]', s.trim()) + } + } + if (params.limit) query.set('limit', params.limit) + if (params.offset) query.set('offset', params.offset) + const qs = query.toString() + return `https://api.pagerduty.com/incidents/${params.incidentId.trim()}/alerts${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Token token=${params.apiKey}`, + Accept: 'application/vnd.pagerduty+json;version=2', + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error?.message || `PagerDuty API error: ${response.status}`) + } + + return { + success: true, + output: { + alerts: (data.alerts ?? []).map( + (alert: Record & { service?: Record }) => ({ + id: alert.id ?? null, + summary: alert.summary ?? null, + status: alert.status ?? null, + severity: alert.severity ?? null, + createdAt: alert.created_at ?? null, + alertKey: alert.alert_key ?? null, + serviceName: alert.service?.summary ?? null, + serviceId: alert.service?.id ?? null, + htmlUrl: alert.html_url ?? null, + }) + ), + total: data.total ?? null, + more: data.more ?? false, + offset: data.offset ?? 0, + }, + } + }, + + outputs: { + alerts: { + type: 'array', + description: 'Array of alerts attached to the incident', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Alert ID' }, + summary: { type: 'string', description: 'Alert summary' }, + status: { type: 'string', description: 'Alert status' }, + severity: { type: 'string', description: 'Alert severity' }, + createdAt: { type: 'string', description: 'Creation timestamp' }, + alertKey: { type: 'string', description: 'De-duplication key' }, + serviceName: { type: 'string', description: 'Service name' }, + serviceId: { type: 'string', description: 'Service ID' }, + htmlUrl: { type: 'string', description: 'PagerDuty web URL' }, + }, + }, + }, + total: { + type: 'number', + description: + 'Total number of matching alerts (null unless explicitly requested by PagerDuty)', + optional: true, + }, + more: { + type: 'boolean', + description: 'Whether more results are available', + }, + offset: { + type: 'number', + description: 'Offset used for this page of results', + }, + }, +} diff --git a/apps/sim/tools/pagerduty/list_incidents.ts b/apps/sim/tools/pagerduty/list_incidents.ts index a2ed3530761..52be2ee1478 100644 --- a/apps/sim/tools/pagerduty/list_incidents.ts +++ b/apps/sim/tools/pagerduty/list_incidents.ts @@ -26,6 +26,12 @@ export const listIncidentsTool: ToolConfig< visibility: 'user-or-llm', description: 'Comma-separated statuses to filter (triggered, acknowledged, resolved)', }, + urgencies: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated urgencies to filter (high, low)', + }, serviceIds: { type: 'string', required: false, @@ -56,6 +62,12 @@ export const listIncidentsTool: ToolConfig< visibility: 'user-or-llm', description: 'Maximum number of results (max 100)', }, + offset: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Offset to start pagination search results', + }, }, request: { @@ -66,6 +78,11 @@ export const listIncidentsTool: ToolConfig< query.append('statuses[]', s.trim()) } } + if (params.urgencies) { + for (const u of params.urgencies.split(',')) { + query.append('urgencies[]', u.trim()) + } + } if (params.serviceIds) { for (const id of params.serviceIds.split(',')) { query.append('service_ids[]', id.trim()) @@ -75,6 +92,7 @@ export const listIncidentsTool: ToolConfig< if (params.until) query.set('until', params.until) if (params.sortBy) query.set('sort_by', params.sortBy) if (params.limit) query.set('limit', params.limit) + if (params.offset) query.set('offset', params.offset) query.append('include[]', 'services') const qs = query.toString() return `https://api.pagerduty.com/incidents${qs ? `?${qs}` : ''}` @@ -120,8 +138,9 @@ export const listIncidentsTool: ToolConfig< htmlUrl: inc.html_url ?? null, }) ), - total: data.total ?? 0, + total: data.total ?? null, more: data.more ?? false, + offset: data.offset ?? 0, }, } }, @@ -151,11 +170,17 @@ export const listIncidentsTool: ToolConfig< }, total: { type: 'number', - description: 'Total number of matching incidents', + description: + 'Total number of matching incidents (null unless explicitly requested by PagerDuty)', + optional: true, }, more: { type: 'boolean', description: 'Whether more results are available', }, + offset: { + type: 'number', + description: 'Offset used for this page of results', + }, }, } diff --git a/apps/sim/tools/pagerduty/list_oncalls.ts b/apps/sim/tools/pagerduty/list_oncalls.ts index 92f436b9c39..aeb4aa2c6c5 100644 --- a/apps/sim/tools/pagerduty/list_oncalls.ts +++ b/apps/sim/tools/pagerduty/list_oncalls.ts @@ -48,6 +48,12 @@ export const listOncallsTool: ToolConfig = { + id: 'pagerduty_list_schedules', + name: 'PagerDuty List Schedules', + description: 'List on-call schedules from PagerDuty with an optional name filter.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'PagerDuty REST API Key', + }, + query: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter schedules by name', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results (max 100)', + }, + offset: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Offset to start pagination search results', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (params.query) query.set('query', params.query) + if (params.limit) query.set('limit', params.limit) + if (params.offset) query.set('offset', params.offset) + const qs = query.toString() + return `https://api.pagerduty.com/schedules${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Token token=${params.apiKey}`, + Accept: 'application/vnd.pagerduty+json;version=2', + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error?.message || `PagerDuty API error: ${response.status}`) + } + + return { + success: true, + output: { + schedules: (data.schedules ?? []).map((sched: Record) => ({ + id: sched.id ?? null, + name: sched.name ?? null, + description: sched.description ?? null, + timeZone: sched.time_zone ?? null, + htmlUrl: sched.html_url ?? null, + })), + total: data.total ?? null, + more: data.more ?? false, + offset: data.offset ?? 0, + }, + } + }, + + outputs: { + schedules: { + type: 'array', + description: 'Array of on-call schedules', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Schedule ID' }, + name: { type: 'string', description: 'Schedule name' }, + description: { type: 'string', description: 'Schedule description' }, + timeZone: { type: 'string', description: 'Schedule time zone' }, + htmlUrl: { type: 'string', description: 'PagerDuty web URL' }, + }, + }, + }, + total: { + type: 'number', + description: + 'Total number of matching schedules (null unless explicitly requested by PagerDuty)', + optional: true, + }, + more: { + type: 'boolean', + description: 'Whether more results are available', + }, + offset: { + type: 'number', + description: 'Offset used for this page of results', + }, + }, +} diff --git a/apps/sim/tools/pagerduty/list_services.ts b/apps/sim/tools/pagerduty/list_services.ts index af281ffc837..beb1c0cf3d5 100644 --- a/apps/sim/tools/pagerduty/list_services.ts +++ b/apps/sim/tools/pagerduty/list_services.ts @@ -32,6 +32,12 @@ export const listServicesTool: ToolConfig< visibility: 'user-or-llm', description: 'Maximum number of results (max 100)', }, + offset: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Offset to start pagination search results', + }, }, request: { @@ -39,6 +45,7 @@ export const listServicesTool: ToolConfig< const query = new URLSearchParams() if (params.query) query.set('query', params.query) if (params.limit) query.set('limit', params.limit) + if (params.offset) query.set('offset', params.offset) const qs = query.toString() return `https://api.pagerduty.com/services${qs ? `?${qs}` : ''}` }, @@ -72,8 +79,9 @@ export const listServicesTool: ToolConfig< htmlUrl: svc.html_url ?? null, }) ), - total: data.total ?? 0, + total: data.total ?? null, more: data.more ?? false, + offset: data.offset ?? 0, }, } }, @@ -98,11 +106,17 @@ export const listServicesTool: ToolConfig< }, total: { type: 'number', - description: 'Total number of matching services', + description: + 'Total number of matching services (null unless explicitly requested by PagerDuty)', + optional: true, }, more: { type: 'boolean', description: 'Whether more results are available', }, + offset: { + type: 'number', + description: 'Offset used for this page of results', + }, }, } diff --git a/apps/sim/tools/pagerduty/list_users.ts b/apps/sim/tools/pagerduty/list_users.ts new file mode 100644 index 00000000000..48ba74af532 --- /dev/null +++ b/apps/sim/tools/pagerduty/list_users.ts @@ -0,0 +1,111 @@ +import type { PagerDutyListUsersParams, PagerDutyListUsersResponse } from '@/tools/pagerduty/types' +import type { ToolConfig } from '@/tools/types' + +export const listUsersTool: ToolConfig = { + id: 'pagerduty_list_users', + name: 'PagerDuty List Users', + description: 'List users from PagerDuty with an optional name/email filter.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'PagerDuty REST API Key', + }, + query: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter users by name or email', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results (max 100)', + }, + offset: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Offset to start pagination search results', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (params.query) query.set('query', params.query) + if (params.limit) query.set('limit', params.limit) + if (params.offset) query.set('offset', params.offset) + const qs = query.toString() + return `https://api.pagerduty.com/users${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Token token=${params.apiKey}`, + Accept: 'application/vnd.pagerduty+json;version=2', + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error?.message || `PagerDuty API error: ${response.status}`) + } + + return { + success: true, + output: { + users: (data.users ?? []).map((u: Record) => ({ + id: u.id ?? null, + name: u.name ?? null, + email: u.email ?? null, + role: u.role ?? null, + jobTitle: u.job_title ?? null, + timeZone: u.time_zone ?? null, + htmlUrl: u.html_url ?? null, + })), + total: data.total ?? null, + more: data.more ?? false, + offset: data.offset ?? 0, + }, + } + }, + + outputs: { + users: { + type: 'array', + description: 'Array of users', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'User ID' }, + name: { type: 'string', description: 'User name' }, + email: { type: 'string', description: 'User email' }, + role: { type: 'string', description: 'User role' }, + jobTitle: { type: 'string', description: 'User job title' }, + timeZone: { type: 'string', description: 'User preferred time zone' }, + htmlUrl: { type: 'string', description: 'PagerDuty web URL' }, + }, + }, + }, + total: { + type: 'number', + description: 'Total number of matching users (null unless explicitly requested by PagerDuty)', + optional: true, + }, + more: { + type: 'boolean', + description: 'Whether more results are available', + }, + offset: { + type: 'number', + description: 'Offset used for this page of results', + }, + }, +} diff --git a/apps/sim/tools/pagerduty/merge_incidents.ts b/apps/sim/tools/pagerduty/merge_incidents.ts new file mode 100644 index 00000000000..68e59d1cc0d --- /dev/null +++ b/apps/sim/tools/pagerduty/merge_incidents.ts @@ -0,0 +1,88 @@ +import type { + PagerDutyMergeIncidentsParams, + PagerDutyMergeIncidentsResponse, +} from '@/tools/pagerduty/types' +import type { ToolConfig } from '@/tools/types' + +export const mergeIncidentsTool: ToolConfig< + PagerDutyMergeIncidentsParams, + PagerDutyMergeIncidentsResponse +> = { + id: 'pagerduty_merge_incidents', + name: 'PagerDuty Merge Incidents', + description: + 'Merge one or more source incidents into a target incident. Source incidents are resolved and their alerts move to the target.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'PagerDuty REST API Key', + }, + fromEmail: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Email address of a valid PagerDuty user', + }, + targetIncidentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the incident that will absorb the source incidents', + }, + sourceIncidentIds: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Comma-separated IDs of the incidents to merge into the target incident', + }, + }, + + request: { + url: (params) => `https://api.pagerduty.com/incidents/${params.targetIncidentId.trim()}/merge`, + method: 'PUT', + headers: (params) => ({ + Authorization: `Token token=${params.apiKey}`, + Accept: 'application/vnd.pagerduty+json;version=2', + 'Content-Type': 'application/json', + From: params.fromEmail, + }), + body: (params) => ({ + source_incidents: params.sourceIncidentIds.split(',').map((id) => ({ + id: id.trim(), + type: 'incident_reference', + })), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error?.message || `PagerDuty API error: ${response.status}`) + } + + const inc = data.incident ?? {} + return { + success: true, + output: { + id: inc.id ?? null, + incidentNumber: inc.incident_number ?? null, + title: inc.title ?? null, + status: inc.status ?? null, + htmlUrl: inc.html_url ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Target incident ID' }, + incidentNumber: { type: 'number', description: 'Target incident number' }, + title: { type: 'string', description: 'Target incident title' }, + status: { type: 'string', description: 'Target incident status' }, + htmlUrl: { type: 'string', description: 'PagerDuty web URL' }, + }, +} diff --git a/apps/sim/tools/pagerduty/send_event.ts b/apps/sim/tools/pagerduty/send_event.ts new file mode 100644 index 00000000000..18e61f392ba --- /dev/null +++ b/apps/sim/tools/pagerduty/send_event.ts @@ -0,0 +1,122 @@ +import type { PagerDutySendEventParams, PagerDutySendEventResponse } from '@/tools/pagerduty/types' +import type { ToolConfig } from '@/tools/types' + +export const sendEventTool: ToolConfig = { + id: 'pagerduty_send_event', + name: 'PagerDuty Send Event', + description: + 'Send a trigger, acknowledge, or resolve event to PagerDuty Events API v2 using a service integration key. Used to page from monitoring/alerting sources without a PagerDuty user account.', + version: '1.0.0', + + params: { + routingKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'The Events API v2 integration key (routing key) for the target service', + }, + eventAction: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Event action: trigger, acknowledge, or resolve', + }, + summary: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Brief summary of the event. Required when eventAction is trigger', + }, + source: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Unique location of the affected system (e.g. hostname). Required when eventAction is trigger', + }, + severity: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Perceived severity: critical, warning, error, or info. Required when eventAction is trigger', + }, + dedupKey: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'De-duplication key identifying the alert. Required when eventAction is acknowledge or resolve; optional on trigger', + }, + component: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Component of the source machine responsible for the event', + }, + group: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Logical grouping of components of a service', + }, + class: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'The class/type of the event', + }, + }, + + request: { + url: 'https://events.pagerduty.com/v2/enqueue', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = { + routing_key: params.routingKey, + event_action: params.eventAction, + } + + if (params.dedupKey) body.dedup_key = params.dedupKey + + if (params.eventAction === 'trigger') { + body.payload = { + summary: params.summary, + source: params.source, + severity: params.severity, + ...(params.component && { component: params.component }), + ...(params.group && { group: params.group }), + ...(params.class && { class: params.class }), + } + } + + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || `PagerDuty Events API error: ${response.status}`) + } + + return { + success: true, + output: { + status: data.status ?? null, + message: data.message ?? null, + dedupKey: data.dedup_key ?? null, + }, + } + }, + + outputs: { + status: { type: 'string', description: 'Result status ("success" if accepted)' }, + message: { type: 'string', description: 'Description of the result', optional: true }, + dedupKey: { type: 'string', description: 'De-duplication key for the alert', optional: true }, + }, +} diff --git a/apps/sim/tools/pagerduty/snooze_incident.ts b/apps/sim/tools/pagerduty/snooze_incident.ts new file mode 100644 index 00000000000..c4b4e1f8f2a --- /dev/null +++ b/apps/sim/tools/pagerduty/snooze_incident.ts @@ -0,0 +1,83 @@ +import type { + PagerDutySnoozeIncidentParams, + PagerDutySnoozeIncidentResponse, +} from '@/tools/pagerduty/types' +import type { ToolConfig } from '@/tools/types' + +export const snoozeIncidentTool: ToolConfig< + PagerDutySnoozeIncidentParams, + PagerDutySnoozeIncidentResponse +> = { + id: 'pagerduty_snooze_incident', + name: 'PagerDuty Snooze Incident', + description: + 'Snooze a triggered PagerDuty incident for a number of seconds, after which it returns to triggered.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'PagerDuty REST API Key', + }, + fromEmail: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Email address of a valid PagerDuty user', + }, + incidentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the incident to snooze', + }, + duration: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Number of seconds to snooze the incident for (1 to 604800)', + }, + }, + + request: { + url: (params) => `https://api.pagerduty.com/incidents/${params.incidentId.trim()}/snooze`, + method: 'POST', + headers: (params) => ({ + Authorization: `Token token=${params.apiKey}`, + Accept: 'application/vnd.pagerduty+json;version=2', + 'Content-Type': 'application/json', + From: params.fromEmail, + }), + body: (params) => ({ + duration: Number(params.duration), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error?.message || `PagerDuty API error: ${response.status}`) + } + + const inc = data.incident ?? {} + return { + success: true, + output: { + id: inc.id ?? null, + incidentNumber: inc.incident_number ?? null, + status: inc.status ?? null, + htmlUrl: inc.html_url ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Incident ID' }, + incidentNumber: { type: 'number', description: 'Incident number' }, + status: { type: 'string', description: 'Incident status after snoozing' }, + htmlUrl: { type: 'string', description: 'PagerDuty web URL' }, + }, +} diff --git a/apps/sim/tools/pagerduty/types.ts b/apps/sim/tools/pagerduty/types.ts index 827821c6186..a03ff404e63 100644 --- a/apps/sim/tools/pagerduty/types.ts +++ b/apps/sim/tools/pagerduty/types.ts @@ -19,11 +19,13 @@ interface PagerDutyWriteParams extends PagerDutyBaseParams { */ export interface PagerDutyListIncidentsParams extends PagerDutyBaseParams { statuses?: string + urgencies?: string serviceIds?: string since?: string until?: string sortBy?: string limit?: string + offset?: string } export interface PagerDutyListIncidentsResponse extends ToolResponse { @@ -43,8 +45,37 @@ export interface PagerDutyListIncidentsResponse extends ToolResponse { escalationPolicyName: string | null htmlUrl: string | null }> - total: number + total: number | null more: boolean + offset: number + } +} + +/** + * Get Incident params. + */ +export interface PagerDutyGetIncidentParams extends PagerDutyBaseParams { + incidentId: string +} + +export interface PagerDutyGetIncidentResponse extends ToolResponse { + output: { + id: string + incidentNumber: number + title: string + status: string + urgency: string + createdAt: string + updatedAt: string | null + resolvedAt: string | null + serviceName: string | null + serviceId: string | null + assigneeName: string | null + assigneeId: string | null + escalationPolicyName: string | null + escalationPolicyId: string | null + incidentKey: string | null + htmlUrl: string | null } } @@ -58,6 +89,7 @@ export interface PagerDutyCreateIncidentParams extends PagerDutyWriteParams { body?: string escalationPolicyId?: string assigneeId?: string + incidentKey?: string } export interface PagerDutyCreateIncidentResponse extends ToolResponse { @@ -83,6 +115,7 @@ export interface PagerDutyUpdateIncidentParams extends PagerDutyWriteParams { title?: string urgency?: string escalationLevel?: string + resolution?: string } export interface PagerDutyUpdateIncidentResponse extends ToolResponse { @@ -120,6 +153,7 @@ export interface PagerDutyAddNoteResponse extends ToolResponse { export interface PagerDutyListServicesParams extends PagerDutyBaseParams { query?: string limit?: string + offset?: string } export interface PagerDutyListServicesResponse extends ToolResponse { @@ -134,8 +168,32 @@ export interface PagerDutyListServicesResponse extends ToolResponse { createdAt: string htmlUrl: string | null }> - total: number + total: number | null more: boolean + offset: number + } +} + +/** + * Get Service params. + */ +export interface PagerDutyGetServiceParams extends PagerDutyBaseParams { + serviceId: string +} + +export interface PagerDutyGetServiceResponse extends ToolResponse { + output: { + id: string + name: string + description: string | null + status: string + autoResolveTimeout: number | null + acknowledgementTimeout: number | null + createdAt: string | null + lastIncidentTimestamp: string | null + escalationPolicyName: string | null + escalationPolicyId: string | null + htmlUrl: string | null } } @@ -148,6 +206,7 @@ export interface PagerDutyListOncallsParams extends PagerDutyBaseParams { since?: string until?: string limit?: string + offset?: string } export interface PagerDutyListOncallsResponse extends ToolResponse { @@ -163,7 +222,170 @@ export interface PagerDutyListOncallsResponse extends ToolResponse { start: string | null end: string | null }> - total: number + total: number | null + more: boolean + offset: number + } +} + +/** + * List Escalation Policies params. + */ +export interface PagerDutyListEscalationPoliciesParams extends PagerDutyBaseParams { + query?: string + limit?: string + offset?: string +} + +export interface PagerDutyListEscalationPoliciesResponse extends ToolResponse { + output: { + escalationPolicies: Array<{ + id: string + name: string + description: string | null + numLoops: number + onCallHandoffNotifications: string | null + htmlUrl: string | null + }> + total: number | null + more: boolean + offset: number + } +} + +/** + * List Schedules params. + */ +export interface PagerDutyListSchedulesParams extends PagerDutyBaseParams { + query?: string + limit?: string + offset?: string +} + +export interface PagerDutyListSchedulesResponse extends ToolResponse { + output: { + schedules: Array<{ + id: string + name: string + description: string | null + timeZone: string | null + htmlUrl: string | null + }> + total: number | null + more: boolean + offset: number + } +} + +/** + * List Users params. + */ +export interface PagerDutyListUsersParams extends PagerDutyBaseParams { + query?: string + limit?: string + offset?: string +} + +export interface PagerDutyListUsersResponse extends ToolResponse { + output: { + users: Array<{ + id: string + name: string + email: string + role: string | null + jobTitle: string | null + timeZone: string | null + htmlUrl: string | null + }> + total: number | null + more: boolean + offset: number + } +} + +/** + * Snooze Incident params. + */ +export interface PagerDutySnoozeIncidentParams extends PagerDutyWriteParams { + incidentId: string + duration: string +} + +export interface PagerDutySnoozeIncidentResponse extends ToolResponse { + output: { + id: string + incidentNumber: number + status: string + htmlUrl: string | null + } +} + +/** + * Merge Incidents params. + */ +export interface PagerDutyMergeIncidentsParams extends PagerDutyWriteParams { + targetIncidentId: string + sourceIncidentIds: string +} + +export interface PagerDutyMergeIncidentsResponse extends ToolResponse { + output: { + id: string + incidentNumber: number + title: string + status: string + htmlUrl: string | null + } +} + +/** + * List Incident Alerts params. + */ +export interface PagerDutyListIncidentAlertsParams extends PagerDutyBaseParams { + incidentId: string + statuses?: string + limit?: string + offset?: string +} + +export interface PagerDutyListIncidentAlertsResponse extends ToolResponse { + output: { + alerts: Array<{ + id: string + summary: string | null + status: string + severity: string | null + createdAt: string + alertKey: string | null + serviceName: string | null + serviceId: string | null + htmlUrl: string | null + }> + total: number | null more: boolean + offset: number + } +} + +/** + * Send Event (Events API v2) params. + */ +export interface PagerDutySendEventParams { + routingKey: string + eventAction: string + summary?: string + source?: string + severity?: string + dedupKey?: string + component?: string + group?: string + class?: string +} + +export interface PagerDutySendEventResponse extends ToolResponse { + output: { + status: string + message: string | null + dedupKey: string | null } } diff --git a/apps/sim/tools/pagerduty/update_incident.ts b/apps/sim/tools/pagerduty/update_incident.ts index 156b5a1ad57..598fc1b5db9 100644 --- a/apps/sim/tools/pagerduty/update_incident.ts +++ b/apps/sim/tools/pagerduty/update_incident.ts @@ -56,6 +56,13 @@ export const updateIncidentTool: ToolConfig< visibility: 'user-or-llm', description: 'Escalation level to escalate to', }, + resolution: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + "Resolution note added to the incident's log entry. Only used when status is set to resolved", + }, }, request: { @@ -79,6 +86,7 @@ export const updateIncidentTool: ToolConfig< if (params.escalationLevel) { incident.escalation_level = Number(params.escalationLevel) } + if (params.resolution) incident.resolution = params.resolution return { incident } }, }, diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index c293f27acf2..974df283f65 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -2479,9 +2479,18 @@ import { import { pagerdutyAddNoteTool, pagerdutyCreateIncidentTool, + pagerdutyGetIncidentTool, + pagerdutyGetServiceTool, + pagerdutyListEscalationPoliciesTool, + pagerdutyListIncidentAlertsTool, pagerdutyListIncidentsTool, pagerdutyListOncallsTool, + pagerdutyListSchedulesTool, pagerdutyListServicesTool, + pagerdutyListUsersTool, + pagerdutyMergeIncidentsTool, + pagerdutySendEventTool, + pagerdutySnoozeIncidentTool, pagerdutyUpdateIncidentTool, } from '@/tools/pagerduty' import { parallelDeepResearchTool, parallelExtractTool, parallelSearchTool } from '@/tools/parallel' @@ -7279,11 +7288,20 @@ export const tools: Record = { outlook_search: outlookSearchTool, outlook_update_message: outlookUpdateMessageTool, pagerduty_list_incidents: pagerdutyListIncidentsTool, + pagerduty_get_incident: pagerdutyGetIncidentTool, pagerduty_create_incident: pagerdutyCreateIncidentTool, pagerduty_update_incident: pagerdutyUpdateIncidentTool, + pagerduty_snooze_incident: pagerdutySnoozeIncidentTool, + pagerduty_merge_incidents: pagerdutyMergeIncidentsTool, pagerduty_add_note: pagerdutyAddNoteTool, + pagerduty_list_incident_alerts: pagerdutyListIncidentAlertsTool, pagerduty_list_services: pagerdutyListServicesTool, + pagerduty_get_service: pagerdutyGetServiceTool, pagerduty_list_oncalls: pagerdutyListOncallsTool, + pagerduty_list_escalation_policies: pagerdutyListEscalationPoliciesTool, + pagerduty_list_schedules: pagerdutyListSchedulesTool, + pagerduty_list_users: pagerdutyListUsersTool, + pagerduty_send_event: pagerdutySendEventTool, linear_read_issues: linearReadIssuesTool, linear_create_issue: linearCreateIssueTool, linear_get_issue: linearGetIssueTool, From 904886ad2473cef89b6d27a606157cf1088d1399 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 6 Jul 2026 15:02:01 -0700 Subject: [PATCH 2/5] fix(pagerduty): address review findings on send_event/snooze/merge validation - match required condition to visibility condition for eventSummary/eventSource (trigger-only) - validate trigger payload has summary/source/severity before sending, throw descriptive error - validate snooze duration is finite and within PagerDuty's 1-604800 range - drop empty segments when splitting merge source incident IDs --- apps/sim/blocks/blocks/pagerduty.ts | 12 ++++++++++-- apps/sim/tools/pagerduty/merge_incidents.ts | 12 ++++++++---- apps/sim/tools/pagerduty/send_event.ts | 4 ++++ apps/sim/tools/pagerduty/snooze_incident.ts | 10 +++++++--- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/apps/sim/blocks/blocks/pagerduty.ts b/apps/sim/blocks/blocks/pagerduty.ts index f15b6db1fc7..3faf31345d8 100644 --- a/apps/sim/blocks/blocks/pagerduty.ts +++ b/apps/sim/blocks/blocks/pagerduty.ts @@ -591,7 +591,11 @@ export const PagerDutyBlock: BlockConfig = { id: 'eventSummary', title: 'Summary', type: 'short-input', - required: { field: 'operation', value: 'send_event' }, + required: { + field: 'operation', + value: 'send_event', + and: { field: 'eventAction', value: 'trigger' }, + }, placeholder: 'Brief summary of the event', condition: { field: 'operation', @@ -603,7 +607,11 @@ export const PagerDutyBlock: BlockConfig = { id: 'eventSource', title: 'Source', type: 'short-input', - required: { field: 'operation', value: 'send_event' }, + required: { + field: 'operation', + value: 'send_event', + and: { field: 'eventAction', value: 'trigger' }, + }, placeholder: 'Affected system, e.g. a hostname', condition: { field: 'operation', diff --git a/apps/sim/tools/pagerduty/merge_incidents.ts b/apps/sim/tools/pagerduty/merge_incidents.ts index 68e59d1cc0d..5021f3f1add 100644 --- a/apps/sim/tools/pagerduty/merge_incidents.ts +++ b/apps/sim/tools/pagerduty/merge_incidents.ts @@ -51,10 +51,14 @@ export const mergeIncidentsTool: ToolConfig< From: params.fromEmail, }), body: (params) => ({ - source_incidents: params.sourceIncidentIds.split(',').map((id) => ({ - id: id.trim(), - type: 'incident_reference', - })), + source_incidents: params.sourceIncidentIds + .split(',') + .map((id) => id.trim()) + .filter((id) => id.length > 0) + .map((id) => ({ + id, + type: 'incident_reference', + })), }), }, diff --git a/apps/sim/tools/pagerduty/send_event.ts b/apps/sim/tools/pagerduty/send_event.ts index 18e61f392ba..1011a7c54ae 100644 --- a/apps/sim/tools/pagerduty/send_event.ts +++ b/apps/sim/tools/pagerduty/send_event.ts @@ -83,6 +83,10 @@ export const sendEventTool: ToolConfig ({ - duration: Number(params.duration), - }), + body: (params) => { + const duration = Number(params.duration) + if (!Number.isFinite(duration) || duration < 1 || duration > 604800) { + throw new Error('duration must be a whole number of seconds between 1 and 604800') + } + return { duration } + }, }, transformResponse: async (response: Response) => { From c3a080c09e3e2a122b0bcee6c01deaa963bd8497 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 6 Jul 2026 15:17:08 -0700 Subject: [PATCH 3/5] fix(pagerduty): reject empty merge sources, enforce dedupKey, require integer snooze duration - merge_incidents: throw if source_incidents ends up empty after filtering blanks - send_event: require dedupKey for acknowledge/resolve to match block's required condition - snooze_incident: require an integer duration, not just a finite number --- apps/sim/tools/pagerduty/merge_incidents.ts | 15 +++++++++++---- apps/sim/tools/pagerduty/send_event.ts | 6 ++++++ apps/sim/tools/pagerduty/snooze_incident.ts | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/apps/sim/tools/pagerduty/merge_incidents.ts b/apps/sim/tools/pagerduty/merge_incidents.ts index 5021f3f1add..9de264c1a11 100644 --- a/apps/sim/tools/pagerduty/merge_incidents.ts +++ b/apps/sim/tools/pagerduty/merge_incidents.ts @@ -50,16 +50,23 @@ export const mergeIncidentsTool: ToolConfig< 'Content-Type': 'application/json', From: params.fromEmail, }), - body: (params) => ({ - source_incidents: params.sourceIncidentIds + body: (params) => { + const sourceIds = params.sourceIncidentIds .split(',') .map((id) => id.trim()) .filter((id) => id.length > 0) - .map((id) => ({ + + if (sourceIds.length === 0) { + throw new Error('sourceIncidentIds must contain at least one non-empty incident ID') + } + + return { + source_incidents: sourceIds.map((id) => ({ id, type: 'incident_reference', })), - }), + } + }, }, transformResponse: async (response: Response) => { diff --git a/apps/sim/tools/pagerduty/send_event.ts b/apps/sim/tools/pagerduty/send_event.ts index 1011a7c54ae..2b4e7a1c2d2 100644 --- a/apps/sim/tools/pagerduty/send_event.ts +++ b/apps/sim/tools/pagerduty/send_event.ts @@ -80,6 +80,12 @@ export const sendEventTool: ToolConfig { const duration = Number(params.duration) - if (!Number.isFinite(duration) || duration < 1 || duration > 604800) { + if (!Number.isInteger(duration) || duration < 1 || duration > 604800) { throw new Error('duration must be a whole number of seconds between 1 and 604800') } return { duration } From 8d6d341bce8e7654a4437df424289cd6a02ed48e Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 6 Jul 2026 15:27:04 -0700 Subject: [PATCH 4/5] fix(pagerduty): reject resolution note unless status is resolved PagerDuty only accepts an incident's resolution field when status is being set to resolved in the same request; sending it otherwise gets rejected by the API with an opaque error. --- apps/sim/tools/pagerduty/update_incident.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/sim/tools/pagerduty/update_incident.ts b/apps/sim/tools/pagerduty/update_incident.ts index 598fc1b5db9..2ca16d51fea 100644 --- a/apps/sim/tools/pagerduty/update_incident.ts +++ b/apps/sim/tools/pagerduty/update_incident.ts @@ -86,7 +86,12 @@ export const updateIncidentTool: ToolConfig< if (params.escalationLevel) { incident.escalation_level = Number(params.escalationLevel) } - if (params.resolution) incident.resolution = params.resolution + if (params.resolution) { + if (params.status !== 'resolved') { + throw new Error('resolution can only be set when status is resolved') + } + incident.resolution = params.resolution + } return { incident } }, }, From ab9a335c2cb3aafb9d4df85b7abec383f6ce2fea Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 6 Jul 2026 15:38:49 -0700 Subject: [PATCH 5/5] docs(pagerduty): mention triggered as a valid update_incident status --- apps/sim/tools/pagerduty/update_incident.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/tools/pagerduty/update_incident.ts b/apps/sim/tools/pagerduty/update_incident.ts index 2ca16d51fea..803690decac 100644 --- a/apps/sim/tools/pagerduty/update_incident.ts +++ b/apps/sim/tools/pagerduty/update_incident.ts @@ -36,7 +36,7 @@ export const updateIncidentTool: ToolConfig< type: 'string', required: false, visibility: 'user-or-llm', - description: 'New status (acknowledged or resolved)', + description: 'New status (triggered, acknowledged, or resolved)', }, title: { type: 'string',