Skip to content

Commit be6f693

Browse files
authored
feat(pagerduty): validate integration + add 9 tools for deeper API coverage (#5446)
* 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 * 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 * 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 * 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. * docs(pagerduty): mention triggered as a valid update_incident status
1 parent d64ad85 commit be6f693

18 files changed

Lines changed: 1898 additions & 23 deletions

apps/sim/blocks/blocks/pagerduty.ts

Lines changed: 577 additions & 13 deletions
Large diffs are not rendered by default.

apps/sim/tools/pagerduty/create_incident.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ export const createIncidentTool: ToolConfig<
6262
visibility: 'user-or-llm',
6363
description: 'User ID to assign the incident to',
6464
},
65+
incidentKey: {
66+
type: 'string',
67+
required: false,
68+
visibility: 'user-or-llm',
69+
description:
70+
'De-duplication key. A subsequent request with the same service and incident key updates the existing open incident instead of creating a new one',
71+
},
6572
},
6673

6774
request: {
@@ -106,6 +113,7 @@ export const createIncidentTool: ToolConfig<
106113
},
107114
]
108115
}
116+
if (params.incidentKey) incident.incident_key = params.incidentKey
109117

110118
return { incident }
111119
},
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import type {
2+
PagerDutyGetIncidentParams,
3+
PagerDutyGetIncidentResponse,
4+
} from '@/tools/pagerduty/types'
5+
import type { ToolConfig } from '@/tools/types'
6+
7+
export const getIncidentTool: ToolConfig<PagerDutyGetIncidentParams, PagerDutyGetIncidentResponse> =
8+
{
9+
id: 'pagerduty_get_incident',
10+
name: 'PagerDuty Get Incident',
11+
description: 'Get a single incident from PagerDuty by ID.',
12+
version: '1.0.0',
13+
14+
params: {
15+
apiKey: {
16+
type: 'string',
17+
required: true,
18+
visibility: 'user-only',
19+
description: 'PagerDuty REST API Key',
20+
},
21+
incidentId: {
22+
type: 'string',
23+
required: true,
24+
visibility: 'user-or-llm',
25+
description: 'ID of the incident to fetch',
26+
},
27+
},
28+
29+
request: {
30+
url: (params) =>
31+
`https://api.pagerduty.com/incidents/${params.incidentId.trim()}?include[]=services`,
32+
method: 'GET',
33+
headers: (params) => ({
34+
Authorization: `Token token=${params.apiKey}`,
35+
Accept: 'application/vnd.pagerduty+json;version=2',
36+
'Content-Type': 'application/json',
37+
}),
38+
},
39+
40+
transformResponse: async (response: Response) => {
41+
const data = await response.json()
42+
43+
if (!response.ok) {
44+
throw new Error(data.error?.message || `PagerDuty API error: ${response.status}`)
45+
}
46+
47+
const inc = data.incident ?? {}
48+
return {
49+
success: true,
50+
output: {
51+
id: inc.id ?? null,
52+
incidentNumber: inc.incident_number ?? null,
53+
title: inc.title ?? null,
54+
status: inc.status ?? null,
55+
urgency: inc.urgency ?? null,
56+
createdAt: inc.created_at ?? null,
57+
updatedAt: inc.updated_at ?? null,
58+
resolvedAt: inc.resolved_at ?? null,
59+
serviceName: inc.service?.summary ?? null,
60+
serviceId: inc.service?.id ?? null,
61+
assigneeName: inc.assignments?.[0]?.assignee?.summary ?? null,
62+
assigneeId: inc.assignments?.[0]?.assignee?.id ?? null,
63+
escalationPolicyName: inc.escalation_policy?.summary ?? null,
64+
escalationPolicyId: inc.escalation_policy?.id ?? null,
65+
incidentKey: inc.incident_key ?? null,
66+
htmlUrl: inc.html_url ?? null,
67+
},
68+
}
69+
},
70+
71+
outputs: {
72+
id: { type: 'string', description: 'Incident ID' },
73+
incidentNumber: { type: 'number', description: 'Incident number' },
74+
title: { type: 'string', description: 'Incident title' },
75+
status: { type: 'string', description: 'Incident status' },
76+
urgency: { type: 'string', description: 'Incident urgency' },
77+
createdAt: { type: 'string', description: 'Creation timestamp' },
78+
updatedAt: { type: 'string', description: 'Last updated timestamp', optional: true },
79+
resolvedAt: { type: 'string', description: 'Resolution timestamp', optional: true },
80+
serviceName: { type: 'string', description: 'Service name', optional: true },
81+
serviceId: { type: 'string', description: 'Service ID', optional: true },
82+
assigneeName: { type: 'string', description: 'Assignee name', optional: true },
83+
assigneeId: { type: 'string', description: 'Assignee ID', optional: true },
84+
escalationPolicyName: {
85+
type: 'string',
86+
description: 'Escalation policy name',
87+
optional: true,
88+
},
89+
escalationPolicyId: { type: 'string', description: 'Escalation policy ID', optional: true },
90+
incidentKey: { type: 'string', description: 'De-duplication key', optional: true },
91+
htmlUrl: { type: 'string', description: 'PagerDuty web URL' },
92+
},
93+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import type {
2+
PagerDutyGetServiceParams,
3+
PagerDutyGetServiceResponse,
4+
} from '@/tools/pagerduty/types'
5+
import type { ToolConfig } from '@/tools/types'
6+
7+
export const getServiceTool: ToolConfig<PagerDutyGetServiceParams, PagerDutyGetServiceResponse> = {
8+
id: 'pagerduty_get_service',
9+
name: 'PagerDuty Get Service',
10+
description: 'Get a single service from PagerDuty by ID.',
11+
version: '1.0.0',
12+
13+
params: {
14+
apiKey: {
15+
type: 'string',
16+
required: true,
17+
visibility: 'user-only',
18+
description: 'PagerDuty REST API Key',
19+
},
20+
serviceId: {
21+
type: 'string',
22+
required: true,
23+
visibility: 'user-or-llm',
24+
description: 'ID of the service to fetch',
25+
},
26+
},
27+
28+
request: {
29+
url: (params) =>
30+
`https://api.pagerduty.com/services/${params.serviceId.trim()}?include[]=escalation_policies`,
31+
method: 'GET',
32+
headers: (params) => ({
33+
Authorization: `Token token=${params.apiKey}`,
34+
Accept: 'application/vnd.pagerduty+json;version=2',
35+
'Content-Type': 'application/json',
36+
}),
37+
},
38+
39+
transformResponse: async (response: Response) => {
40+
const data = await response.json()
41+
42+
if (!response.ok) {
43+
throw new Error(data.error?.message || `PagerDuty API error: ${response.status}`)
44+
}
45+
46+
const svc = data.service ?? {}
47+
return {
48+
success: true,
49+
output: {
50+
id: svc.id ?? null,
51+
name: svc.name ?? null,
52+
description: svc.description ?? null,
53+
status: svc.status ?? null,
54+
autoResolveTimeout: svc.auto_resolve_timeout ?? null,
55+
acknowledgementTimeout: svc.acknowledgement_timeout ?? null,
56+
createdAt: svc.created_at ?? null,
57+
lastIncidentTimestamp: svc.last_incident_timestamp ?? null,
58+
escalationPolicyName: svc.escalation_policy?.summary ?? null,
59+
escalationPolicyId: svc.escalation_policy?.id ?? null,
60+
htmlUrl: svc.html_url ?? null,
61+
},
62+
}
63+
},
64+
65+
outputs: {
66+
id: { type: 'string', description: 'Service ID' },
67+
name: { type: 'string', description: 'Service name' },
68+
description: { type: 'string', description: 'Service description', optional: true },
69+
status: { type: 'string', description: 'Service status' },
70+
autoResolveTimeout: {
71+
type: 'number',
72+
description: 'Seconds before an open incident auto-resolves',
73+
optional: true,
74+
},
75+
acknowledgementTimeout: {
76+
type: 'number',
77+
description: 'Seconds before an acknowledged incident reverts to triggered',
78+
optional: true,
79+
},
80+
createdAt: { type: 'string', description: 'Creation timestamp', optional: true },
81+
lastIncidentTimestamp: {
82+
type: 'string',
83+
description: 'Timestamp of the most recent incident',
84+
optional: true,
85+
},
86+
escalationPolicyName: { type: 'string', description: 'Escalation policy name', optional: true },
87+
escalationPolicyId: { type: 'string', description: 'Escalation policy ID', optional: true },
88+
htmlUrl: { type: 'string', description: 'PagerDuty web URL' },
89+
},
90+
}

apps/sim/tools/pagerduty/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
11
import { addNoteTool } from '@/tools/pagerduty/add_note'
22
import { createIncidentTool } from '@/tools/pagerduty/create_incident'
3+
import { getIncidentTool } from '@/tools/pagerduty/get_incident'
4+
import { getServiceTool } from '@/tools/pagerduty/get_service'
5+
import { listEscalationPoliciesTool } from '@/tools/pagerduty/list_escalation_policies'
6+
import { listIncidentAlertsTool } from '@/tools/pagerduty/list_incident_alerts'
37
import { listIncidentsTool } from '@/tools/pagerduty/list_incidents'
48
import { listOncallsTool } from '@/tools/pagerduty/list_oncalls'
9+
import { listSchedulesTool } from '@/tools/pagerduty/list_schedules'
510
import { listServicesTool } from '@/tools/pagerduty/list_services'
11+
import { listUsersTool } from '@/tools/pagerduty/list_users'
12+
import { mergeIncidentsTool } from '@/tools/pagerduty/merge_incidents'
13+
import { sendEventTool } from '@/tools/pagerduty/send_event'
14+
import { snoozeIncidentTool } from '@/tools/pagerduty/snooze_incident'
615
import { updateIncidentTool } from '@/tools/pagerduty/update_incident'
716

817
export const pagerdutyListIncidentsTool = listIncidentsTool
18+
export const pagerdutyGetIncidentTool = getIncidentTool
919
export const pagerdutyCreateIncidentTool = createIncidentTool
1020
export const pagerdutyUpdateIncidentTool = updateIncidentTool
21+
export const pagerdutySnoozeIncidentTool = snoozeIncidentTool
22+
export const pagerdutyMergeIncidentsTool = mergeIncidentsTool
1123
export const pagerdutyAddNoteTool = addNoteTool
24+
export const pagerdutyListIncidentAlertsTool = listIncidentAlertsTool
1225
export const pagerdutyListServicesTool = listServicesTool
26+
export const pagerdutyGetServiceTool = getServiceTool
1327
export const pagerdutyListOncallsTool = listOncallsTool
28+
export const pagerdutyListEscalationPoliciesTool = listEscalationPoliciesTool
29+
export const pagerdutyListSchedulesTool = listSchedulesTool
30+
export const pagerdutyListUsersTool = listUsersTool
31+
export const pagerdutySendEventTool = sendEventTool
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import type {
2+
PagerDutyListEscalationPoliciesParams,
3+
PagerDutyListEscalationPoliciesResponse,
4+
} from '@/tools/pagerduty/types'
5+
import type { ToolConfig } from '@/tools/types'
6+
7+
export const listEscalationPoliciesTool: ToolConfig<
8+
PagerDutyListEscalationPoliciesParams,
9+
PagerDutyListEscalationPoliciesResponse
10+
> = {
11+
id: 'pagerduty_list_escalation_policies',
12+
name: 'PagerDuty List Escalation Policies',
13+
description: 'List escalation policies from PagerDuty with an optional name filter.',
14+
version: '1.0.0',
15+
16+
params: {
17+
apiKey: {
18+
type: 'string',
19+
required: true,
20+
visibility: 'user-only',
21+
description: 'PagerDuty REST API Key',
22+
},
23+
query: {
24+
type: 'string',
25+
required: false,
26+
visibility: 'user-or-llm',
27+
description: 'Filter escalation policies by name',
28+
},
29+
limit: {
30+
type: 'string',
31+
required: false,
32+
visibility: 'user-or-llm',
33+
description: 'Maximum number of results (max 100)',
34+
},
35+
offset: {
36+
type: 'string',
37+
required: false,
38+
visibility: 'user-or-llm',
39+
description: 'Offset to start pagination search results',
40+
},
41+
},
42+
43+
request: {
44+
url: (params) => {
45+
const query = new URLSearchParams()
46+
if (params.query) query.set('query', params.query)
47+
if (params.limit) query.set('limit', params.limit)
48+
if (params.offset) query.set('offset', params.offset)
49+
const qs = query.toString()
50+
return `https://api.pagerduty.com/escalation_policies${qs ? `?${qs}` : ''}`
51+
},
52+
method: 'GET',
53+
headers: (params) => ({
54+
Authorization: `Token token=${params.apiKey}`,
55+
Accept: 'application/vnd.pagerduty+json;version=2',
56+
'Content-Type': 'application/json',
57+
}),
58+
},
59+
60+
transformResponse: async (response: Response) => {
61+
const data = await response.json()
62+
63+
if (!response.ok) {
64+
throw new Error(data.error?.message || `PagerDuty API error: ${response.status}`)
65+
}
66+
67+
return {
68+
success: true,
69+
output: {
70+
escalationPolicies: (data.escalation_policies ?? []).map((ep: Record<string, unknown>) => ({
71+
id: ep.id ?? null,
72+
name: ep.name ?? null,
73+
description: ep.description ?? null,
74+
numLoops: ep.num_loops ?? 0,
75+
onCallHandoffNotifications: ep.on_call_handoff_notifications ?? null,
76+
htmlUrl: ep.html_url ?? null,
77+
})),
78+
total: data.total ?? null,
79+
more: data.more ?? false,
80+
offset: data.offset ?? 0,
81+
},
82+
}
83+
},
84+
85+
outputs: {
86+
escalationPolicies: {
87+
type: 'array',
88+
description: 'Array of escalation policies',
89+
items: {
90+
type: 'object',
91+
properties: {
92+
id: { type: 'string', description: 'Escalation policy ID' },
93+
name: { type: 'string', description: 'Escalation policy name' },
94+
description: { type: 'string', description: 'Escalation policy description' },
95+
numLoops: { type: 'number', description: 'Number of times the policy repeats' },
96+
onCallHandoffNotifications: {
97+
type: 'string',
98+
description: 'Handoff notification setting (if_has_services or always)',
99+
},
100+
htmlUrl: { type: 'string', description: 'PagerDuty web URL' },
101+
},
102+
},
103+
},
104+
total: {
105+
type: 'number',
106+
description:
107+
'Total number of matching escalation policies (null unless explicitly requested by PagerDuty)',
108+
optional: true,
109+
},
110+
more: {
111+
type: 'boolean',
112+
description: 'Whether more results are available',
113+
},
114+
offset: {
115+
type: 'number',
116+
description: 'Offset used for this page of results',
117+
},
118+
},
119+
}

0 commit comments

Comments
 (0)