diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 6508cb8bbb5..3db9c61a75d 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -6928,6 +6928,14 @@ export function HexIcon(props: SVGProps) { ) } +export function RailwayIcon(props: SVGProps) { + return ( + + + + ) +} + export function BigQueryIcon(props: SVGProps) { return ( @@ -6951,6 +6959,21 @@ export function SnowflakeIcon(props: SVGProps) { ) } +export function NewRelicIcon(props: SVGProps) { + return ( + + + + + ) +} + export function WizaIcon(props: SVGProps) { return ( diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 312eb8893ae..81da18e5df6 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -127,6 +127,7 @@ import { MongoDBIcon, MySQLIcon, Neo4jIcon, + NewRelicIcon, NotionIcon, ObsidianIcon, OktaIcon, @@ -148,6 +149,7 @@ import { PulseIcon, QdrantIcon, QuiverIcon, + RailwayIcon, RDSIcon, RedditIcon, RedisIcon, @@ -346,6 +348,7 @@ export const blockTypeToIconMap: Record = { mongodb: MongoDBIcon, mysql: MySQLIcon, neo4j: Neo4jIcon, + new_relic: NewRelicIcon, notion: NotionIcon, notion_v2: NotionIcon, obsidian: ObsidianIcon, @@ -369,6 +372,7 @@ export const blockTypeToIconMap: Record = { pulse_v2: PulseIcon, qdrant: QdrantIcon, quiver: QuiverIcon, + railway: RailwayIcon, rds: RDSIcon, reddit: RedditIcon, redis: RedisIcon, diff --git a/apps/docs/content/docs/en/tools/gong.mdx b/apps/docs/content/docs/en/tools/gong.mdx index 954883120ad..f01b1ae395a 100644 --- a/apps/docs/content/docs/en/tools/gong.mdx +++ b/apps/docs/content/docs/en/tools/gong.mdx @@ -48,7 +48,7 @@ Retrieve call data by date range from Gong. | `accessKey` | string | Yes | Gong API Access Key | | `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `fromDateTime` | string | Yes | Start date/time in ISO-8601 format \(e.g., 2024-01-01T00:00:00Z\) | -| `toDateTime` | string | No | End date/time in ISO-8601 format \(e.g., 2024-01-31T23:59:59Z\). If omitted, lists calls up to the most recent. | +| `toDateTime` | string | No | End date/time in ISO-8601 format \(e.g., 2024-01-31T23:59:59Z\). Defaults to the current execution time when omitted. | | `cursor` | string | No | Pagination cursor from a previous response | | `workspaceId` | string | No | Gong workspace ID to filter calls | @@ -56,6 +56,7 @@ Retrieve call data by date range from Gong. | Parameter | Type | Description | | --------- | ---- | ----------- | +| `requestId` | string | A Gong request reference ID for troubleshooting purposes | | `calls` | array | List of calls matching the date range | | ↳ `id` | string | Gong's unique numeric identifier for the call | | ↳ `title` | string | Call title | @@ -79,6 +80,39 @@ Retrieve call data by date range from Gong. | ↳ `calendarEventId` | string | Calendar event identifier | | `cursor` | string | Pagination cursor for the next page | | `totalRecords` | number | Total number of records matching the filter | +| `currentPageSize` | number | Number of records in the current page | +| `currentPageNumber` | number | Current page number | + +### `gong_create_call` + +Upload call metadata to Gong and let Gong pull the media from a URL. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `accessKey` | string | Yes | Gong API Access Key | +| `accessKeySecret` | string | Yes | Gong API Access Key Secret | +| `clientUniqueId` | string | Yes | Unique call ID from the source telephony or recording system | +| `actualStart` | string | Yes | Actual call start time in ISO-8601 format | +| `primaryUser` | string | Yes | Gong user ID for the call's host or owner | +| `parties` | json | Yes | Array of call parties, with at least the primary user included | +| `direction` | string | Yes | Call direction: Inbound, Outbound, Conference, or Unknown | +| `downloadMediaUrl` | string | Yes | URL where Gong can download the call media file | +| `title` | string | No | Human-readable call title | +| `workspaceId` | string | No | Optional Gong workspace ID | +| `disposition` | string | No | Optional call disposition | +| `purpose` | string | No | Optional call purpose | +| `context` | json | No | Optional CRM context array for the call | +| `callProviderCode` | string | No | Optional conferencing or telephony provider code | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `callId` | string | Gong's unique numeric identifier for the created call | +| `requestId` | string | Gong request reference ID for troubleshooting | +| `url` | string | URL to the created call in the Gong web app | ### `gong_get_call` @@ -275,6 +309,7 @@ List all users in your Gong account. | Parameter | Type | Description | | --------- | ---- | ----------- | +| `requestId` | string | A Gong request reference ID for troubleshooting purposes | | `users` | array | List of Gong users | | ↳ `id` | string | Unique numeric user ID \(up to 20 digits\) | | ↳ `emailAddress` | string | User email address | diff --git a/apps/docs/content/docs/en/tools/incidentio.mdx b/apps/docs/content/docs/en/tools/incidentio.mdx index 3476d3535e7..5d10442a33b 100644 --- a/apps/docs/content/docs/en/tools/incidentio.mdx +++ b/apps/docs/content/docs/en/tools/incidentio.mdx @@ -51,6 +51,8 @@ List incidents from incident.io. Returns a list of incidents with their details | `apiKey` | string | Yes | incident.io API Key | | `page_size` | number | No | Number of incidents to return per page \(e.g., 10, 25, 50\). Default: 25 | | `after` | string | No | Pagination cursor to fetch the next page of results \(e.g., "01FCNDV6P870EA6S7TK1DSYDG0"\) | +| `sort_by` | string | No | Sort order for incidents: created_at_newest_first or created_at_oldest_first | +| `filter_mode` | string | No | How to combine filters: all or any | #### Output @@ -234,6 +236,7 @@ List actions from incident.io. Optionally filter by incident ID. | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | incident.io API Key | | `incident_id` | string | No | Filter actions by incident ID \(e.g., "01FCNDV6P870EA6S7TK1DSYDG0"\) | +| `incident_mode` | string | No | Filter actions by incident mode \(standard, retrospective, test, tutorial, or stream\) | #### Output @@ -308,6 +311,7 @@ List follow-ups from incident.io. Optionally filter by incident ID. | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | incident.io API Key | | `incident_id` | string | No | Filter follow-ups by incident ID \(e.g., "01FCNDV6P870EA6S7TK1DSYDG0"\) | +| `incident_mode` | string | No | Filter follow-ups by incident mode \(standard, retrospective, test, tutorial, or stream\) | #### Output @@ -395,6 +399,8 @@ List all users in your Incident.io workspace. Returns user details including id, | `apiKey` | string | Yes | Incident.io API Key | | `page_size` | number | No | Number of results to return per page \(e.g., 10, 25, 50\). Default: 25 | | `after` | string | No | Pagination cursor to fetch the next page of results | +| `email` | string | No | Filter users by email address | +| `slack_user_id` | string | No | Filter users by Slack user ID | #### Output @@ -440,23 +446,78 @@ List all workflows in your incident.io workspace. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | incident.io API Key | -| `page_size` | number | No | Number of workflows to return per page \(e.g., 10, 25, 50\) | -| `after` | string | No | Pagination cursor to fetch the next page of results \(e.g., "01FCNDV6P870EA6S7TK1DSYDG0"\) | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | | `workflows` | array | List of workflows | -| ↳ `id` | string | Unique identifier for the workflow | -| ↳ `name` | string | Name of the workflow | -| ↳ `state` | string | State of the workflow \(active, draft, or disabled\) | -| ↳ `folder` | string | Folder the workflow belongs to | -| ↳ `created_at` | string | When the workflow was created | -| ↳ `updated_at` | string | When the workflow was last updated | -| `pagination_meta` | object | Pagination metadata | -| ↳ `after` | string | Cursor for next page | -| ↳ `page_size` | number | Number of results per page | +| ↳ `id` | string | Workflow ID | +| ↳ `name` | string | Workflow name | +| ↳ `trigger` | string | Workflow trigger | +| ↳ `once_for` | array | Fields that make the workflow run once | +| ↳ `version` | number | Workflow version | +| ↳ `expressions` | array | Workflow expressions | +| ↳ `condition_groups` | array | Workflow condition groups | +| ↳ `steps` | array | Workflow steps | +| ↳ `include_private_incidents` | boolean | Whether the workflow includes private incidents | +| ↳ `include_private_escalations` | boolean | Whether the workflow includes private escalations | +| ↳ `runs_on_incident_modes` | array | Incident modes the workflow runs on | +| ↳ `continue_on_step_error` | boolean | Whether execution continues after a step error | +| ↳ `runs_on_incidents` | string | Incident lifecycle filter | +| ↳ `state` | string | Workflow state \(active, draft, disabled\) | +| ↳ `delay` | object | Workflow delay configuration | +| ↳ `folder` | string | Workflow folder | +| ↳ `runs_from` | string | When the workflow runs from | +| ↳ `shortform` | string | Workflow shortform identifier | + +### `incidentio_workflows_create` + +Create a new workflow in incident.io. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | incident.io API Key | +| `name` | string | Yes | Name of the workflow \(e.g., "Notify on Critical Incidents"\) | +| `folder` | string | No | Folder to organize the workflow in | +| `state` | string | No | State of the workflow \(active, draft, or disabled\) | +| `trigger` | string | No | Trigger type for the workflow \(e.g., "incident.updated", "incident.created"\) | +| `steps` | string | No | Array of workflow steps as JSON string. Example: \[\{"label": "Notify team", "name": "slack.post_message"\}\] | +| `condition_groups` | string | No | Array of condition groups as JSON string to control when the workflow runs. Example: \[\{"conditions": \[\{"operation": "one_of", "param_bindings": \[\], "subject": "incident.severity"\}\]\}\] | +| `runs_on_incidents` | string | No | When to run the workflow: "newly_created" \(only new incidents\), "newly_created_and_active" \(new and active incidents\), "active" \(only active incidents\), or "all" \(all incidents\) | +| `runs_on_incident_modes` | string | No | Array of incident modes to run on as JSON string. Example: \["standard", "retrospective"\] | +| `include_private_incidents` | boolean | No | Whether to include private incidents | +| `continue_on_step_error` | boolean | No | Whether to continue executing subsequent steps if a step fails | +| `once_for` | string | No | Array of fields to ensure the workflow runs only once per unique combination of these fields, as JSON string. Example: \["incident.id"\] | +| `expressions` | string | No | Array of workflow expressions as JSON string for advanced workflow logic. Example: \[\{"label": "My expression", "operations": \[\]\}\] | +| `delay` | string | No | Delay configuration as JSON string. Example: \{"for_seconds": 60, "conditions_apply_over_delay": false\} | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `workflow` | object | The created workflow | +| ↳ `id` | string | Workflow ID | +| ↳ `name` | string | Workflow name | +| ↳ `trigger` | string | Workflow trigger | +| ↳ `once_for` | array | Fields that make the workflow run once | +| ↳ `version` | number | Workflow version | +| ↳ `expressions` | array | Workflow expressions | +| ↳ `condition_groups` | array | Workflow condition groups | +| ↳ `steps` | array | Workflow steps | +| ↳ `include_private_incidents` | boolean | Whether the workflow includes private incidents | +| ↳ `include_private_escalations` | boolean | Whether the workflow includes private escalations | +| ↳ `runs_on_incident_modes` | array | Incident modes the workflow runs on | +| ↳ `continue_on_step_error` | boolean | Whether execution continues after a step error | +| ↳ `runs_on_incidents` | string | Incident lifecycle filter | +| ↳ `state` | string | Workflow state \(active, draft, disabled\) | +| ↳ `delay` | object | Workflow delay configuration | +| ↳ `folder` | string | Workflow folder | +| ↳ `runs_from` | string | When the workflow runs from | +| ↳ `shortform` | string | Workflow shortform identifier | +| `management_meta` | json | Workflow management metadata | ### `incidentio_workflows_show` @@ -468,18 +529,32 @@ Get details of a specific workflow in incident.io. | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | incident.io API Key | | `id` | string | Yes | The ID of the workflow to retrieve \(e.g., "01FCNDV6P870EA6S7TK1DSYDG0"\) | +| `skip_step_upgrades` | boolean | No | Skip workflow step upgrades when existing workflow step parameters changed | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | | `workflow` | object | The workflow details | -| ↳ `id` | string | Unique identifier for the workflow | -| ↳ `name` | string | Name of the workflow | -| ↳ `state` | string | State of the workflow \(active, draft, or disabled\) | -| ↳ `folder` | string | Folder the workflow belongs to | -| ↳ `created_at` | string | When the workflow was created | -| ↳ `updated_at` | string | When the workflow was last updated | +| ↳ `id` | string | Workflow ID | +| ↳ `name` | string | Workflow name | +| ↳ `trigger` | string | Workflow trigger | +| ↳ `once_for` | array | Fields that make the workflow run once | +| ↳ `version` | number | Workflow version | +| ↳ `expressions` | array | Workflow expressions | +| ↳ `condition_groups` | array | Workflow condition groups | +| ↳ `steps` | array | Workflow steps | +| ↳ `include_private_incidents` | boolean | Whether the workflow includes private incidents | +| ↳ `include_private_escalations` | boolean | Whether the workflow includes private escalations | +| ↳ `runs_on_incident_modes` | array | Incident modes the workflow runs on | +| ↳ `continue_on_step_error` | boolean | Whether execution continues after a step error | +| ↳ `runs_on_incidents` | string | Incident lifecycle filter | +| ↳ `state` | string | Workflow state \(active, draft, disabled\) | +| ↳ `delay` | object | Workflow delay configuration | +| ↳ `folder` | string | Workflow folder | +| ↳ `runs_from` | string | When the workflow runs from | +| ↳ `shortform` | string | Workflow shortform identifier | +| `management_meta` | json | Workflow management metadata | ### `incidentio_workflows_update` @@ -491,21 +566,43 @@ Update an existing workflow in incident.io. | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | incident.io API Key | | `id` | string | Yes | The ID of the workflow to update \(e.g., "01FCNDV6P870EA6S7TK1DSYDG0"\) | -| `name` | string | No | New name for the workflow \(e.g., "Notify on Critical Incidents"\) | +| `name` | string | Yes | New name for the workflow \(e.g., "Notify on Critical Incidents"\) | +| `steps` | string | Yes | Complete array of workflow steps as a JSON string | +| `condition_groups` | string | Yes | Complete array of workflow condition groups as a JSON string | +| `runs_on_incidents` | string | Yes | When to run the workflow: newly_created, newly_created_and_active, active, or all | +| `runs_on_incident_modes` | string | Yes | Complete array of incident modes to run on as a JSON string | +| `include_private_incidents` | boolean | Yes | Whether to include private incidents | +| `continue_on_step_error` | boolean | Yes | Whether to continue executing subsequent steps if a step fails | +| `once_for` | string | Yes | Complete array of fields that make the workflow run once as a JSON string | +| `expressions` | string | Yes | Complete array of workflow expressions as a JSON string | | `state` | string | No | New state for the workflow \(active, draft, or disabled\) | | `folder` | string | No | New folder for the workflow | +| `delay` | string | No | Delay configuration as a JSON string | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | | `workflow` | object | The updated workflow | -| ↳ `id` | string | Unique identifier for the workflow | -| ↳ `name` | string | Name of the workflow | -| ↳ `state` | string | State of the workflow \(active, draft, or disabled\) | -| ↳ `folder` | string | Folder the workflow belongs to | -| ↳ `created_at` | string | When the workflow was created | -| ↳ `updated_at` | string | When the workflow was last updated | +| ↳ `id` | string | Workflow ID | +| ↳ `name` | string | Workflow name | +| ↳ `trigger` | string | Workflow trigger | +| ↳ `once_for` | array | Fields that make the workflow run once | +| ↳ `version` | number | Workflow version | +| ↳ `expressions` | array | Workflow expressions | +| ↳ `condition_groups` | array | Workflow condition groups | +| ↳ `steps` | array | Workflow steps | +| ↳ `include_private_incidents` | boolean | Whether the workflow includes private incidents | +| ↳ `include_private_escalations` | boolean | Whether the workflow includes private escalations | +| ↳ `runs_on_incident_modes` | array | Incident modes the workflow runs on | +| ↳ `continue_on_step_error` | boolean | Whether execution continues after a step error | +| ↳ `runs_on_incidents` | string | Incident lifecycle filter | +| ↳ `state` | string | Workflow state \(active, draft, disabled\) | +| ↳ `delay` | object | Workflow delay configuration | +| ↳ `folder` | string | Workflow folder | +| ↳ `runs_from` | string | When the workflow runs from | +| ↳ `shortform` | string | Workflow shortform identifier | +| `management_meta` | json | Workflow management metadata | ### `incidentio_workflows_delete` @@ -647,6 +744,8 @@ List all escalation policies in incident.io | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | incident.io API Key | +| `page_size` | number | No | Number of escalations to return per page | +| `after` | string | No | Pagination cursor to fetch the next page of results | #### Output @@ -657,6 +756,9 @@ List all escalation policies in incident.io | ↳ `name` | string | The escalation policy name | | ↳ `created_at` | string | When the escalation policy was created | | ↳ `updated_at` | string | When the escalation policy was last updated | +| `pagination_meta` | object | Pagination metadata | +| ↳ `after` | string | Cursor for next page | +| ↳ `page_size` | number | Number of results per page | ### `incidentio_escalations_create` @@ -1096,29 +1198,18 @@ List all entries for a specific schedule in incident.io | `schedule_id` | string | Yes | The ID of the schedule to get entries for \(e.g., "01FCNDV6P870EA6S7TK1DSYDG0"\) | | `entry_window_start` | string | No | Start date/time to filter entries in ISO 8601 format \(e.g., "2024-01-15T09:00:00Z"\) | | `entry_window_end` | string | No | End date/time to filter entries in ISO 8601 format \(e.g., "2024-01-22T09:00:00Z"\) | -| `page_size` | number | No | Number of results to return per page \(e.g., 10, 25, 50\) | -| `after` | string | No | Cursor for pagination \(e.g., "01FCNDV6P870EA6S7TK1DSYDG0"\) | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `schedule_entries` | array | List of schedule entries | -| ↳ `id` | string | The entry ID | -| ↳ `schedule_id` | string | The schedule ID | -| ↳ `user` | object | User assigned to this entry | -| ↳ `id` | string | User ID | -| ↳ `name` | string | User name | -| ↳ `email` | string | User email | -| ↳ `start_at` | string | When the entry starts | -| ↳ `end_at` | string | When the entry ends | -| ↳ `layer_id` | string | The schedule layer ID | -| ↳ `created_at` | string | When the entry was created | -| ↳ `updated_at` | string | When the entry was last updated | +| `schedule_entries` | object | Schedule entries grouped by final, overrides, and scheduled entries | +| ↳ `final` | array | Final computed schedule entries | +| ↳ `overrides` | array | Override schedule entries | +| ↳ `scheduled` | array | Scheduled entries before overrides are applied | | `pagination_meta` | object | Pagination information | | ↳ `after` | string | Cursor for next page | | ↳ `after_url` | string | URL for next page | -| ↳ `page_size` | number | Number of results per page | ### `incidentio_schedule_overrides_create` @@ -1130,6 +1221,7 @@ Create a new schedule override in incident.io | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | incident.io API Key | | `rotation_id` | string | Yes | The ID of the rotation to override \(e.g., "01FCNDV6P870EA6S7TK1DSYDG0"\) | +| `layer_id` | string | Yes | The ID of the layer this override applies to | | `schedule_id` | string | Yes | The ID of the schedule \(e.g., "01FCNDV6P870EA6S7TK1DSYDG0"\) | | `user_id` | string | No | The ID of the user to assign \(provide one of: user_id, user_email, or user_slack_id\) | | `user_email` | string | No | The email of the user to assign \(provide one of: user_id, user_email, or user_slack_id\) | @@ -1143,6 +1235,7 @@ Create a new schedule override in incident.io | --------- | ---- | ----------- | | `override` | object | The created schedule override | | ↳ `id` | string | The override ID | +| ↳ `layer_id` | string | The schedule layer ID | | ↳ `rotation_id` | string | The rotation ID | | ↳ `schedule_id` | string | The schedule ID | | ↳ `user` | object | User assigned to this override | @@ -1154,6 +1247,31 @@ Create a new schedule override in incident.io | ↳ `created_at` | string | When the override was created | | ↳ `updated_at` | string | When the override was last updated | +### `incidentio_escalation_paths_list` + +List escalation paths in incident.io + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | incident.io API Key | +| `page_size` | number | No | Number of escalation paths to return per page | +| `after` | string | No | Pagination cursor to fetch the next page of results | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `escalation_paths` | array | List of escalation paths | +| ↳ `id` | string | The escalation path ID | +| ↳ `name` | string | The escalation path name | +| ↳ `path` | array | Array of escalation levels | +| ↳ `working_hours` | array | Working hours configuration | +| `pagination_meta` | object | Pagination metadata | +| ↳ `after` | string | Cursor for next page | +| ↳ `page_size` | number | Number of results per page | + ### `incidentio_escalation_paths_create` Create a new escalation path in incident.io @@ -1186,8 +1304,6 @@ Create a new escalation path in incident.io | ↳ `weekday` | string | Day of week | | ↳ `start_time` | string | Start time | | ↳ `end_time` | string | End time | -| ↳ `created_at` | string | When the path was created | -| ↳ `updated_at` | string | When the path was last updated | ### `incidentio_escalation_paths_show` @@ -1219,8 +1335,6 @@ Get details of a specific escalation path in incident.io | ↳ `weekday` | string | Day of week | | ↳ `start_time` | string | Start time | | ↳ `end_time` | string | End time | -| ↳ `created_at` | string | When the path was created | -| ↳ `updated_at` | string | When the path was last updated | ### `incidentio_escalation_paths_update` @@ -1232,8 +1346,8 @@ Update an existing escalation path in incident.io | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | incident.io API Key | | `id` | string | Yes | The ID of the escalation path to update \(e.g., "01FCNDV6P870EA6S7TK1DSYDG0"\) | -| `name` | string | No | New name for the escalation path \(e.g., "Critical Incident Path"\) | -| `path` | json | No | New escalation path configuration. Array of escalation levels with targets and time_to_ack_seconds | +| `name` | string | Yes | New name for the escalation path \(e.g., "Critical Incident Path"\) | +| `path` | json | Yes | New escalation path configuration. Array of escalation levels with targets and time_to_ack_seconds | | `working_hours` | json | No | New working hours configuration. Array of \{weekday, start_time, end_time\} | #### Output @@ -1255,8 +1369,6 @@ Update an existing escalation path in incident.io | ↳ `weekday` | string | Day of week | | ↳ `start_time` | string | Start time | | ↳ `end_time` | string | End time | -| ↳ `created_at` | string | When the path was created | -| ↳ `updated_at` | string | When the path was last updated | ### `incidentio_escalation_paths_delete` diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 5448fe6407b..f223bf0ed70 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -123,6 +123,7 @@ "mongodb", "mysql", "neo4j", + "new_relic", "notion", "obsidian", "okta", @@ -144,6 +145,7 @@ "pulse", "qdrant", "quiver", + "railway", "rds", "reddit", "redis", diff --git a/apps/docs/content/docs/en/tools/new_relic.mdx b/apps/docs/content/docs/en/tools/new_relic.mdx new file mode 100644 index 00000000000..7beb793335a --- /dev/null +++ b/apps/docs/content/docs/en/tools/new_relic.mdx @@ -0,0 +1,143 @@ +--- +title: New Relic +description: Query observability data and record deployments in New Relic +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[New Relic](https://newrelic.com/) is an observability platform for monitoring application performance, infrastructure, logs, traces, and business-impacting changes across your systems. It centralizes telemetry in NRDB and exposes that data through NerdGraph, New Relic's GraphQL API. + +With New Relic, you can: + +- **Query telemetry with NRQL**: Run NRQL against account data to inspect service health, usage, errors, latency, and custom events. +- **Find monitored entities**: Search services, applications, hosts, and other monitored resources by name, type, tags, alert state, or reporting status. +- **Fetch entity details**: Resolve an entity GUID into basic entity metadata for downstream workflow steps. +- **Record deployment changes**: Create deployment change tracking events with version, changelog, commit, build links, group IDs, and user context. + +Sim's New Relic integration lets agents pull production observability signals into workflows and annotate releases or operational changes directly from automation. Use it to summarize live service health, route incident workflows from entity searches, or mark deployments so New Relic charts can correlate performance changes with release activity. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate New Relic into workflows. Run NRQL queries, search monitored entities, fetch entity details, and record deployment change events. + + + +## Tools + +### `new_relic_nrql_query` + +Run a NRQL query against a New Relic account using NerdGraph. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | New Relic user API key for NerdGraph | +| `region` | string | No | New Relic data center region: us or eu | +| `accountId` | number | Yes | New Relic account ID to query | +| `nrql` | string | Yes | NRQL query to execute | +| `timeout` | number | No | Optional query timeout in seconds | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `results` | array | NRQL result rows. Row fields depend on the query projection. | +| `resultCount` | number | Number of NRQL result rows returned | + +### `new_relic_search_entities` + +Search New Relic entities by name, GUID, domain type, tags, or reporting state. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | New Relic user API key for NerdGraph | +| `region` | string | No | New Relic data center region: us or eu | +| `query` | string | Yes | Entity search query, for example: name like "api" or domainType = "APM-APPLICATION" | +| `cursor` | string | No | Pagination cursor from a previous entity search | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `count` | number | Total number of entities matching the query | +| `query` | string | Entity search query New Relic executed | +| `entities` | array | Matching New Relic entities | +| ↳ `guid` | string | Entity GUID | +| ↳ `name` | string | Entity name | +| ↳ `entityType` | string | Entity type | +| `nextCursor` | string | Cursor for the next page of results | + +### `new_relic_get_entity` + +Fetch a New Relic entity by GUID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | New Relic user API key for NerdGraph | +| `region` | string | No | New Relic data center region: us or eu | +| `guid` | string | Yes | Entity GUID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `entity` | object | New Relic entity details | +| ↳ `guid` | string | Entity GUID | +| ↳ `name` | string | Entity name | +| ↳ `entityType` | string | Entity type | + +### `new_relic_create_deployment_event` + +Record a deployment change event in New Relic change tracking. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | New Relic user API key for NerdGraph | +| `region` | string | No | New Relic data center region: us or eu | +| `entityGuid` | string | Yes | GUID of the entity associated with the deployment | +| `version` | string | Yes | Deployment version, release name, or commit SHA | +| `shortDescription` | string | No | Short description of the deployment | +| `description` | string | No | Longer deployment description | +| `changelog` | string | No | Deployment changelog text or URL | +| `commit` | string | No | Commit SHA or identifier associated with the deployment | +| `deepLink` | string | No | URL to the deployment, build, or release details | +| `user` | string | No | User or automation that performed the deployment | +| `groupId` | string | No | Optional group ID to correlate related changes | +| `customAttributes` | json | No | Custom change event metadata as key-value pairs with string, number, or boolean values | +| `deploymentType` | string | No | Deployment type: basic, blue green, canary, rolling, or shadow | +| `timestamp` | number | No | Event timestamp in milliseconds since Unix epoch | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `event` | object | Created New Relic change tracking event | +| ↳ `changeTrackingId` | string | New Relic change tracking ID | +| ↳ `customAttributes` | json | Custom attributes on the change tracking event | +| ↳ `category` | string | Change category | +| ↳ `categoryAndType` | string | Combined category and type | +| ↳ `type` | string | Change type | +| ↳ `shortDescription` | string | Short change description | +| ↳ `description` | string | Change description | +| ↳ `timestamp` | number | Change timestamp in milliseconds | +| ↳ `user` | string | User associated with the change | +| ↳ `groupId` | string | Change group ID | +| ↳ `entity` | object | Entity associated with the change | +| ↳ `guid` | string | Entity GUID | +| ↳ `name` | string | Entity name | +| `messages` | array | Messages returned by New Relic for the created change event | diff --git a/apps/docs/content/docs/en/tools/railway.mdx b/apps/docs/content/docs/en/tools/railway.mdx new file mode 100644 index 00000000000..ab26d387838 --- /dev/null +++ b/apps/docs/content/docs/en/tools/railway.mdx @@ -0,0 +1,343 @@ +--- +title: Railway +description: Manage Railway projects, deployments, and variables +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Railway](https://railway.com/) is a cloud application platform for deploying, operating, and scaling services, databases, jobs, and production environments from a single project workspace. Teams use Railway to connect source repositories, manage environments, configure variables, trigger deployments, and monitor delivery across staging and production. + +With Railway, you can: + +- **Manage projects and environments**: Organize deployed services and inspect the environments attached to each project +- **Automate deployments**: Trigger new service deployments and inspect recent deployment status from workflows +- **Control runtime configuration**: Read and update environment variables for services or shared project environments +- **Connect infrastructure workflows**: Use project, service, and environment IDs from one step to drive release automation in later steps + +In Sim, the Railway integration lets your agents work with Railway's public GraphQL API directly from workflows. You can list projects, fetch project services and environments, inspect deployments, deploy a service, and manage environment variables as part of CI/CD, operations, and release processes. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate Railway into workflows to list projects, inspect services and environments, monitor deployments, trigger service deployments, and manage environment variables. + + + +## Tools + +### `railway_list_projects` + +List Railway projects visible to the provided token + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Railway API token | +| `tokenType` | string | No | Railway token type: account, workspace, project, or oauth | +| `workspaceId` | string | No | Workspace ID to list projects from | +| `first` | number | No | Maximum number of projects to return | +| `after` | string | No | Cursor for pagination | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `projects` | array | Railway projects | +| ↳ `id` | string | Project ID | +| ↳ `name` | string | Project name | +| ↳ `description` | string | Project description | +| ↳ `createdAt` | string | Project creation timestamp | +| ↳ `updatedAt` | string | Project update timestamp | +| `pageInfo` | object | Pagination information | +| ↳ `hasNextPage` | boolean | Whether more projects are available | +| ↳ `endCursor` | string | Cursor for the next page | +| `count` | number | Number of projects returned | + +### `railway_get_project` + +Get a Railway project with its services and environments + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Railway API token | +| `tokenType` | string | No | Railway token type: account, workspace, project, or oauth | +| `projectId` | string | Yes | Railway project ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `project` | object | Project with services and environments | +| ↳ `id` | string | Project ID | +| ↳ `name` | string | Project name | +| ↳ `description` | string | Project description | +| ↳ `createdAt` | string | Project creation timestamp | +| ↳ `services` | array | Project services | +| ↳ `id` | string | Service ID | +| ↳ `name` | string | Service name | +| ↳ `icon` | string | Service icon | +| ↳ `environments` | array | Project environments | +| ↳ `id` | string | Environment ID | +| ↳ `name` | string | Environment name | + +### `railway_create_project` + +Create a Railway project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Railway API token | +| `tokenType` | string | No | Railway token type: account, workspace, project, or oauth | +| `name` | string | Yes | Project name | +| `description` | string | No | Project description | +| `workspaceId` | string | No | Workspace ID to create the project in | +| `isPublic` | boolean | No | Whether the project should be publicly visible | +| `defaultEnvironmentName` | string | No | Name for the default environment | +| `prDeploys` | boolean | No | Whether to enable pull request deploys | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `project` | object | Created project | +| ↳ `id` | string | Project ID | +| ↳ `name` | string | Project name | + +### `railway_update_project` + +Update a Railway project name or description + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Railway API token | +| `tokenType` | string | No | Railway token type: account, workspace, project, or oauth | +| `projectId` | string | Yes | Railway project ID | +| `name` | string | No | Updated project name | +| `description` | string | No | Updated project description | +| `isPublic` | boolean | No | Whether the project should be publicly visible | +| `prDeploys` | boolean | No | Whether to enable pull request deploy environments | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `project` | object | Updated project | +| ↳ `id` | string | Project ID | +| ↳ `name` | string | Project name | +| ↳ `description` | string | Project description | + +### `railway_delete_project` + +Delete a Railway project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Railway API token | +| `tokenType` | string | No | Railway token type: account, workspace, project, or oauth | +| `projectId` | string | Yes | Railway project ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the project was deleted | + +### `railway_transfer_project` + +Transfer a Railway project to another workspace + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Railway API token | +| `tokenType` | string | No | Railway token type: account, workspace, project, or oauth | +| `projectId` | string | Yes | Railway project ID | +| `workspaceId` | string | Yes | Destination workspace ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the project was transferred | + +### `railway_list_project_members` + +List members for a Railway project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Railway API token | +| `tokenType` | string | No | Railway token type: account, workspace, project, or oauth | +| `projectId` | string | Yes | Railway project ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `members` | array | Project members | +| ↳ `id` | string | Project membership ID | +| ↳ `role` | string | Project role | +| ↳ `user` | object | Railway user | +| ↳ `id` | string | User ID | +| ↳ `name` | string | User name | +| ↳ `email` | string | User email | +| `count` | number | Number of members returned | + +### `railway_create_environment` + +Create a Railway project environment + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Railway API token | +| `tokenType` | string | No | Railway token type: account, workspace, project, or oauth | +| `projectId` | string | Yes | Railway project ID | +| `name` | string | Yes | Environment name | +| `sourceEnvironmentId` | string | No | Environment ID to clone from | +| `ephemeral` | boolean | No | Whether the environment is ephemeral | +| `skipInitialDeploys` | boolean | No | Whether to skip initial deploys for the environment | +| `stageInitialChanges` | boolean | No | Whether to stage initial changes instead of applying them immediately | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `environment` | object | Created environment | +| ↳ `id` | string | Environment ID | +| ↳ `name` | string | Environment name | + +### `railway_delete_environment` + +Delete a Railway project environment + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Railway API token | +| `tokenType` | string | No | Railway token type: account, workspace, project, or oauth | +| `environmentId` | string | Yes | Railway environment ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the environment was deleted | + +### `railway_list_deployments` + +List deployments for a Railway service in an environment + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Railway API token | +| `tokenType` | string | No | Railway token type: account, workspace, project, or oauth | +| `projectId` | string | Yes | Railway project ID | +| `serviceId` | string | Yes | Railway service ID | +| `environmentId` | string | Yes | Railway environment ID | +| `first` | number | No | Maximum number of deployments to return | +| `after` | string | No | Cursor for pagination | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deployments` | array | Service deployments | +| ↳ `id` | string | Deployment ID | +| ↳ `status` | string | Deployment status | +| ↳ `createdAt` | string | Deployment creation timestamp | +| ↳ `url` | string | Deployment URL | +| ↳ `staticUrl` | string | Static deployment URL | +| `count` | number | Number of deployments returned | +| `pageInfo` | object | Pagination information | +| ↳ `hasNextPage` | boolean | Whether more deployments are available | +| ↳ `endCursor` | string | Cursor for the next page | + +### `railway_deploy_service` + +Trigger a deployment for a Railway service in an environment + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Railway API token | +| `tokenType` | string | No | Railway token type: account, workspace, project, or oauth | +| `serviceId` | string | Yes | Railway service ID | +| `environmentId` | string | Yes | Railway environment ID | +| `commitSha` | string | No | Specific Git commit SHA to deploy | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deploymentId` | string | Created deployment ID | + +### `railway_list_variables` + +List Railway environment variables for a service or shared environment + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Railway API token | +| `tokenType` | string | No | Railway token type: account, workspace, project, or oauth | +| `projectId` | string | Yes | Railway project ID | +| `environmentId` | string | Yes | Railway environment ID | +| `serviceId` | string | No | Railway service ID. Omit for shared environment variables. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `variables` | object | Variable names and values | +| `count` | number | Number of variables returned | + +### `railway_upsert_variable` + +Create or update a Railway environment variable + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Railway API token | +| `tokenType` | string | No | Railway token type: account, workspace, project, or oauth | +| `projectId` | string | Yes | Railway project ID | +| `environmentId` | string | Yes | Railway environment ID | +| `serviceId` | string | No | Railway service ID. Omit to create or update a shared variable. | +| `name` | string | Yes | Variable name | +| `value` | string | Yes | Variable value | +| `skipDeploys` | boolean | No | Whether to skip automatic redeploys after changing the variable | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the variable was created or updated | + + diff --git a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts index ab6f6b1831c..eaf8da5b50b 100644 --- a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts +++ b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts @@ -127,6 +127,7 @@ import { MongoDBIcon, MySQLIcon, Neo4jIcon, + NewRelicIcon, NotionIcon, ObsidianIcon, OktaIcon, @@ -148,6 +149,7 @@ import { PulseIcon, QdrantIcon, QuiverIcon, + RailwayIcon, RDSIcon, RedditIcon, RedisIcon, @@ -331,6 +333,7 @@ export const blockTypeToIconMap: Record = { mongodb: MongoDBIcon, mysql: MySQLIcon, neo4j: Neo4jIcon, + new_relic: NewRelicIcon, notion_v2: NotionIcon, obsidian: ObsidianIcon, okta: OktaIcon, @@ -352,6 +355,7 @@ export const blockTypeToIconMap: Record = { pulse_v2: PulseIcon, qdrant: QdrantIcon, quiver: QuiverIcon, + railway: RailwayIcon, rds: RDSIcon, reddit: RedditIcon, redis: RedisIcon, diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index b3d8b3bc4fc..1fa64647043 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -4881,6 +4881,10 @@ "name": "List Calls", "description": "Retrieve call data by date range from Gong." }, + { + "name": "Create Call", + "description": "Upload call metadata to Gong and let Gong pull the media from a URL." + }, { "name": "Get Call", "description": "Retrieve detailed data for a specific call from Gong." @@ -4950,7 +4954,7 @@ "description": "Find all references to a phone number in Gong (calls, email messages, meetings, CRM data, and associated contacts)." } ], - "operationCount": 18, + "operationCount": 19, "triggers": [ { "id": "gong_webhook", @@ -6737,6 +6741,10 @@ "name": "List Workflows", "description": "List all workflows in your incident.io workspace." }, + { + "name": "Create Workflow", + "description": "Create a new workflow in incident.io." + }, { "name": "Show Workflow", "description": "Get details of a specific workflow in incident.io." @@ -6853,6 +6861,10 @@ "name": "Create Schedule Override", "description": "Create a new schedule override in incident.io" }, + { + "name": "List Escalation Paths", + "description": "List escalation paths in incident.io" + }, { "name": "Create Escalation Path", "description": "Create a new escalation path in incident.io" @@ -6870,7 +6882,7 @@ "description": "Delete an escalation path in incident.io" } ], - "operationCount": 44, + "operationCount": 46, "triggers": [], "triggerCount": 0, "authType": "api-key", @@ -9378,6 +9390,41 @@ "integrationTypes": ["databases", "analytics"], "tags": ["data-warehouse", "data-analytics"] }, + { + "type": "new_relic", + "slug": "new-relic", + "name": "New Relic", + "description": "Query observability data and record deployments in New Relic", + "longDescription": "Integrate New Relic into workflows. Run NRQL queries, search monitored entities, fetch entity details, and record deployment change events.", + "bgColor": "#000000", + "iconName": "NewRelicIcon", + "docsUrl": "https://docs.sim.ai/tools/new_relic", + "operations": [ + { + "name": "Run NRQL Query", + "description": "Run a NRQL query against a New Relic account using NerdGraph." + }, + { + "name": "Search Entities", + "description": "Search New Relic entities by name, GUID, domain type, tags, or reporting state." + }, + { + "name": "Get Entity", + "description": "Fetch a New Relic entity by GUID." + }, + { + "name": "Create Deployment Event", + "description": "Record a deployment change event in New Relic change tracking." + } + ], + "operationCount": 4, + "triggers": [], + "triggerCount": 0, + "authType": "api-key", + "category": "tools", + "integrationTypes": ["analytics", "developer-tools"], + "tags": ["monitoring", "data-analytics", "incident-management"] + }, { "type": "notion_v2", "slug": "notion", @@ -10589,6 +10636,77 @@ "integrationTypes": ["design", "ai"], "tags": ["image-generation"] }, + { + "type": "railway", + "slug": "railway", + "name": "Railway", + "description": "Manage Railway projects, deployments, and variables", + "longDescription": "Integrate Railway into workflows to list projects, inspect services and environments, monitor deployments, trigger service deployments, and manage environment variables.", + "bgColor": "#FFFFFF", + "iconName": "RailwayIcon", + "docsUrl": "https://docs.sim.ai/tools/railway", + "operations": [ + { + "name": "List Projects", + "description": "List Railway projects visible to the provided token" + }, + { + "name": "Get Project", + "description": "Get a Railway project with its services and environments" + }, + { + "name": "Create Project", + "description": "Create a Railway project" + }, + { + "name": "Update Project", + "description": "Update a Railway project name or description" + }, + { + "name": "Delete Project", + "description": "Delete a Railway project" + }, + { + "name": "Transfer Project", + "description": "Transfer a Railway project to another workspace" + }, + { + "name": "List Project Members", + "description": "List members for a Railway project" + }, + { + "name": "Create Environment", + "description": "Create a Railway project environment" + }, + { + "name": "Delete Environment", + "description": "Delete a Railway project environment" + }, + { + "name": "List Deployments", + "description": "List deployments for a Railway service in an environment" + }, + { + "name": "Deploy Service", + "description": "Trigger a deployment for a Railway service in an environment" + }, + { + "name": "List Variables", + "description": "List Railway environment variables for a service or shared environment" + }, + { + "name": "Upsert Variable", + "description": "Create or update a Railway environment variable" + } + ], + "operationCount": 13, + "triggers": [], + "triggerCount": 0, + "authType": "api-key", + "category": "tools", + "integrationTypes": ["developer-tools"], + "tags": ["cloud", "ci-cd"] + }, { "type": "reddit", "slug": "reddit", diff --git a/apps/sim/blocks/blocks/gong.ts b/apps/sim/blocks/blocks/gong.ts index ec02672ab6c..a7756b1be00 100644 --- a/apps/sim/blocks/blocks/gong.ts +++ b/apps/sim/blocks/blocks/gong.ts @@ -26,6 +26,7 @@ export const GongBlock: BlockConfig = { type: 'dropdown', options: [ { label: 'List Calls', id: 'list_calls' }, + { label: 'Create Call', id: 'create_call' }, { label: 'Get Call', id: 'get_call' }, { label: 'Get Call Transcript', id: 'get_call_transcript' }, { label: 'Get Extensive Calls', id: 'get_extensive_calls' }, @@ -47,6 +48,127 @@ export const GongBlock: BlockConfig = { value: () => 'list_calls', }, + // Create Call inputs + { + id: 'clientUniqueId', + title: 'Client Unique ID', + type: 'short-input', + placeholder: 'Unique call ID from your source system', + condition: { field: 'operation', value: 'create_call' }, + required: { field: 'operation', value: 'create_call' }, + }, + { + id: 'actualStart', + title: 'Actual Start', + type: 'short-input', + placeholder: '2018-02-17T02:30:00-08:00', + condition: { field: 'operation', value: 'create_call' }, + required: { field: 'operation', value: 'create_call' }, + wandConfig: { + enabled: true, + prompt: `Generate an ISO 8601 timestamp based on the user's description. +The timestamp should be in the format: YYYY-MM-DDTHH:MM:SSZ (UTC timezone) or include a timezone offset. + +Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes, no extra text.`, + placeholder: 'Describe the call start time...', + generationType: 'timestamp', + }, + }, + { + id: 'primaryUser', + title: 'Primary User ID', + type: 'short-input', + placeholder: 'Gong user ID for the call host', + condition: { field: 'operation', value: 'create_call' }, + required: { field: 'operation', value: 'create_call' }, + }, + { + id: 'parties', + title: 'Parties', + type: 'long-input', + placeholder: '[{"userId":"65192578128262669"}]', + condition: { field: 'operation', value: 'create_call' }, + required: { field: 'operation', value: 'create_call' }, + wandConfig: { + enabled: true, + prompt: `Generate a JSON array of Gong call parties. +Include the primary Gong user as an object with userId when provided. Parties can include name, phoneNumber, emailAddress, userId, mediaChannelId, and context. + +Return ONLY the JSON array - no explanations, no quotes, no extra text.`, + placeholder: 'Describe the call participants...', + }, + }, + { + id: 'direction', + title: 'Direction', + type: 'dropdown', + options: [ + { label: 'Inbound', id: 'Inbound' }, + { label: 'Outbound', id: 'Outbound' }, + { label: 'Conference', id: 'Conference' }, + { label: 'Unknown', id: 'Unknown' }, + ], + value: () => 'Inbound', + condition: { field: 'operation', value: 'create_call' }, + required: { field: 'operation', value: 'create_call' }, + }, + { + id: 'downloadMediaUrl', + title: 'Download Media URL', + type: 'short-input', + placeholder: 'https://example.com/call-recording.mp3', + condition: { field: 'operation', value: 'create_call' }, + required: { field: 'operation', value: 'create_call' }, + }, + { + id: 'title', + title: 'Title', + type: 'short-input', + placeholder: 'Discovery call with ACME', + condition: { field: 'operation', value: 'create_call' }, + }, + { + id: 'disposition', + title: 'Disposition', + type: 'short-input', + placeholder: 'Connected', + condition: { field: 'operation', value: 'create_call' }, + mode: 'advanced', + }, + { + id: 'purpose', + title: 'Purpose', + type: 'short-input', + placeholder: 'Demo Call', + condition: { field: 'operation', value: 'create_call' }, + mode: 'advanced', + }, + { + id: 'context', + title: 'Context', + type: 'long-input', + placeholder: + '[{"system":"Salesforce","objects":[{"objectType":"Opportunity","objectId":"006..."}]}]', + condition: { field: 'operation', value: 'create_call' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate a JSON array of Gong CRM context objects for the call. +Use objects with system and objects fields when external CRM records are provided. + +Return ONLY the JSON array - no explanations, no quotes, no extra text.`, + placeholder: 'Describe CRM records to associate...', + }, + }, + { + id: 'callProviderCode', + title: 'Call Provider Code', + type: 'short-input', + placeholder: 'zoom', + condition: { field: 'operation', value: 'create_call' }, + mode: 'advanced', + }, + // List Calls inputs { id: 'fromDateTime', @@ -82,7 +204,6 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes field: 'operation', value: ['list_calls'], }, - mode: 'advanced', wandConfig: { enabled: true, prompt: `Generate an ISO 8601 timestamp based on the user's description. @@ -349,6 +470,7 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n field: 'operation', value: [ 'list_calls', + 'create_call', 'get_call_transcript', 'get_extensive_calls', 'list_library_folders', @@ -492,6 +614,7 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes tools: { access: [ 'gong_list_calls', + 'gong_create_call', 'gong_get_call', 'gong_get_call_transcript', 'gong_get_extensive_calls', @@ -534,6 +657,17 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes operation: { type: 'string', description: 'Operation to perform' }, accessKey: { type: 'string', description: 'Gong API Access Key' }, accessKeySecret: { type: 'string', description: 'Gong API Access Key Secret' }, + clientUniqueId: { type: 'string', description: 'Unique source-system call ID' }, + actualStart: { type: 'string', description: 'Actual call start date/time' }, + primaryUser: { type: 'string', description: 'Gong user ID for the call host' }, + parties: { type: 'json', description: 'Call party array' }, + direction: { type: 'string', description: 'Call direction' }, + downloadMediaUrl: { type: 'string', description: 'URL where Gong can download call media' }, + title: { type: 'string', description: 'Call title' }, + disposition: { type: 'string', description: 'Call disposition' }, + purpose: { type: 'string', description: 'Call purpose' }, + context: { type: 'json', description: 'Call CRM context array' }, + callProviderCode: { type: 'string', description: 'Provider conferencing or telephony code' }, fromDateTime: { type: 'string', description: 'Start date/time in ISO-8601 format (list calls)', @@ -569,7 +703,8 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes outputs: { response: { type: 'json', - description: 'Gong API response data', + description: + 'Gong API response data. Shape depends on the selected operation and can include callId, requestId, url, calls, callTranscripts, users, usersActivity, peopleInteractionStats, answeredScorecards, folders, scorecards, trackers, workspaces, flows, coachingData, emails, meetings, customerData, and pagination cursor fields.', }, }, triggers: { diff --git a/apps/sim/blocks/blocks/incidentio.ts b/apps/sim/blocks/blocks/incidentio.ts index ad8c19949d6..f7bcca49dac 100644 --- a/apps/sim/blocks/blocks/incidentio.ts +++ b/apps/sim/blocks/blocks/incidentio.ts @@ -38,6 +38,7 @@ export const IncidentioBlock: BlockConfig = { { label: 'Show User', id: 'incidentio_users_show' }, // Workflows { label: 'List Workflows', id: 'incidentio_workflows_list' }, + { label: 'Create Workflow', id: 'incidentio_workflows_create' }, { label: 'Show Workflow', id: 'incidentio_workflows_show' }, { label: 'Update Workflow', id: 'incidentio_workflows_update' }, { label: 'Delete Workflow', id: 'incidentio_workflows_delete' }, @@ -77,6 +78,7 @@ export const IncidentioBlock: BlockConfig = { // Schedule Overrides { label: 'Create Schedule Override', id: 'incidentio_schedule_overrides_create' }, // Escalation Paths + { label: 'List Escalation Paths', id: 'incidentio_escalation_paths_list' }, { label: 'Create Escalation Path', id: 'incidentio_escalation_paths_create' }, { label: 'Show Escalation Path', id: 'incidentio_escalation_paths_show' }, { label: 'Update Escalation Path', id: 'incidentio_escalation_paths_update' }, @@ -95,12 +97,13 @@ export const IncidentioBlock: BlockConfig = { value: [ 'incidentio_incidents_list', 'incidentio_users_list', - 'incidentio_workflows_list', 'incidentio_schedules_list', + 'incidentio_escalations_list', 'incidentio_incident_updates_list', - 'incidentio_schedule_entries_list', + 'incidentio_escalation_paths_list', ], }, + mode: 'advanced', }, // Pagination 'after' field for list operations { @@ -113,32 +116,39 @@ export const IncidentioBlock: BlockConfig = { value: [ 'incidentio_incidents_list', 'incidentio_users_list', - 'incidentio_workflows_list', 'incidentio_schedules_list', + 'incidentio_escalations_list', 'incidentio_incident_updates_list', - 'incidentio_schedule_entries_list', + 'incidentio_escalation_paths_list', ], }, + mode: 'advanced', }, - // Incidents Create operation inputs { - id: 'name', - title: 'Incident Name', - type: 'short-input', - placeholder: 'Enter incident name...', - condition: { field: 'operation', value: 'incidentio_incidents_create' }, - wandConfig: { - enabled: true, - prompt: `Generate a concise, descriptive incident name based on the user's description. -The incident name should: -- Be clear and descriptive -- Indicate the nature of the issue -- Be suitable for incident tracking and communication - -Return ONLY the incident name - no explanations.`, - placeholder: 'Describe the incident (e.g., "database outage", "API latency issues")...', - }, + id: 'sort_by', + title: 'Sort By', + type: 'dropdown', + options: [ + { label: 'Created At: Newest First', id: 'created_at_newest_first' }, + { label: 'Created At: Oldest First', id: 'created_at_oldest_first' }, + ], + value: () => 'created_at_newest_first', + condition: { field: 'operation', value: 'incidentio_incidents_list' }, + mode: 'advanced', }, + { + id: 'filter_mode', + title: 'Filter Mode', + type: 'dropdown', + options: [ + { label: 'All', id: 'all' }, + { label: 'Any', id: 'any' }, + ], + value: () => 'all', + condition: { field: 'operation', value: 'incidentio_incidents_list' }, + mode: 'advanced', + }, + // Incidents Create operation inputs { id: 'summary', title: 'Summary', @@ -165,15 +175,11 @@ Return ONLY the summary text - no explanations.`, title: 'Severity ID', type: 'short-input', placeholder: 'Enter severity ID...', - condition: { field: 'operation', value: 'incidentio_incidents_create' }, - required: true, - }, - { - id: 'severity_id', - title: 'Severity ID', - type: 'short-input', - placeholder: 'Enter severity ID...', - condition: { field: 'operation', value: 'incidentio_incidents_update' }, + condition: { + field: 'operation', + value: ['incidentio_incidents_create', 'incidentio_incidents_update'], + }, + required: { field: 'operation', value: 'incidentio_incidents_create' }, }, { id: 'incident_type_id', @@ -215,14 +221,6 @@ Return ONLY the summary text - no explanations.`, condition: { field: 'operation', value: 'incidentio_incidents_create' }, required: true, }, - { - id: 'idempotency_key', - title: 'Idempotency Key', - type: 'short-input', - placeholder: 'Enter unique key (e.g., UUID)', - condition: { field: 'operation', value: 'incidentio_incidents_create' }, - required: true, - }, // Show/Update Incident inputs { id: 'id', @@ -266,26 +264,44 @@ Return ONLY the summary text - no explanations.`, condition: { field: 'operation', value: [ + 'incidentio_incidents_create', + 'incidentio_incidents_update', + 'incidentio_workflows_create', + 'incidentio_workflows_update', 'incidentio_schedules_create', + 'incidentio_schedules_update', 'incidentio_custom_fields_create', 'incidentio_custom_fields_update', 'incidentio_incident_roles_create', 'incidentio_incident_roles_update', 'incidentio_escalation_paths_create', + 'incidentio_escalation_paths_update', ], }, - required: true, - }, - { - id: 'name', - title: 'Name', - type: 'short-input', - placeholder: 'Enter name (optional for update)...', - condition: { + required: { field: 'operation', - value: 'incidentio_escalation_paths_update', + value: [ + 'incidentio_workflows_create', + 'incidentio_workflows_update', + 'incidentio_schedules_create', + 'incidentio_custom_fields_create', + 'incidentio_incident_roles_create', + 'incidentio_incident_roles_update', + 'incidentio_escalation_paths_create', + 'incidentio_escalation_paths_update', + ], + }, + wandConfig: { + enabled: true, + prompt: `Generate a concise resource name based on the user's description. +The name should: +- Be clear and descriptive +- Indicate the resource purpose +- Be suitable for incident.io records + +Return ONLY the name - no explanations.`, + placeholder: 'Describe the name you want to use...', }, - required: false, }, // Escalations inputs { @@ -293,8 +309,14 @@ Return ONLY the summary text - no explanations.`, title: 'Idempotency Key', type: 'short-input', placeholder: 'Enter unique key (e.g., UUID)...', - condition: { field: 'operation', value: 'incidentio_escalations_create' }, - required: true, + condition: { + field: 'operation', + value: ['incidentio_incidents_create', 'incidentio_escalations_create'], + }, + required: { + field: 'operation', + value: ['incidentio_incidents_create', 'incidentio_escalations_create'], + }, }, { id: 'title', @@ -330,30 +352,37 @@ Return ONLY the title - no explanations.`, placeholder: 'Enter user IDs, comma-separated (required if no path ID)...', condition: { field: 'operation', value: 'incidentio_escalations_create' }, }, + // Actions List inputs { - id: 'name', - title: 'Name', + id: 'incident_id', + title: 'Incident ID', type: 'short-input', - placeholder: 'Enter name...', + placeholder: 'Filter by incident ID...', condition: { field: 'operation', value: [ - 'incidentio_incidents_update', - 'incidentio_workflows_update', - 'incidentio_schedules_update', + 'incidentio_actions_list', + 'incidentio_follow_ups_list', + 'incidentio_incident_updates_list', ], }, }, - // Actions List inputs { - id: 'incident_id', - title: 'Incident ID', - type: 'short-input', - placeholder: 'Filter by incident ID...', + id: 'incident_mode', + title: 'Incident Mode', + type: 'dropdown', + options: [ + { label: 'Standard', id: 'standard' }, + { label: 'Retrospective', id: 'retrospective' }, + { label: 'Test', id: 'test' }, + { label: 'Tutorial', id: 'tutorial' }, + { label: 'Stream', id: 'stream' }, + ], condition: { field: 'operation', value: ['incidentio_actions_list', 'incidentio_follow_ups_list'], }, + mode: 'advanced', }, // Workflows inputs { @@ -361,7 +390,18 @@ Return ONLY the title - no explanations.`, title: 'Folder', type: 'short-input', placeholder: 'Enter folder name...', - condition: { field: 'operation', value: 'incidentio_workflows_update' }, + condition: { + field: 'operation', + value: ['incidentio_workflows_create', 'incidentio_workflows_update'], + }, + }, + { + id: 'skip_step_upgrades', + title: 'Skip Step Upgrades', + type: 'switch', + value: () => 'false', + condition: { field: 'operation', value: 'incidentio_workflows_show' }, + mode: 'advanced', }, { id: 'state', @@ -373,7 +413,172 @@ Return ONLY the title - no explanations.`, { label: 'Disabled', id: 'disabled' }, ], value: () => 'active', - condition: { field: 'operation', value: 'incidentio_workflows_update' }, + condition: { + field: 'operation', + value: ['incidentio_workflows_create', 'incidentio_workflows_update'], + }, + }, + { + id: 'trigger', + title: 'Trigger', + type: 'short-input', + placeholder: 'incident.updated', + condition: { field: 'operation', value: 'incidentio_workflows_create' }, + mode: 'advanced', + }, + { + id: 'runs_on_incidents', + title: 'Runs On Incidents', + type: 'dropdown', + options: [ + { label: 'Newly Created', id: 'newly_created' }, + { label: 'Newly Created and Active', id: 'newly_created_and_active' }, + { label: 'Active', id: 'active' }, + { label: 'All', id: 'all' }, + ], + value: () => 'newly_created', + condition: { + field: 'operation', + value: ['incidentio_workflows_create', 'incidentio_workflows_update'], + }, + mode: 'advanced', + required: { field: 'operation', value: 'incidentio_workflows_update' }, + }, + { + id: 'runs_on_incident_modes', + title: 'Incident Modes', + type: 'short-input', + placeholder: '["standard"]', + condition: { + field: 'operation', + value: ['incidentio_workflows_create', 'incidentio_workflows_update'], + }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON array of incident.io incident modes, such as ["standard"] or ["standard","retrospective"]. Return ONLY the JSON array - no explanations, no extra text.', + placeholder: 'Describe which incident modes should run the workflow...', + generationType: 'json-object', + }, + }, + { + id: 'steps', + title: 'Steps', + type: 'long-input', + placeholder: '[{"label":"Notify team","name":"slack.post_message"}]', + condition: { + field: 'operation', + value: ['incidentio_workflows_create', 'incidentio_workflows_update'], + }, + mode: 'advanced', + required: { field: 'operation', value: 'incidentio_workflows_update' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON array of incident.io workflow steps. Return ONLY the JSON array - no explanations, no extra text.', + placeholder: 'Describe the workflow steps...', + generationType: 'json-object', + }, + }, + { + id: 'condition_groups', + title: 'Condition Groups', + type: 'long-input', + placeholder: '[]', + condition: { + field: 'operation', + value: ['incidentio_workflows_create', 'incidentio_workflows_update'], + }, + mode: 'advanced', + required: { field: 'operation', value: 'incidentio_workflows_update' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON array of incident.io workflow condition groups. Return ONLY the JSON array - no explanations, no extra text.', + placeholder: 'Describe the workflow conditions...', + generationType: 'json-object', + }, + }, + { + id: 'expressions', + title: 'Expressions', + type: 'long-input', + placeholder: '[]', + condition: { + field: 'operation', + value: ['incidentio_workflows_create', 'incidentio_workflows_update'], + }, + mode: 'advanced', + required: { field: 'operation', value: 'incidentio_workflows_update' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON array of incident.io workflow expressions. Return ONLY the JSON array - no explanations, no extra text.', + placeholder: 'Describe any workflow expressions...', + generationType: 'json-object', + }, + }, + { + id: 'once_for', + title: 'Once For', + type: 'short-input', + placeholder: '[]', + condition: { + field: 'operation', + value: ['incidentio_workflows_create', 'incidentio_workflows_update'], + }, + mode: 'advanced', + required: { field: 'operation', value: 'incidentio_workflows_update' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON array of fields that should make an incident.io workflow run only once for each unique combination. Return ONLY the JSON array - no explanations, no extra text.', + placeholder: 'Describe what should make the workflow run once...', + generationType: 'json-object', + }, + }, + { + id: 'delay', + title: 'Delay', + type: 'long-input', + placeholder: '{"for_seconds":60,"conditions_apply_over_delay":false}', + condition: { + field: 'operation', + value: ['incidentio_workflows_create', 'incidentio_workflows_update'], + }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON object for an incident.io workflow delay. Return ONLY the JSON object - no explanations, no extra text.', + placeholder: 'Describe the workflow delay...', + generationType: 'json-object', + }, + }, + { + id: 'include_private_incidents', + title: 'Include Private Incidents', + type: 'switch', + value: () => 'true', + condition: { + field: 'operation', + value: ['incidentio_workflows_create', 'incidentio_workflows_update'], + }, + mode: 'advanced', + required: { field: 'operation', value: 'incidentio_workflows_update' }, + }, + { + id: 'continue_on_step_error', + title: 'Continue On Step Error', + type: 'switch', + value: () => 'false', + condition: { + field: 'operation', + value: ['incidentio_workflows_create', 'incidentio_workflows_update'], + }, + mode: 'advanced', + required: { field: 'operation', value: 'incidentio_workflows_update' }, }, // Schedules inputs { @@ -406,8 +611,11 @@ Return ONLY the title - no explanations.`, { label: 'UTC', id: 'UTC' }, ], value: () => 'UTC', - condition: { field: 'operation', value: 'incidentio_schedules_create' }, - required: true, + condition: { + field: 'operation', + value: ['incidentio_schedules_create', 'incidentio_schedules_update'], + }, + required: { field: 'operation', value: 'incidentio_schedules_create' }, }, { id: 'config', @@ -438,38 +646,6 @@ Return ONLY the JSON object - no explanations or markdown formatting.`, generationType: 'json-object', }, }, - { - id: 'timezone', - title: 'Timezone', - type: 'dropdown', - options: [ - { label: 'America/New_York (Eastern)', id: 'America/New_York' }, - { label: 'America/Chicago (Central)', id: 'America/Chicago' }, - { label: 'America/Denver (Mountain)', id: 'America/Denver' }, - { label: 'America/Los_Angeles (Pacific)', id: 'America/Los_Angeles' }, - { label: 'America/Phoenix (Arizona)', id: 'America/Phoenix' }, - { label: 'America/Anchorage (Alaska)', id: 'America/Anchorage' }, - { label: 'Pacific/Honolulu (Hawaii)', id: 'Pacific/Honolulu' }, - { label: 'Europe/London (UK)', id: 'Europe/London' }, - { label: 'Europe/Paris (Central Europe)', id: 'Europe/Paris' }, - { label: 'Europe/Berlin (Germany)', id: 'Europe/Berlin' }, - { label: 'Europe/Dublin (Ireland)', id: 'Europe/Dublin' }, - { label: 'Europe/Amsterdam (Netherlands)', id: 'Europe/Amsterdam' }, - { label: 'Asia/Tokyo (Japan)', id: 'Asia/Tokyo' }, - { label: 'Asia/Singapore', id: 'Asia/Singapore' }, - { label: 'Asia/Hong_Kong', id: 'Asia/Hong_Kong' }, - { label: 'Asia/Shanghai (China)', id: 'Asia/Shanghai' }, - { label: 'Asia/Seoul (South Korea)', id: 'Asia/Seoul' }, - { label: 'Asia/Dubai (UAE)', id: 'Asia/Dubai' }, - { label: 'Asia/Kolkata (India)', id: 'Asia/Kolkata' }, - { label: 'Australia/Sydney', id: 'Australia/Sydney' }, - { label: 'Australia/Melbourne', id: 'Australia/Melbourne' }, - { label: 'Pacific/Auckland (New Zealand)', id: 'Pacific/Auckland' }, - { label: 'UTC', id: 'UTC' }, - ], - value: () => 'UTC', - condition: { field: 'operation', value: 'incidentio_schedules_update' }, - }, // Custom Fields inputs { id: 'description', @@ -478,7 +654,12 @@ Return ONLY the JSON object - no explanations or markdown formatting.`, placeholder: 'Enter description...', condition: { field: 'operation', - value: ['incidentio_custom_fields_create', 'incidentio_custom_fields_update'], + value: [ + 'incidentio_custom_fields_create', + 'incidentio_custom_fields_update', + 'incidentio_incident_roles_create', + 'incidentio_incident_roles_update', + ], }, required: true, wandConfig: { @@ -510,29 +691,6 @@ Return ONLY the description text - no explanations.`, required: true, }, // Incident Roles inputs - { - id: 'description', - title: 'Description', - type: 'long-input', - placeholder: 'Enter description...', - condition: { - field: 'operation', - value: ['incidentio_incident_roles_create', 'incidentio_incident_roles_update'], - }, - required: true, - wandConfig: { - enabled: true, - prompt: `Generate a description for an incident role based on the user's description. -The description should: -- Explain the role's responsibilities -- Clarify when this role is needed -- Be suitable for incident response documentation - -Return ONLY the description text - no explanations.`, - placeholder: - 'Describe the role (e.g., "coordinates communication with stakeholders", "leads technical investigation")...', - }, - }, { id: 'instructions', title: 'Instructions', @@ -568,14 +726,6 @@ Return ONLY the instructions text - no explanations.`, required: true, }, // Incident Updates inputs - { - id: 'incident_id', - title: 'Incident ID', - type: 'short-input', - placeholder: 'Enter incident ID (optional - leave blank for all incidents)...', - condition: { field: 'operation', value: 'incidentio_incident_updates_list' }, - required: false, - }, // Schedule Entries inputs { id: 'schedule_id', @@ -637,6 +787,14 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, condition: { field: 'operation', value: 'incidentio_schedule_overrides_create' }, required: true, }, + { + id: 'layer_id', + title: 'Layer ID', + type: 'short-input', + placeholder: 'Enter layer ID...', + condition: { field: 'operation', value: 'incidentio_schedule_overrides_create' }, + required: true, + }, { id: 'user_id', title: 'User ID', @@ -649,16 +807,22 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, id: 'user_email', title: 'User Email', type: 'short-input', - placeholder: 'Enter user email (provide one of: user_id, user_email, or user_slack_id)...', - condition: { field: 'operation', value: 'incidentio_schedule_overrides_create' }, + placeholder: 'Enter user email...', + condition: { + field: 'operation', + value: ['incidentio_schedule_overrides_create', 'incidentio_users_list'], + }, required: false, }, { id: 'user_slack_id', title: 'User Slack ID', type: 'short-input', - placeholder: 'Enter user Slack ID (provide one of: user_id, user_email, or user_slack_id)...', - condition: { field: 'operation', value: 'incidentio_schedule_overrides_create' }, + placeholder: 'Enter user Slack ID...', + condition: { + field: 'operation', + value: ['incidentio_schedule_overrides_create', 'incidentio_users_list'], + }, required: false, }, { @@ -712,7 +876,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, 'JSON array of escalation levels: [{"targets": [{"id": "...", "type": "...", "urgency": "..."}], "time_to_ack_seconds": 300}]', condition: { field: 'operation', - value: 'incidentio_escalation_paths_create', + value: ['incidentio_escalation_paths_create', 'incidentio_escalation_paths_update'], }, required: true, wandConfig: { @@ -734,18 +898,6 @@ Return ONLY the JSON array - no explanations or markdown formatting.`, generationType: 'json-object', }, }, - { - id: 'path', - title: 'Path Configuration', - type: 'long-input', - placeholder: - 'JSON array of escalation levels (optional for update): [{"targets": [{"id": "...", "type": "...", "urgency": "..."}], "time_to_ack_seconds": 300}]', - condition: { - field: 'operation', - value: 'incidentio_escalation_paths_update', - }, - required: false, - }, { id: 'working_hours', title: 'Working Hours', @@ -792,6 +944,7 @@ Return ONLY the JSON array - no explanations or markdown formatting.`, 'incidentio_users_list', 'incidentio_users_show', 'incidentio_workflows_list', + 'incidentio_workflows_create', 'incidentio_workflows_show', 'incidentio_workflows_update', 'incidentio_workflows_delete', @@ -821,6 +974,7 @@ Return ONLY the JSON array - no explanations or markdown formatting.`, 'incidentio_incident_updates_list', 'incidentio_schedule_entries_list', 'incidentio_schedule_overrides_create', + 'incidentio_escalation_paths_list', 'incidentio_escalation_paths_create', 'incidentio_escalation_paths_show', 'incidentio_escalation_paths_update', @@ -851,6 +1005,8 @@ Return ONLY the JSON array - no explanations or markdown formatting.`, return 'incidentio_users_show' case 'incidentio_workflows_list': return 'incidentio_workflows_list' + case 'incidentio_workflows_create': + return 'incidentio_workflows_create' case 'incidentio_workflows_show': return 'incidentio_workflows_show' case 'incidentio_workflows_update': @@ -909,6 +1065,8 @@ Return ONLY the JSON array - no explanations or markdown formatting.`, return 'incidentio_schedule_entries_list' case 'incidentio_schedule_overrides_create': return 'incidentio_schedule_overrides_create' + case 'incidentio_escalation_paths_list': + return 'incidentio_escalation_paths_list' case 'incidentio_escalation_paths_create': return 'incidentio_escalation_paths_create' case 'incidentio_escalation_paths_show': @@ -923,9 +1081,23 @@ Return ONLY the JSON array - no explanations or markdown formatting.`, }, params: (params) => { const result: Record = {} + const toBoolean = (value: unknown) => value === true || value === 'true' if (params.page_size) result.page_size = Number(params.page_size) if (params.notify_incident_channel !== undefined) { - result.notify_incident_channel = params.notify_incident_channel === 'true' + result.notify_incident_channel = toBoolean(params.notify_incident_channel) + } + if (params.include_private_incidents !== undefined) { + result.include_private_incidents = toBoolean(params.include_private_incidents) + } + if (params.continue_on_step_error !== undefined) { + result.continue_on_step_error = toBoolean(params.continue_on_step_error) + } + if (params.skip_step_upgrades !== undefined) { + result.skip_step_upgrades = toBoolean(params.skip_step_upgrades) + } + if (params.operation === 'incidentio_users_list') { + if (params.user_email) result.email = params.user_email + if (params.user_slack_id) result.slack_user_id = params.user_slack_id } return result }, @@ -939,6 +1111,8 @@ Return ONLY the JSON array - no explanations or markdown formatting.`, name: { type: 'string', description: 'Resource name' }, page_size: { type: 'number', description: 'Number of results per page' }, after: { type: 'string', description: 'Pagination cursor' }, + sort_by: { type: 'string', description: 'Incident sort order' }, + filter_mode: { type: 'string', description: 'Incident filter combination mode' }, // Incident fields summary: { type: 'string', description: 'Incident summary' }, severity_id: { type: 'string', description: 'Severity ID' }, @@ -946,6 +1120,7 @@ Return ONLY the JSON array - no explanations or markdown formatting.`, incident_status_id: { type: 'string', description: 'Incident status ID' }, visibility: { type: 'string', description: 'Incident visibility' }, incident_id: { type: 'string', description: 'Incident ID for filtering' }, + incident_mode: { type: 'string', description: 'Incident mode filter' }, notify_incident_channel: { type: 'boolean', description: 'Whether to notify the incident channel', @@ -953,6 +1128,29 @@ Return ONLY the JSON array - no explanations or markdown formatting.`, // Workflow fields folder: { type: 'string', description: 'Workflow folder' }, state: { type: 'string', description: 'Workflow state' }, + trigger: { type: 'string', description: 'Workflow trigger type' }, + skip_step_upgrades: { type: 'boolean', description: 'Whether to skip workflow step upgrades' }, + steps: { type: 'string', description: 'Workflow steps JSON' }, + condition_groups: { type: 'string', description: 'Workflow condition groups JSON' }, + runs_on_incidents: { + type: 'string', + description: 'Incident lifecycle filter for workflow runs', + }, + runs_on_incident_modes: { + type: 'string', + description: 'Incident modes JSON for workflow runs', + }, + include_private_incidents: { + type: 'boolean', + description: 'Whether the workflow includes private incidents', + }, + continue_on_step_error: { + type: 'boolean', + description: 'Whether workflow execution continues after a step error', + }, + once_for: { type: 'string', description: 'Workflow run-once fields JSON' }, + expressions: { type: 'string', description: 'Workflow expressions JSON' }, + delay: { type: 'string', description: 'Workflow delay JSON' }, // Schedule fields timezone: { type: 'string', description: 'Schedule timezone' }, // Custom field fields @@ -964,9 +1162,12 @@ Return ONLY the JSON array - no explanations or markdown formatting.`, required: { type: 'boolean', description: 'Whether the role is required' }, // Schedule Entries/Overrides fields schedule_id: { type: 'string', description: 'Schedule ID' }, - from: { type: 'string', description: 'Start date/time for filtering' }, - to: { type: 'string', description: 'End date/time for filtering' }, + entry_window_start: { type: 'string', description: 'Schedule entry window start' }, + entry_window_end: { type: 'string', description: 'Schedule entry window end' }, + rotation_id: { type: 'string', description: 'Schedule rotation ID' }, user_id: { type: 'string', description: 'User ID' }, + user_email: { type: 'string', description: 'User email' }, + user_slack_id: { type: 'string', description: 'User Slack ID' }, start_at: { type: 'string', description: 'Start date/time' }, end_at: { type: 'string', description: 'End date/time' }, layer_id: { type: 'string', description: 'Schedule layer ID' }, @@ -990,6 +1191,7 @@ Return ONLY the JSON array - no explanations or markdown formatting.`, // Workflows workflows: { type: 'json', description: 'List of workflows' }, workflow: { type: 'json', description: 'Workflow details' }, + management_meta: { type: 'json', description: 'Workflow management metadata' }, // Schedules schedules: { type: 'json', description: 'List of schedules' }, schedule: { type: 'json', description: 'Schedule details' }, @@ -1016,6 +1218,7 @@ Return ONLY the JSON array - no explanations or markdown formatting.`, // Schedule Overrides schedule_override: { type: 'json', description: 'Schedule override details' }, // Escalation Paths + escalation_paths: { type: 'json', description: 'List of escalation paths' }, escalation_path: { type: 'json', description: 'Escalation path details' }, // General message: { type: 'string', description: 'Operation result message' }, diff --git a/apps/sim/blocks/blocks/new_relic.ts b/apps/sim/blocks/blocks/new_relic.ts new file mode 100644 index 00000000000..d9001b0df26 --- /dev/null +++ b/apps/sim/blocks/blocks/new_relic.ts @@ -0,0 +1,355 @@ +import { NewRelicIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' +import type { NewRelicCustomAttributes, NewRelicResponse } from '@/tools/new_relic/types' + +function parseCustomAttributes(value: unknown): NewRelicCustomAttributes | undefined { + if (!value) return undefined + if (typeof value !== 'string') return value as NewRelicCustomAttributes + + const trimmed = value.trim() + if (!trimmed) return undefined + + try { + return JSON.parse(trimmed) as NewRelicCustomAttributes + } catch (error) { + throw new Error( + `Invalid JSON for customAttributes: ${error instanceof Error ? error.message : String(error)}` + ) + } +} + +export const NewRelicBlock: BlockConfig = { + type: 'new_relic', + name: 'New Relic', + description: 'Query observability data and record deployments in New Relic', + longDescription: + 'Integrate New Relic into workflows. Run NRQL queries, search monitored entities, fetch entity details, and record deployment change events.', + docsLink: 'https://docs.sim.ai/tools/new_relic', + category: 'tools', + authMode: AuthMode.ApiKey, + integrationType: IntegrationType.Analytics, + tags: ['monitoring', 'data-analytics', 'incident-management'], + bgColor: '#000000', + icon: NewRelicIcon, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Run NRQL Query', id: 'new_relic_nrql_query' }, + { label: 'Search Entities', id: 'new_relic_search_entities' }, + { label: 'Get Entity', id: 'new_relic_get_entity' }, + { label: 'Create Deployment Event', id: 'new_relic_create_deployment_event' }, + ], + value: () => 'new_relic_nrql_query', + }, + { + id: 'apiKey', + title: 'User API Key', + type: 'short-input', + placeholder: 'NRAK-...', + password: true, + required: true, + }, + { + id: 'region', + title: 'Region', + type: 'dropdown', + options: [ + { label: 'US', id: 'us' }, + { label: 'EU', id: 'eu' }, + ], + value: () => 'us', + }, + { + id: 'accountId', + title: 'Account ID', + type: 'short-input', + placeholder: '1234567', + condition: { field: 'operation', value: 'new_relic_nrql_query' }, + required: true, + }, + { + id: 'nrql', + title: 'NRQL Query', + type: 'code', + placeholder: 'SELECT count(*) FROM Transaction SINCE 1 hour ago', + condition: { field: 'operation', value: 'new_relic_nrql_query' }, + required: true, + wandConfig: { + enabled: true, + prompt: `Generate a New Relic NRQL query based on the user's request. + +Return ONLY the NRQL query - no explanations, no extra text.`, + placeholder: 'Describe the New Relic data you want to query...', + }, + }, + { + id: 'timeout', + title: 'Timeout (Seconds)', + type: 'short-input', + placeholder: '70', + condition: { field: 'operation', value: 'new_relic_nrql_query' }, + mode: 'advanced', + }, + { + id: 'query', + title: 'Entity Search Query', + type: 'long-input', + placeholder: 'name like "api" and domainType = "APM-APPLICATION"', + condition: { field: 'operation', value: 'new_relic_search_entities' }, + required: true, + wandConfig: { + enabled: true, + prompt: `Generate a New Relic entity search query based on the user's request. + +Examples: +name like "api" +domainType = "APM-APPLICATION" +reporting is false and lastReportingChangeAt > 1651708800000 + +Return ONLY the entity search query - no explanations, no extra text.`, + placeholder: 'Describe the entities you want to find...', + }, + }, + { + id: 'cursor', + title: 'Cursor', + type: 'short-input', + placeholder: 'Next cursor from prior search', + condition: { field: 'operation', value: 'new_relic_search_entities' }, + mode: 'advanced', + }, + { + id: 'guid', + title: 'Entity GUID', + type: 'short-input', + placeholder: 'Entity GUID', + condition: { field: 'operation', value: 'new_relic_get_entity' }, + required: true, + }, + { + id: 'entityGuid', + title: 'Entity GUID', + type: 'short-input', + placeholder: 'Entity GUID', + condition: { field: 'operation', value: 'new_relic_create_deployment_event' }, + required: true, + }, + { + id: 'version', + title: 'Version', + type: 'short-input', + placeholder: '1.2.3 or commit SHA', + condition: { field: 'operation', value: 'new_relic_create_deployment_event' }, + required: true, + }, + { + id: 'shortDescription', + title: 'Short Description', + type: 'short-input', + placeholder: 'Deploy version 1.2.3', + condition: { field: 'operation', value: 'new_relic_create_deployment_event' }, + wandConfig: { + enabled: true, + prompt: `Generate a concise New Relic deployment change description based on the user's request. + +Return ONLY the description text - no explanations, no extra text.`, + placeholder: 'Describe the deployment...', + }, + }, + { + id: 'description', + title: 'Description', + type: 'long-input', + placeholder: 'Deployment details', + condition: { field: 'operation', value: 'new_relic_create_deployment_event' }, + mode: 'advanced', + }, + { + id: 'changelog', + title: 'Changelog', + type: 'long-input', + placeholder: 'Release notes, summary, or changelog URL', + condition: { field: 'operation', value: 'new_relic_create_deployment_event' }, + mode: 'advanced', + }, + { + id: 'commit', + title: 'Commit', + type: 'short-input', + placeholder: 'Commit SHA or build identifier', + condition: { field: 'operation', value: 'new_relic_create_deployment_event' }, + mode: 'advanced', + }, + { + id: 'deepLink', + title: 'Deep Link', + type: 'short-input', + placeholder: 'https://github.com/org/repo/actions/runs/123', + condition: { field: 'operation', value: 'new_relic_create_deployment_event' }, + mode: 'advanced', + }, + { + id: 'user', + title: 'User', + type: 'short-input', + placeholder: 'deploy-bot@example.com', + condition: { field: 'operation', value: 'new_relic_create_deployment_event' }, + mode: 'advanced', + }, + { + id: 'groupId', + title: 'Group ID', + type: 'short-input', + placeholder: 'release-2026-05-19', + condition: { field: 'operation', value: 'new_relic_create_deployment_event' }, + mode: 'advanced', + }, + { + id: 'customAttributes', + title: 'Custom Attributes', + type: 'code', + language: 'json', + placeholder: '{"isProduction": true, "region": "us-east-1", "instances": 2}', + condition: { field: 'operation', value: 'new_relic_create_deployment_event' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate a JSON object of New Relic change tracking custom attributes based on the user's request. +Use only string, number, and boolean values. + +Return ONLY the JSON object - no explanations, no extra text.`, + placeholder: 'Describe the custom metadata to attach...', + generationType: 'json-object', + }, + }, + { + id: 'deploymentType', + title: 'Deployment Type', + type: 'dropdown', + options: [ + { label: 'Basic', id: 'basic' }, + { label: 'Blue Green', id: 'blue green' }, + { label: 'Canary', id: 'canary' }, + { label: 'Rolling', id: 'rolling' }, + { label: 'Shadow', id: 'shadow' }, + ], + value: () => 'basic', + condition: { field: 'operation', value: 'new_relic_create_deployment_event' }, + mode: 'advanced', + }, + { + id: 'timestamp', + title: 'Timestamp (Epoch ms)', + type: 'short-input', + placeholder: '1767225600000', + condition: { field: 'operation', value: 'new_relic_create_deployment_event' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate an epoch millisecond timestamp based on the user's request. + +Return ONLY the numeric timestamp - no explanations, no extra text.`, + placeholder: 'Describe the deployment time...', + generationType: 'timestamp', + }, + }, + ], + tools: { + access: [ + 'new_relic_nrql_query', + 'new_relic_search_entities', + 'new_relic_get_entity', + 'new_relic_create_deployment_event', + ], + config: { + tool: (params) => String(params.operation || 'new_relic_nrql_query'), + params: (params) => { + const baseParams = { + apiKey: params.apiKey, + region: params.region || 'us', + } + + switch (params.operation) { + case 'new_relic_nrql_query': + return { + ...baseParams, + accountId: Number(params.accountId), + nrql: params.nrql, + timeout: params.timeout ? Number(params.timeout) : undefined, + } + + case 'new_relic_search_entities': + return { + ...baseParams, + query: params.query, + cursor: params.cursor, + } + + case 'new_relic_get_entity': + return { + ...baseParams, + guid: params.guid, + } + + case 'new_relic_create_deployment_event': + return { + ...baseParams, + entityGuid: params.entityGuid, + version: params.version, + shortDescription: params.shortDescription, + description: params.description, + changelog: params.changelog, + commit: params.commit, + deepLink: params.deepLink, + user: params.user, + groupId: params.groupId, + customAttributes: parseCustomAttributes(params.customAttributes), + deploymentType: params.deploymentType, + timestamp: params.timestamp ? Number(params.timestamp) : undefined, + } + + default: + return baseParams + } + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'New Relic user API key' }, + region: { type: 'string', description: 'New Relic data center region' }, + accountId: { type: 'number', description: 'New Relic account ID' }, + nrql: { type: 'string', description: 'NRQL query' }, + timeout: { type: 'number', description: 'Optional NRQL timeout in seconds' }, + query: { type: 'string', description: 'Entity search query' }, + cursor: { type: 'string', description: 'Entity search pagination cursor' }, + guid: { type: 'string', description: 'Entity GUID' }, + entityGuid: { type: 'string', description: 'Deployment entity GUID' }, + version: { type: 'string', description: 'Deployment version' }, + shortDescription: { type: 'string', description: 'Short deployment description' }, + description: { type: 'string', description: 'Deployment description' }, + changelog: { type: 'string', description: 'Deployment changelog text or URL' }, + commit: { type: 'string', description: 'Deployment commit SHA or identifier' }, + deepLink: { type: 'string', description: 'Deployment, build, or release URL' }, + user: { type: 'string', description: 'Deployment user' }, + groupId: { type: 'string', description: 'Deployment group ID' }, + customAttributes: { type: 'json', description: 'Custom change event metadata' }, + deploymentType: { type: 'string', description: 'Deployment type' }, + timestamp: { type: 'number', description: 'Deployment timestamp in epoch milliseconds' }, + }, + outputs: { + results: { type: 'json', description: 'NRQL result rows' }, + resultCount: { type: 'number', description: 'Number of NRQL result rows' }, + count: { type: 'number', description: 'Number of matching entities' }, + query: { type: 'string', description: 'Entity search query New Relic executed' }, + entities: { type: 'json', description: 'Matching New Relic entities (guid, name, entityType)' }, + nextCursor: { type: 'string', description: 'Cursor for the next entity search page' }, + entity: { type: 'json', description: 'New Relic entity details (guid, name, entityType)' }, + event: { type: 'json', description: 'Created change tracking event metadata' }, + messages: { type: 'json', description: 'New Relic change tracking messages' }, + }, +} diff --git a/apps/sim/blocks/blocks/railway.ts b/apps/sim/blocks/blocks/railway.ts new file mode 100644 index 00000000000..e011afea67e --- /dev/null +++ b/apps/sim/blocks/blocks/railway.ts @@ -0,0 +1,642 @@ +import { RailwayIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' +import type { RailwayResponse } from '@/tools/railway/types' + +export const RailwayBlock: BlockConfig = { + type: 'railway', + name: 'Railway', + description: 'Manage Railway projects, deployments, and variables', + longDescription: + 'Integrate Railway into workflows to list projects, inspect services and environments, monitor deployments, trigger service deployments, and manage environment variables.', + docsLink: 'https://docs.sim.ai/tools/railway', + category: 'tools', + integrationType: IntegrationType.DeveloperTools, + tags: ['cloud', 'ci-cd'], + bgColor: '#FFFFFF', + icon: RailwayIcon, + authMode: AuthMode.ApiKey, + + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'List Projects', id: 'list_projects' }, + { label: 'Get Project', id: 'get_project' }, + { label: 'Create Project', id: 'create_project' }, + { label: 'Update Project', id: 'update_project' }, + { label: 'Delete Project', id: 'delete_project' }, + { label: 'Transfer Project', id: 'transfer_project' }, + { label: 'List Project Members', id: 'list_project_members' }, + { label: 'Create Environment', id: 'create_environment' }, + { label: 'Delete Environment', id: 'delete_environment' }, + { label: 'List Deployments', id: 'list_deployments' }, + { label: 'Deploy Service', id: 'deploy_service' }, + { label: 'List Variables', id: 'list_variables' }, + { label: 'Upsert Variable', id: 'upsert_variable' }, + ], + value: () => 'list_projects', + }, + { + id: 'apiKey', + title: 'API Token', + type: 'short-input', + placeholder: 'Enter Railway API token', + password: true, + required: true, + }, + { + id: 'tokenType', + title: 'Token Type', + type: 'dropdown', + options: [ + { label: 'Account / Workspace / OAuth', id: 'account' }, + { label: 'Project', id: 'project' }, + ], + value: () => 'account', + mode: 'advanced', + }, + { + id: 'listProjectsWorkspaceId', + title: 'Workspace ID', + type: 'short-input', + placeholder: 'Workspace ID', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'first', + title: 'Limit', + type: 'short-input', + placeholder: '20', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'after', + title: 'After Cursor', + type: 'short-input', + placeholder: 'Cursor from a previous response', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'detailProjectId', + title: 'Project ID', + type: 'short-input', + placeholder: 'Railway project ID', + condition: { field: 'operation', value: 'get_project' }, + required: { field: 'operation', value: 'get_project' }, + }, + { + id: 'createProjectName', + title: 'Project Name', + type: 'short-input', + placeholder: 'my-app', + condition: { field: 'operation', value: 'create_project' }, + required: { field: 'operation', value: 'create_project' }, + }, + { + id: 'createProjectDescription', + title: 'Description', + type: 'long-input', + placeholder: 'Project description', + condition: { field: 'operation', value: 'create_project' }, + mode: 'advanced', + }, + { + id: 'createProjectWorkspaceId', + title: 'Workspace ID', + type: 'short-input', + placeholder: 'Workspace ID', + condition: { field: 'operation', value: 'create_project' }, + mode: 'advanced', + }, + { + id: 'createProjectIsPublic', + title: 'Public Project', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'create_project' }, + mode: 'advanced', + }, + { + id: 'defaultEnvironmentName', + title: 'Default Environment', + type: 'short-input', + placeholder: 'production', + condition: { field: 'operation', value: 'create_project' }, + mode: 'advanced', + }, + { + id: 'prDeploys', + title: 'PR Deploys', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'create_project' }, + mode: 'advanced', + }, + { + id: 'updateProjectId', + title: 'Project ID', + type: 'short-input', + placeholder: 'Railway project ID', + condition: { field: 'operation', value: 'update_project' }, + required: { field: 'operation', value: 'update_project' }, + }, + { + id: 'updateProjectName', + title: 'Project Name', + type: 'short-input', + placeholder: 'Updated project name', + condition: { field: 'operation', value: 'update_project' }, + }, + { + id: 'updateProjectDescription', + title: 'Description', + type: 'long-input', + placeholder: 'Updated project description', + condition: { field: 'operation', value: 'update_project' }, + }, + { + id: 'updateProjectIsPublic', + title: 'Public Project', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'update_project' }, + mode: 'advanced', + }, + { + id: 'updateProjectPrDeploys', + title: 'PR Deploys', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'update_project' }, + mode: 'advanced', + }, + { + id: 'deleteProjectId', + title: 'Project ID', + type: 'short-input', + placeholder: 'Railway project ID', + condition: { field: 'operation', value: 'delete_project' }, + required: { field: 'operation', value: 'delete_project' }, + }, + { + id: 'transferProjectId', + title: 'Project ID', + type: 'short-input', + placeholder: 'Railway project ID', + condition: { field: 'operation', value: 'transfer_project' }, + required: { field: 'operation', value: 'transfer_project' }, + }, + { + id: 'workspaceId', + title: 'Workspace ID', + type: 'short-input', + placeholder: 'Destination workspace ID', + condition: { field: 'operation', value: 'transfer_project' }, + required: { field: 'operation', value: 'transfer_project' }, + }, + { + id: 'membersProjectId', + title: 'Project ID', + type: 'short-input', + placeholder: 'Railway project ID', + condition: { field: 'operation', value: 'list_project_members' }, + required: { field: 'operation', value: 'list_project_members' }, + }, + { + id: 'createEnvironmentProjectId', + title: 'Project ID', + type: 'short-input', + placeholder: 'Railway project ID', + condition: { field: 'operation', value: 'create_environment' }, + required: { field: 'operation', value: 'create_environment' }, + }, + { + id: 'environmentName', + title: 'Environment Name', + type: 'short-input', + placeholder: 'staging', + condition: { field: 'operation', value: 'create_environment' }, + required: { field: 'operation', value: 'create_environment' }, + }, + { + id: 'sourceEnvironmentId', + title: 'Source Environment ID', + type: 'short-input', + placeholder: 'Environment ID to clone from', + condition: { field: 'operation', value: 'create_environment' }, + mode: 'advanced', + }, + { + id: 'ephemeral', + title: 'Ephemeral', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'create_environment' }, + mode: 'advanced', + }, + { + id: 'skipInitialDeploys', + title: 'Skip Initial Deploys', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'create_environment' }, + mode: 'advanced', + }, + { + id: 'stageInitialChanges', + title: 'Stage Initial Changes', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'create_environment' }, + mode: 'advanced', + }, + { + id: 'deleteEnvironmentId', + title: 'Environment ID', + type: 'short-input', + placeholder: 'Railway environment ID', + condition: { field: 'operation', value: 'delete_environment' }, + required: { field: 'operation', value: 'delete_environment' }, + }, + { + id: 'deploymentProjectId', + title: 'Project ID', + type: 'short-input', + placeholder: 'Railway project ID', + condition: { field: 'operation', value: 'list_deployments' }, + required: { field: 'operation', value: 'list_deployments' }, + }, + { + id: 'deploymentServiceId', + title: 'Service ID', + type: 'short-input', + placeholder: 'Railway service ID', + condition: { field: 'operation', value: 'list_deployments' }, + required: { field: 'operation', value: 'list_deployments' }, + }, + { + id: 'deploymentEnvironmentId', + title: 'Environment ID', + type: 'short-input', + placeholder: 'Railway environment ID', + condition: { field: 'operation', value: 'list_deployments' }, + required: { field: 'operation', value: 'list_deployments' }, + }, + { + id: 'deploymentFirst', + title: 'Limit', + type: 'short-input', + placeholder: '10', + condition: { field: 'operation', value: 'list_deployments' }, + mode: 'advanced', + }, + { + id: 'deploymentAfter', + title: 'After Cursor', + type: 'short-input', + placeholder: 'Cursor from a previous response', + condition: { field: 'operation', value: 'list_deployments' }, + mode: 'advanced', + }, + { + id: 'deployServiceId', + title: 'Service ID', + type: 'short-input', + placeholder: 'Railway service ID', + condition: { field: 'operation', value: 'deploy_service' }, + required: { field: 'operation', value: 'deploy_service' }, + }, + { + id: 'deployEnvironmentId', + title: 'Environment ID', + type: 'short-input', + placeholder: 'Railway environment ID', + condition: { field: 'operation', value: 'deploy_service' }, + required: { field: 'operation', value: 'deploy_service' }, + }, + { + id: 'deployCommitSha', + title: 'Commit SHA', + type: 'short-input', + placeholder: 'abc123...', + condition: { field: 'operation', value: 'deploy_service' }, + mode: 'advanced', + }, + { + id: 'variablesProjectId', + title: 'Project ID', + type: 'short-input', + placeholder: 'Railway project ID', + condition: { field: 'operation', value: 'list_variables' }, + required: { field: 'operation', value: 'list_variables' }, + }, + { + id: 'variablesEnvironmentId', + title: 'Environment ID', + type: 'short-input', + placeholder: 'Railway environment ID', + condition: { field: 'operation', value: 'list_variables' }, + required: { field: 'operation', value: 'list_variables' }, + }, + { + id: 'variablesServiceId', + title: 'Service ID', + type: 'short-input', + placeholder: 'Leave blank for shared variables', + condition: { field: 'operation', value: 'list_variables' }, + mode: 'advanced', + }, + { + id: 'upsertProjectId', + title: 'Project ID', + type: 'short-input', + placeholder: 'Railway project ID', + condition: { field: 'operation', value: 'upsert_variable' }, + required: { field: 'operation', value: 'upsert_variable' }, + }, + { + id: 'upsertEnvironmentId', + title: 'Environment ID', + type: 'short-input', + placeholder: 'Railway environment ID', + condition: { field: 'operation', value: 'upsert_variable' }, + required: { field: 'operation', value: 'upsert_variable' }, + }, + { + id: 'upsertServiceId', + title: 'Service ID', + type: 'short-input', + placeholder: 'Leave blank for shared variables', + condition: { field: 'operation', value: 'upsert_variable' }, + mode: 'advanced', + }, + { + id: 'variableName', + title: 'Variable Name', + type: 'short-input', + placeholder: 'DATABASE_URL', + condition: { field: 'operation', value: 'upsert_variable' }, + required: { field: 'operation', value: 'upsert_variable' }, + }, + { + id: 'variableValue', + title: 'Variable Value', + type: 'long-input', + placeholder: 'Variable value', + condition: { field: 'operation', value: 'upsert_variable' }, + required: { field: 'operation', value: 'upsert_variable' }, + }, + { + id: 'skipDeploys', + title: 'Skip Deploys', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'upsert_variable' }, + mode: 'advanced', + }, + ], + + tools: { + access: [ + 'railway_list_projects', + 'railway_get_project', + 'railway_create_project', + 'railway_update_project', + 'railway_delete_project', + 'railway_transfer_project', + 'railway_list_project_members', + 'railway_create_environment', + 'railway_delete_environment', + 'railway_list_deployments', + 'railway_deploy_service', + 'railway_list_variables', + 'railway_upsert_variable', + ], + config: { + tool: (params) => `railway_${params.operation}`, + params: (params) => { + const baseParams = { + apiKey: params.apiKey, + tokenType: params.tokenType, + } + + switch (params.operation) { + case 'list_projects': + return { + ...baseParams, + workspaceId: params.listProjectsWorkspaceId, + first: params.first ? Number(params.first) : undefined, + after: params.after, + } + case 'create_project': + return { + ...baseParams, + name: params.createProjectName, + description: params.createProjectDescription, + workspaceId: params.createProjectWorkspaceId, + isPublic: params.createProjectIsPublic + ? params.createProjectIsPublic === 'true' + : undefined, + defaultEnvironmentName: params.defaultEnvironmentName, + prDeploys: params.prDeploys ? params.prDeploys === 'true' : undefined, + } + case 'update_project': + return { + ...baseParams, + projectId: params.updateProjectId, + name: params.updateProjectName, + description: params.updateProjectDescription, + isPublic: params.updateProjectIsPublic + ? params.updateProjectIsPublic === 'true' + : undefined, + prDeploys: params.updateProjectPrDeploys + ? params.updateProjectPrDeploys === 'true' + : undefined, + } + case 'delete_project': + return { + ...baseParams, + projectId: params.deleteProjectId, + } + case 'transfer_project': + return { + ...baseParams, + projectId: params.transferProjectId, + workspaceId: params.workspaceId, + } + case 'list_project_members': + return { + ...baseParams, + projectId: params.membersProjectId, + } + case 'create_environment': + return { + ...baseParams, + projectId: params.createEnvironmentProjectId, + name: params.environmentName, + sourceEnvironmentId: params.sourceEnvironmentId, + ephemeral: params.ephemeral ? params.ephemeral === 'true' : undefined, + skipInitialDeploys: params.skipInitialDeploys + ? params.skipInitialDeploys === 'true' + : undefined, + stageInitialChanges: params.stageInitialChanges + ? params.stageInitialChanges === 'true' + : undefined, + } + case 'delete_environment': + return { + ...baseParams, + environmentId: params.deleteEnvironmentId, + } + case 'get_project': + return { + ...baseParams, + projectId: params.detailProjectId, + } + case 'list_deployments': + return { + ...baseParams, + projectId: params.deploymentProjectId, + serviceId: params.deploymentServiceId, + environmentId: params.deploymentEnvironmentId, + first: params.deploymentFirst ? Number(params.deploymentFirst) : undefined, + after: params.deploymentAfter, + } + case 'deploy_service': + return { + ...baseParams, + serviceId: params.deployServiceId, + environmentId: params.deployEnvironmentId, + commitSha: params.deployCommitSha, + } + case 'list_variables': + return { + ...baseParams, + projectId: params.variablesProjectId, + environmentId: params.variablesEnvironmentId, + serviceId: params.variablesServiceId, + } + case 'upsert_variable': + return { + ...baseParams, + projectId: params.upsertProjectId, + environmentId: params.upsertEnvironmentId, + serviceId: params.upsertServiceId, + name: params.variableName, + value: params.variableValue, + skipDeploys: params.skipDeploys ? params.skipDeploys === 'true' : undefined, + } + default: + return baseParams + } + }, + }, + }, + + inputs: { + apiKey: { type: 'string', description: 'Railway API token' }, + tokenType: { type: 'string', description: 'Railway token type' }, + listProjectsWorkspaceId: { type: 'string', description: 'Workspace ID for project listing' }, + first: { type: 'number', description: 'List projects limit' }, + after: { type: 'string', description: 'List projects pagination cursor' }, + detailProjectId: { type: 'string', description: 'Project ID for project lookup' }, + createProjectName: { type: 'string', description: 'Project name to create' }, + createProjectDescription: { type: 'string', description: 'Project description to create' }, + createProjectWorkspaceId: { type: 'string', description: 'Workspace ID for created project' }, + createProjectIsPublic: { type: 'string', description: 'Whether the created project is public' }, + defaultEnvironmentName: { type: 'string', description: 'Default environment name' }, + prDeploys: { type: 'string', description: 'Whether to enable PR deploys' }, + updateProjectId: { type: 'string', description: 'Project ID to update' }, + updateProjectName: { type: 'string', description: 'Updated project name' }, + updateProjectDescription: { type: 'string', description: 'Updated project description' }, + updateProjectIsPublic: { type: 'string', description: 'Whether the project is public' }, + updateProjectPrDeploys: { type: 'string', description: 'Whether to enable PR deploys' }, + deleteProjectId: { type: 'string', description: 'Project ID to delete' }, + transferProjectId: { type: 'string', description: 'Project ID to transfer' }, + workspaceId: { type: 'string', description: 'Destination workspace ID' }, + membersProjectId: { type: 'string', description: 'Project ID for member listing' }, + createEnvironmentProjectId: { type: 'string', description: 'Project ID for new environment' }, + environmentName: { type: 'string', description: 'Environment name to create' }, + sourceEnvironmentId: { type: 'string', description: 'Environment ID to clone from' }, + ephemeral: { type: 'string', description: 'Whether the environment is ephemeral' }, + skipInitialDeploys: { type: 'string', description: 'Whether to skip initial deploys' }, + stageInitialChanges: { type: 'string', description: 'Whether to stage initial changes' }, + deleteEnvironmentId: { type: 'string', description: 'Environment ID to delete' }, + deploymentProjectId: { type: 'string', description: 'Project ID for deployments' }, + deploymentServiceId: { type: 'string', description: 'Service ID for deployments' }, + deploymentEnvironmentId: { type: 'string', description: 'Environment ID for deployments' }, + deploymentFirst: { type: 'number', description: 'List deployments limit' }, + deploymentAfter: { type: 'string', description: 'List deployments pagination cursor' }, + deployServiceId: { type: 'string', description: 'Service ID to deploy' }, + deployEnvironmentId: { type: 'string', description: 'Environment ID to deploy' }, + deployCommitSha: { type: 'string', description: 'Specific Git commit SHA to deploy' }, + variablesProjectId: { type: 'string', description: 'Project ID for variables' }, + variablesEnvironmentId: { type: 'string', description: 'Environment ID for variables' }, + variablesServiceId: { type: 'string', description: 'Optional service ID for variables' }, + upsertProjectId: { type: 'string', description: 'Project ID for variable upsert' }, + upsertEnvironmentId: { type: 'string', description: 'Environment ID for variable upsert' }, + upsertServiceId: { type: 'string', description: 'Optional service ID for variable upsert' }, + variableName: { type: 'string', description: 'Variable name' }, + variableValue: { type: 'string', description: 'Variable value' }, + skipDeploys: { type: 'string', description: 'Whether to skip deploys after variable upsert' }, + }, + + outputs: { + projects: { type: 'json', description: 'List of Railway projects' }, + project: { type: 'json', description: 'Railway project with services and environments' }, + members: { type: 'json', description: 'Railway project members (id, role, user)' }, + environment: { type: 'json', description: 'Railway environment (id, name)' }, + deployments: { type: 'json', description: 'List of Railway deployments' }, + variables: { type: 'json', description: 'Railway environment variables' }, + deploymentId: { type: 'string', description: 'Created deployment ID' }, + success: { type: 'boolean', description: 'Whether the operation succeeded' }, + count: { type: 'number', description: 'Number of items returned' }, + pageInfo: { type: 'json', description: 'Pagination information' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 2e008d00edb..265b545e55b 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -139,6 +139,7 @@ import { MongoDBBlock } from '@/blocks/blocks/mongodb' import { MothershipBlock } from '@/blocks/blocks/mothership' import { MySQLBlock } from '@/blocks/blocks/mysql' import { Neo4jBlock } from '@/blocks/blocks/neo4j' +import { NewRelicBlock } from '@/blocks/blocks/new_relic' import { NoteBlock } from '@/blocks/blocks/note' import { NotionBlock, NotionV2Block } from '@/blocks/blocks/notion' import { ObsidianBlock } from '@/blocks/blocks/obsidian' @@ -161,6 +162,7 @@ import { ProspeoBlock } from '@/blocks/blocks/prospeo' import { PulseBlock, PulseV2Block } from '@/blocks/blocks/pulse' import { QdrantBlock } from '@/blocks/blocks/qdrant' import { QuiverBlock } from '@/blocks/blocks/quiver' +import { RailwayBlock } from '@/blocks/blocks/railway' import { RDSBlock } from '@/blocks/blocks/rds' import { RedditBlock } from '@/blocks/blocks/reddit' import { RedisBlock } from '@/blocks/blocks/redis' @@ -394,6 +396,7 @@ export const registry: Record = { mothership: MothershipBlock, mysql: MySQLBlock, neo4j: Neo4jBlock, + new_relic: NewRelicBlock, note: NoteBlock, notion: NotionBlock, notion_v2: NotionV2Block, @@ -418,6 +421,7 @@ export const registry: Record = { pulse_v2: PulseV2Block, qdrant: QdrantBlock, quiver: QuiverBlock, + railway: RailwayBlock, rds: RDSBlock, reddit: RedditBlock, redis: RedisBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 6508cb8bbb5..3db9c61a75d 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -6928,6 +6928,14 @@ export function HexIcon(props: SVGProps) { ) } +export function RailwayIcon(props: SVGProps) { + return ( + + + + ) +} + export function BigQueryIcon(props: SVGProps) { return ( @@ -6951,6 +6959,21 @@ export function SnowflakeIcon(props: SVGProps) { ) } +export function NewRelicIcon(props: SVGProps) { + return ( + + + + + ) +} + export function WizaIcon(props: SVGProps) { return ( diff --git a/apps/sim/tools/gong/aggregate_activity.ts b/apps/sim/tools/gong/aggregate_activity.ts index d0c3388b0e1..a962e00dc75 100644 --- a/apps/sim/tools/gong/aggregate_activity.ts +++ b/apps/sim/tools/gong/aggregate_activity.ts @@ -1,4 +1,5 @@ import type { GongAggregateActivityParams, GongAggregateActivityResponse } from '@/tools/gong/types' +import { getGongErrorMessage, parseGongIdList } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const aggregateActivityTool: ToolConfig< @@ -59,14 +60,13 @@ export const aggregateActivityTool: ToolConfig< }), body: (params) => { const filter: Record = { - fromDate: params.fromDate, - toDate: params.toDate, - } - if (params.userIds) { - filter.userIds = params.userIds.split(',').map((id) => id.trim()) + fromDate: params.fromDate.trim(), + toDate: params.toDate.trim(), } + const userIds = parseGongIdList(params.userIds) + if (userIds) filter.userIds = userIds const body: Record = { filter } - if (params.cursor) body.cursor = params.cursor + if (params.cursor?.trim()) body.cursor = params.cursor.trim() return body }, }, @@ -74,9 +74,7 @@ export const aggregateActivityTool: ToolConfig< transformResponse: async (response: Response) => { const data = await response.json() if (!response.ok) { - throw new Error( - data.errors?.[0]?.message || data.message || 'Failed to get aggregate activity' - ) + throw new Error(getGongErrorMessage(data, 'Failed to get aggregate activity')) } const usersActivity = (data.usersAggregateActivityStats ?? []).map( (ua: Record) => { diff --git a/apps/sim/tools/gong/answered_scorecards.ts b/apps/sim/tools/gong/answered_scorecards.ts index a37899477db..92e105fdc55 100644 --- a/apps/sim/tools/gong/answered_scorecards.ts +++ b/apps/sim/tools/gong/answered_scorecards.ts @@ -2,6 +2,7 @@ import type { GongAnsweredScorecardsParams, GongAnsweredScorecardsResponse, } from '@/tools/gong/types' +import { getGongErrorMessage, parseGongIdList } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const answeredScorecardsTool: ToolConfig< @@ -83,18 +84,16 @@ export const answeredScorecardsTool: ToolConfig< }), body: (params) => { const filter: Record = {} - if (params.callFromDate) filter.callFromDate = params.callFromDate - if (params.callToDate) filter.callToDate = params.callToDate - if (params.reviewFromDate) filter.reviewFromDate = params.reviewFromDate - if (params.reviewToDate) filter.reviewToDate = params.reviewToDate - if (params.scorecardIds) { - filter.scorecardIds = params.scorecardIds.split(',').map((id) => id.trim()) - } - if (params.reviewedUserIds) { - filter.reviewedUserIds = params.reviewedUserIds.split(',').map((id) => id.trim()) - } + if (params.callFromDate?.trim()) filter.callFromDate = params.callFromDate.trim() + if (params.callToDate?.trim()) filter.callToDate = params.callToDate.trim() + if (params.reviewFromDate?.trim()) filter.reviewFromDate = params.reviewFromDate.trim() + if (params.reviewToDate?.trim()) filter.reviewToDate = params.reviewToDate.trim() + const scorecardIds = parseGongIdList(params.scorecardIds) + const reviewedUserIds = parseGongIdList(params.reviewedUserIds) + if (scorecardIds) filter.scorecardIds = scorecardIds + if (reviewedUserIds) filter.reviewedUserIds = reviewedUserIds const body: Record = { filter } - if (params.cursor) body.cursor = params.cursor + if (params.cursor?.trim()) body.cursor = params.cursor.trim() return body }, }, @@ -102,9 +101,7 @@ export const answeredScorecardsTool: ToolConfig< transformResponse: async (response: Response) => { const data = await response.json() if (!response.ok) { - throw new Error( - data.errors?.[0]?.message || data.message || 'Failed to get answered scorecards' - ) + throw new Error(getGongErrorMessage(data, 'Failed to get answered scorecards')) } const answeredScorecards = (data.answeredScorecards ?? []).map( (sc: Record) => ({ diff --git a/apps/sim/tools/gong/create_call.ts b/apps/sim/tools/gong/create_call.ts new file mode 100644 index 00000000000..5a2c35f0858 --- /dev/null +++ b/apps/sim/tools/gong/create_call.ts @@ -0,0 +1,157 @@ +import type { GongCreateCallParams, GongCreateCallResponse } from '@/tools/gong/types' +import { getGongErrorMessage, parseGongJsonArray } from '@/tools/gong/utils' +import type { ToolConfig } from '@/tools/types' + +export const createCallTool: ToolConfig = { + id: 'gong_create_call', + name: 'Gong Create Call', + description: 'Upload call metadata to Gong and let Gong pull the media from a URL.', + version: '1.0.0', + + params: { + accessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Gong API Access Key', + }, + accessKeySecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Gong API Access Key Secret', + }, + clientUniqueId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique call ID from the source telephony or recording system', + }, + actualStart: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Actual call start time in ISO-8601 format', + }, + primaryUser: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: "Gong user ID for the call's host or owner", + }, + parties: { + type: 'json', + required: true, + visibility: 'user-or-llm', + description: 'Array of call parties, with at least the primary user included', + }, + direction: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Call direction: Inbound, Outbound, Conference, or Unknown', + }, + downloadMediaUrl: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'URL where Gong can download the call media file', + }, + title: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Human-readable call title', + }, + workspaceId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Optional Gong workspace ID', + }, + disposition: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Optional call disposition', + }, + purpose: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Optional call purpose', + }, + context: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'Optional CRM context array for the call', + }, + callProviderCode: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Optional conferencing or telephony provider code', + }, + }, + + request: { + url: 'https://api.gong.io/v2/calls', + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/json', + Authorization: `Basic ${btoa(`${params.accessKey}:${params.accessKeySecret}`)}`, + }), + body: (params) => { + const body: Record = { + clientUniqueId: params.clientUniqueId.trim(), + actualStart: params.actualStart.trim(), + primaryUser: params.primaryUser.trim(), + parties: parseGongJsonArray(params.parties, 'parties'), + direction: params.direction, + downloadMediaUrl: params.downloadMediaUrl.trim(), + } + + if (params.title?.trim()) body.title = params.title.trim() + if (params.workspaceId?.trim()) body.workspaceId = params.workspaceId.trim() + if (params.disposition?.trim()) body.disposition = params.disposition.trim() + if (params.purpose?.trim()) body.purpose = params.purpose.trim() + if (params.context) body.context = parseGongJsonArray(params.context, 'context') + if (params.callProviderCode?.trim()) body.callProviderCode = params.callProviderCode.trim() + + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(getGongErrorMessage(data, 'Failed to create Gong call')) + } + + return { + success: true, + output: { + callId: data.callId ?? '', + requestId: data.requestId ?? '', + url: data.url ?? null, + }, + } + }, + + outputs: { + callId: { + type: 'string', + description: "Gong's unique numeric identifier for the created call", + }, + requestId: { + type: 'string', + description: 'Gong request reference ID for troubleshooting', + }, + url: { + type: 'string', + description: 'URL to the created call in the Gong web app', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/gong/get_call.ts b/apps/sim/tools/gong/get_call.ts index d9595a049a2..35b31585248 100644 --- a/apps/sim/tools/gong/get_call.ts +++ b/apps/sim/tools/gong/get_call.ts @@ -1,4 +1,5 @@ import type { GongGetCallParams, GongGetCallResponse } from '@/tools/gong/types' +import { getGongErrorMessage } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const getCallTool: ToolConfig = { @@ -29,7 +30,7 @@ export const getCallTool: ToolConfig = { }, request: { - url: (params) => `https://api.gong.io/v2/calls/${params.callId}`, + url: (params) => `https://api.gong.io/v2/calls/${params.callId.trim()}`, method: 'GET', headers: (params) => ({ 'Content-Type': 'application/json', @@ -40,7 +41,7 @@ export const getCallTool: ToolConfig = { transformResponse: async (response: Response) => { const data = await response.json() if (!response.ok) { - throw new Error(data.errors?.[0]?.message || data.message || 'Failed to get call') + throw new Error(getGongErrorMessage(data, 'Failed to get call')) } const call = data.call ?? data return { diff --git a/apps/sim/tools/gong/get_call_transcript.ts b/apps/sim/tools/gong/get_call_transcript.ts index 31f327fd6be..ddd26bfb6e6 100644 --- a/apps/sim/tools/gong/get_call_transcript.ts +++ b/apps/sim/tools/gong/get_call_transcript.ts @@ -1,4 +1,5 @@ import type { GongGetCallTranscriptParams, GongGetCallTranscriptResponse } from '@/tools/gong/types' +import { getGongErrorMessage, parseGongIdList } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const getCallTranscriptTool: ToolConfig< @@ -64,14 +65,13 @@ export const getCallTranscriptTool: ToolConfig< }), body: (params) => { const filter: Record = {} - if (params.callIds) { - filter.callIds = params.callIds.split(',').map((id) => id.trim()) - } - if (params.fromDateTime) filter.fromDateTime = params.fromDateTime - if (params.toDateTime) filter.toDateTime = params.toDateTime - if (params.workspaceId) filter.workspaceId = params.workspaceId + const callIds = parseGongIdList(params.callIds) + if (callIds) filter.callIds = callIds + if (params.fromDateTime?.trim()) filter.fromDateTime = params.fromDateTime.trim() + if (params.toDateTime?.trim()) filter.toDateTime = params.toDateTime.trim() + if (params.workspaceId?.trim()) filter.workspaceId = params.workspaceId.trim() const body: Record = { filter } - if (params.cursor) body.cursor = params.cursor + if (params.cursor?.trim()) body.cursor = params.cursor.trim() return body }, }, @@ -79,7 +79,7 @@ export const getCallTranscriptTool: ToolConfig< transformResponse: async (response: Response) => { const data = await response.json() if (!response.ok) { - throw new Error(data.errors?.[0]?.message || data.message || 'Failed to get call transcript') + throw new Error(getGongErrorMessage(data, 'Failed to get call transcript')) } const callTranscripts = (data.callTranscripts ?? []).map((ct: Record) => ({ callId: ct.callId ?? '', diff --git a/apps/sim/tools/gong/get_coaching.ts b/apps/sim/tools/gong/get_coaching.ts index b16e3b28cef..21b0323131b 100644 --- a/apps/sim/tools/gong/get_coaching.ts +++ b/apps/sim/tools/gong/get_coaching.ts @@ -5,6 +5,7 @@ import type { GongGetCoachingParams, GongGetCoachingResponse, } from '@/tools/gong/types' +import { getGongErrorMessage } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const getCoachingTool: ToolConfig = { @@ -55,10 +56,10 @@ export const getCoachingTool: ToolConfig { const url = new URL('https://api.gong.io/v2/coaching') - url.searchParams.set('manager-id', params.managerId) - url.searchParams.set('workspace-id', params.workspaceId) - url.searchParams.set('from', params.fromDate) - url.searchParams.set('to', params.toDate) + url.searchParams.set('manager-id', params.managerId.trim()) + url.searchParams.set('workspace-id', params.workspaceId.trim()) + url.searchParams.set('from', params.fromDate.trim()) + url.searchParams.set('to', params.toDate.trim()) return url.toString() }, method: 'GET', @@ -71,7 +72,7 @@ export const getCoachingTool: ToolConfig { const data = await response.json() if (!response.ok) { - throw new Error(data.errors?.[0]?.message || data.message || 'Failed to get coaching metrics') + throw new Error(getGongErrorMessage(data, 'Failed to get coaching metrics')) } const mapUser = (u: Record | null | undefined): GongCoachingUser | null => { diff --git a/apps/sim/tools/gong/get_extensive_calls.ts b/apps/sim/tools/gong/get_extensive_calls.ts index 6a7c91b4315..aee5065e219 100644 --- a/apps/sim/tools/gong/get_extensive_calls.ts +++ b/apps/sim/tools/gong/get_extensive_calls.ts @@ -1,4 +1,5 @@ import type { GongGetExtensiveCallsParams, GongGetExtensiveCallsResponse } from '@/tools/gong/types' +import { getGongErrorMessage, parseGongIdList } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const getExtensiveCallsTool: ToolConfig< @@ -70,15 +71,13 @@ export const getExtensiveCallsTool: ToolConfig< }), body: (params) => { const filter: Record = {} - if (params.callIds) { - filter.callIds = params.callIds.split(',').map((id) => id.trim()) - } - if (params.fromDateTime) filter.fromDateTime = params.fromDateTime - if (params.toDateTime) filter.toDateTime = params.toDateTime - if (params.workspaceId) filter.workspaceId = params.workspaceId - if (params.primaryUserIds) { - filter.primaryUserIds = params.primaryUserIds.split(',').map((id) => id.trim()) - } + const callIds = parseGongIdList(params.callIds) + const primaryUserIds = parseGongIdList(params.primaryUserIds) + if (callIds) filter.callIds = callIds + if (params.fromDateTime?.trim()) filter.fromDateTime = params.fromDateTime.trim() + if (params.toDateTime?.trim()) filter.toDateTime = params.toDateTime.trim() + if (params.workspaceId?.trim()) filter.workspaceId = params.workspaceId.trim() + if (primaryUserIds) filter.primaryUserIds = primaryUserIds const body: Record = { filter, contentSelector: { @@ -102,7 +101,7 @@ export const getExtensiveCallsTool: ToolConfig< }, }, } - if (params.cursor) body.cursor = params.cursor + if (params.cursor?.trim()) body.cursor = params.cursor.trim() return body }, }, @@ -110,9 +109,7 @@ export const getExtensiveCallsTool: ToolConfig< transformResponse: async (response: Response) => { const data = await response.json() if (!response.ok) { - throw new Error( - data.errors?.[0]?.message || data.message || 'Failed to get extensive call data' - ) + throw new Error(getGongErrorMessage(data, 'Failed to get extensive call data')) } return { success: true, diff --git a/apps/sim/tools/gong/get_folder_content.ts b/apps/sim/tools/gong/get_folder_content.ts index 6704d7ce8e7..65a91da7843 100644 --- a/apps/sim/tools/gong/get_folder_content.ts +++ b/apps/sim/tools/gong/get_folder_content.ts @@ -1,4 +1,5 @@ import type { GongGetFolderContentParams, GongGetFolderContentResponse } from '@/tools/gong/types' +import { getGongErrorMessage } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const getFolderContentTool: ToolConfig< @@ -34,7 +35,7 @@ export const getFolderContentTool: ToolConfig< request: { url: (params) => { const url = new URL('https://api.gong.io/v2/library/folder-content') - url.searchParams.set('folderId', params.folderId) + url.searchParams.set('folderId', params.folderId.trim()) return url.toString() }, method: 'GET', @@ -47,7 +48,7 @@ export const getFolderContentTool: ToolConfig< transformResponse: async (response: Response) => { const data = await response.json() if (!response.ok) { - throw new Error(data.errors?.[0]?.message || data.message || 'Failed to get folder content') + throw new Error(getGongErrorMessage(data, 'Failed to get folder content')) } const calls = (data.calls ?? []).map((c: Record) => ({ id: (c.id as string) ?? '', diff --git a/apps/sim/tools/gong/get_user.ts b/apps/sim/tools/gong/get_user.ts index a8b071389fe..6c5be146d69 100644 --- a/apps/sim/tools/gong/get_user.ts +++ b/apps/sim/tools/gong/get_user.ts @@ -1,4 +1,5 @@ import type { GongGetUserParams, GongGetUserResponse } from '@/tools/gong/types' +import { getGongErrorMessage } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const getUserTool: ToolConfig = { @@ -29,7 +30,7 @@ export const getUserTool: ToolConfig = { }, request: { - url: (params) => `https://api.gong.io/v2/users/${params.userId}`, + url: (params) => `https://api.gong.io/v2/users/${params.userId.trim()}`, method: 'GET', headers: (params) => ({ 'Content-Type': 'application/json', @@ -40,7 +41,7 @@ export const getUserTool: ToolConfig = { transformResponse: async (response: Response) => { const data = await response.json() if (!response.ok) { - throw new Error(data.errors?.[0]?.message || data.message || 'Failed to get user') + throw new Error(getGongErrorMessage(data, 'Failed to get user')) } const user = data.user ?? data return { diff --git a/apps/sim/tools/gong/index.ts b/apps/sim/tools/gong/index.ts index 89c6b80e8c5..c838bd8e075 100644 --- a/apps/sim/tools/gong/index.ts +++ b/apps/sim/tools/gong/index.ts @@ -1,5 +1,6 @@ import { aggregateActivityTool } from '@/tools/gong/aggregate_activity' import { answeredScorecardsTool } from '@/tools/gong/answered_scorecards' +import { createCallTool } from '@/tools/gong/create_call' import { getCallTool } from '@/tools/gong/get_call' import { getCallTranscriptTool } from '@/tools/gong/get_call_transcript' import { getCoachingTool } from '@/tools/gong/get_coaching' @@ -18,6 +19,7 @@ import { lookupEmailTool } from '@/tools/gong/lookup_email' import { lookupPhoneTool } from '@/tools/gong/lookup_phone' export const gongListCallsTool = listCallsTool +export const gongCreateCallTool = createCallTool export const gongGetCallTool = getCallTool export const gongGetCallTranscriptTool = getCallTranscriptTool export const gongGetExtensiveCallsTool = getExtensiveCallsTool @@ -35,3 +37,5 @@ export const gongListFlowsTool = listFlowsTool export const gongGetCoachingTool = getCoachingTool export const gongLookupEmailTool = lookupEmailTool export const gongLookupPhoneTool = lookupPhoneTool + +export * from './types' diff --git a/apps/sim/tools/gong/interaction_stats.ts b/apps/sim/tools/gong/interaction_stats.ts index 6ec3cdca4ff..d8b16709174 100644 --- a/apps/sim/tools/gong/interaction_stats.ts +++ b/apps/sim/tools/gong/interaction_stats.ts @@ -1,4 +1,5 @@ import type { GongInteractionStatsParams, GongInteractionStatsResponse } from '@/tools/gong/types' +import { getGongErrorMessage, parseGongIdList } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const interactionStatsTool: ToolConfig< @@ -60,14 +61,13 @@ export const interactionStatsTool: ToolConfig< }), body: (params) => { const filter: Record = { - fromDate: params.fromDate, - toDate: params.toDate, - } - if (params.userIds) { - filter.userIds = params.userIds.split(',').map((id) => id.trim()) + fromDate: params.fromDate.trim(), + toDate: params.toDate.trim(), } + const userIds = parseGongIdList(params.userIds) + if (userIds) filter.userIds = userIds const body: Record = { filter } - if (params.cursor) body.cursor = params.cursor + if (params.cursor?.trim()) body.cursor = params.cursor.trim() return body }, }, @@ -75,9 +75,7 @@ export const interactionStatsTool: ToolConfig< transformResponse: async (response: Response) => { const data = await response.json() if (!response.ok) { - throw new Error( - data.errors?.[0]?.message || data.message || 'Failed to get interaction stats' - ) + throw new Error(getGongErrorMessage(data, 'Failed to get interaction stats')) } const peopleInteractionStats = (data.peopleInteractionStats ?? []).map( (entry: Record) => ({ diff --git a/apps/sim/tools/gong/list_calls.ts b/apps/sim/tools/gong/list_calls.ts index 3ef67dee7bd..655bed2966d 100644 --- a/apps/sim/tools/gong/list_calls.ts +++ b/apps/sim/tools/gong/list_calls.ts @@ -1,4 +1,5 @@ import type { GongListCallsParams, GongListCallsResponse } from '@/tools/gong/types' +import { getGongErrorMessage } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const listCallsTool: ToolConfig = { @@ -31,7 +32,7 @@ export const listCallsTool: ToolConfig { const url = new URL('https://api.gong.io/v2/calls') - url.searchParams.set('fromDateTime', params.fromDateTime) - if (params.toDateTime) url.searchParams.set('toDateTime', params.toDateTime) - if (params.cursor) url.searchParams.set('cursor', params.cursor) - if (params.workspaceId) url.searchParams.set('workspaceId', params.workspaceId) + url.searchParams.set('fromDateTime', params.fromDateTime.trim()) + url.searchParams.set('toDateTime', params.toDateTime?.trim() || new Date().toISOString()) + if (params.cursor?.trim()) url.searchParams.set('cursor', params.cursor.trim()) + if (params.workspaceId?.trim()) url.searchParams.set('workspaceId', params.workspaceId.trim()) return url.toString() }, method: 'GET', @@ -66,7 +67,7 @@ export const listCallsTool: ToolConfig { const data = await response.json() if (!response.ok) { - throw new Error(data.errors?.[0]?.message || data.message || 'Failed to list calls') + throw new Error(getGongErrorMessage(data, 'Failed to list calls')) } const calls = (data.calls ?? []).map((call: Record) => ({ id: call.id ?? '', @@ -93,14 +94,22 @@ export const listCallsTool: ToolConfig = { @@ -45,9 +46,9 @@ export const listFlowsTool: ToolConfig { const url = new URL('https://api.gong.io/v2/flows') - url.searchParams.set('flowOwnerEmail', params.flowOwnerEmail) - if (params.workspaceId) url.searchParams.set('workspaceId', params.workspaceId) - if (params.cursor) url.searchParams.set('cursor', params.cursor) + url.searchParams.set('flowEmailOwner', params.flowOwnerEmail.trim()) + if (params.workspaceId) url.searchParams.set('workspaceId', params.workspaceId.trim()) + if (params.cursor?.trim()) url.searchParams.set('cursor', params.cursor.trim()) return url.toString() }, method: 'GET', @@ -60,7 +61,7 @@ export const listFlowsTool: ToolConfig { const data = await response.json() if (!response.ok) { - throw new Error(data.errors?.[0]?.message || data.message || 'Failed to list flows') + throw new Error(getGongErrorMessage(data, 'Failed to list flows')) } const flows = (data.flows ?? []).map((f: Record) => ({ id: f.id ?? '', diff --git a/apps/sim/tools/gong/list_library_folders.ts b/apps/sim/tools/gong/list_library_folders.ts index d947a4f78ba..e3d77b9a7a5 100644 --- a/apps/sim/tools/gong/list_library_folders.ts +++ b/apps/sim/tools/gong/list_library_folders.ts @@ -2,6 +2,7 @@ import type { GongListLibraryFoldersParams, GongListLibraryFoldersResponse, } from '@/tools/gong/types' +import { getGongErrorMessage } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const listLibraryFoldersTool: ToolConfig< @@ -37,7 +38,7 @@ export const listLibraryFoldersTool: ToolConfig< request: { url: (params) => { const url = new URL('https://api.gong.io/v2/library/folders') - if (params.workspaceId) url.searchParams.set('workspaceId', params.workspaceId) + if (params.workspaceId?.trim()) url.searchParams.set('workspaceId', params.workspaceId.trim()) return url.toString() }, method: 'GET', @@ -50,7 +51,7 @@ export const listLibraryFoldersTool: ToolConfig< transformResponse: async (response: Response) => { const data = await response.json() if (!response.ok) { - throw new Error(data.errors?.[0]?.message || data.message || 'Failed to list library folders') + throw new Error(getGongErrorMessage(data, 'Failed to list library folders')) } const folders = (data.folders ?? []).map((f: Record) => ({ id: f.id ?? '', diff --git a/apps/sim/tools/gong/list_scorecards.ts b/apps/sim/tools/gong/list_scorecards.ts index 742b520ced9..c08fb1a018a 100644 --- a/apps/sim/tools/gong/list_scorecards.ts +++ b/apps/sim/tools/gong/list_scorecards.ts @@ -1,4 +1,5 @@ import type { GongListScorecardsParams, GongListScorecardsResponse } from '@/tools/gong/types' +import { getGongErrorMessage } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const listScorecardsTool: ToolConfig = @@ -35,7 +36,7 @@ export const listScorecardsTool: ToolConfig { const data = await response.json() if (!response.ok) { - throw new Error(data.errors?.[0]?.message || data.message || 'Failed to list scorecards') + throw new Error(getGongErrorMessage(data, 'Failed to list scorecards')) } const scorecards = (data.scorecards ?? []).map((sc: Record) => ({ scorecardId: sc.scorecardId ?? '', diff --git a/apps/sim/tools/gong/list_trackers.ts b/apps/sim/tools/gong/list_trackers.ts index 2c54e3eea84..ff0d9055fa9 100644 --- a/apps/sim/tools/gong/list_trackers.ts +++ b/apps/sim/tools/gong/list_trackers.ts @@ -1,4 +1,5 @@ import type { GongListTrackersParams, GongListTrackersResponse } from '@/tools/gong/types' +import { getGongErrorMessage } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const listTrackersTool: ToolConfig = { @@ -32,7 +33,7 @@ export const listTrackersTool: ToolConfig { const url = new URL('https://api.gong.io/v2/settings/trackers') - if (params.workspaceId) url.searchParams.set('workspaceId', params.workspaceId) + if (params.workspaceId?.trim()) url.searchParams.set('workspaceId', params.workspaceId.trim()) return url.toString() }, method: 'GET', @@ -45,7 +46,7 @@ export const listTrackersTool: ToolConfig { const data = await response.json() if (!response.ok) { - throw new Error(data.errors?.[0]?.message || data.message || 'Failed to list trackers') + throw new Error(getGongErrorMessage(data, 'Failed to list trackers')) } const trackers = (data.keywordTrackers ?? []).map((t: Record) => ({ trackerId: t.trackerId ?? '', diff --git a/apps/sim/tools/gong/list_users.ts b/apps/sim/tools/gong/list_users.ts index 54ac8303934..b829075eacc 100644 --- a/apps/sim/tools/gong/list_users.ts +++ b/apps/sim/tools/gong/list_users.ts @@ -1,4 +1,5 @@ import type { GongListUsersParams, GongListUsersResponse } from '@/tools/gong/types' +import { getGongErrorMessage } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const listUsersTool: ToolConfig = { @@ -37,7 +38,7 @@ export const listUsersTool: ToolConfig { const url = new URL('https://api.gong.io/v2/users') - if (params.cursor) url.searchParams.set('cursor', params.cursor) + if (params.cursor?.trim()) url.searchParams.set('cursor', params.cursor.trim()) if (params.includeAvatars) url.searchParams.set('includeAvatars', params.includeAvatars) return url.toString() }, @@ -51,7 +52,7 @@ export const listUsersTool: ToolConfig { const data = await response.json() if (!response.ok) { - throw new Error(data.errors?.[0]?.message || data.message || 'Failed to list users') + throw new Error(getGongErrorMessage(data, 'Failed to list users')) } const users = (data.users ?? []).map((user: Record) => ({ id: user.id ?? '', @@ -74,6 +75,7 @@ export const listUsersTool: ToolConfig = @@ -35,7 +36,7 @@ export const listWorkspacesTool: ToolConfig { const data = await response.json() if (!response.ok) { - throw new Error(data.errors?.[0]?.message || data.message || 'Failed to list workspaces') + throw new Error(getGongErrorMessage(data, 'Failed to list workspaces')) } const workspaces = (data.workspaces ?? []).map((w: Record) => ({ id: w.id ?? '', diff --git a/apps/sim/tools/gong/lookup_email.ts b/apps/sim/tools/gong/lookup_email.ts index 16e9376830f..57076d4df1f 100644 --- a/apps/sim/tools/gong/lookup_email.ts +++ b/apps/sim/tools/gong/lookup_email.ts @@ -1,4 +1,5 @@ import type { GongLookupEmailParams, GongLookupEmailResponse } from '@/tools/gong/types' +import { getGongErrorMessage } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const lookupEmailTool: ToolConfig = { @@ -32,7 +33,7 @@ export const lookupEmailTool: ToolConfig { const url = new URL('https://api.gong.io/v2/data-privacy/data-for-email-address') - url.searchParams.set('emailAddress', params.emailAddress) + url.searchParams.set('emailAddress', params.emailAddress.trim()) return url.toString() }, method: 'GET', @@ -45,7 +46,7 @@ export const lookupEmailTool: ToolConfig { const data = await response.json() if (!response.ok) { - throw new Error(data.errors?.[0]?.message || data.message || 'Failed to lookup email address') + throw new Error(getGongErrorMessage(data, 'Failed to lookup email address')) } return { success: true, diff --git a/apps/sim/tools/gong/lookup_phone.ts b/apps/sim/tools/gong/lookup_phone.ts index fd5c214de24..9101d40b9dd 100644 --- a/apps/sim/tools/gong/lookup_phone.ts +++ b/apps/sim/tools/gong/lookup_phone.ts @@ -1,4 +1,5 @@ import type { GongLookupPhoneParams, GongLookupPhoneResponse } from '@/tools/gong/types' +import { getGongErrorMessage } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const lookupPhoneTool: ToolConfig = { @@ -32,7 +33,7 @@ export const lookupPhoneTool: ToolConfig { const url = new URL('https://api.gong.io/v2/data-privacy/data-for-phone-number') - url.searchParams.set('phoneNumber', params.phoneNumber) + url.searchParams.set('phoneNumber', params.phoneNumber.trim()) return url.toString() }, method: 'GET', @@ -45,7 +46,7 @@ export const lookupPhoneTool: ToolConfig { const data = await response.json() if (!response.ok) { - throw new Error(data.errors?.[0]?.message || data.message || 'Failed to lookup phone number') + throw new Error(getGongErrorMessage(data, 'Failed to lookup phone number')) } return { success: true, diff --git a/apps/sim/tools/gong/types.ts b/apps/sim/tools/gong/types.ts index 71d6a92da5c..55d7c9b3c94 100644 --- a/apps/sim/tools/gong/types.ts +++ b/apps/sim/tools/gong/types.ts @@ -14,6 +14,30 @@ export interface GongListCallsParams extends GongBaseParams { workspaceId?: string } +/** Create Call */ +export interface GongCreateCallParams extends GongBaseParams { + clientUniqueId: string + actualStart: string + primaryUser: string + parties: unknown + direction: string + downloadMediaUrl: string + title?: string + workspaceId?: string + disposition?: string + purpose?: string + context?: unknown + callProviderCode?: string +} + +export interface GongCreateCallResponse extends ToolResponse { + output: { + callId: string + requestId: string + url: string | null + } +} + interface GongCallBasic { id: string title: string | null @@ -52,9 +76,12 @@ interface GongParty { export interface GongListCallsResponse extends ToolResponse { output: { + requestId: string | null calls: GongCallBasic[] cursor: string | null totalRecords: number + currentPageSize: number | null + currentPageNumber: number | null } } @@ -168,6 +195,7 @@ interface GongUser { export interface GongListUsersResponse extends ToolResponse { output: { + requestId: string | null users: GongUser[] cursor: string | null totalRecords: number | null @@ -585,6 +613,7 @@ export interface GongLookupPhoneResponse extends ToolResponse { /** Union type for all Gong responses */ export type GongResponse = | GongListCallsResponse + | GongCreateCallResponse | GongGetCallResponse | GongGetCallTranscriptResponse | GongGetExtensiveCallsResponse diff --git a/apps/sim/tools/gong/utils.ts b/apps/sim/tools/gong/utils.ts new file mode 100644 index 00000000000..06c725e5504 --- /dev/null +++ b/apps/sim/tools/gong/utils.ts @@ -0,0 +1,68 @@ +/** + * Extract a useful Gong API error message from documented error payloads. + */ +export function getGongErrorMessage(data: unknown, fallback: string): string { + if (!data || typeof data !== 'object') { + return fallback + } + + const payload = data as Record + const firstError = Array.isArray(payload.errors) ? payload.errors[0] : undefined + if (typeof firstError === 'string' && firstError.trim()) { + return firstError + } + if (firstError && typeof firstError === 'object') { + const message = (firstError as Record).message + if (typeof message === 'string' && message.trim()) { + return message + } + } + if (typeof payload.message === 'string' && payload.message.trim()) { + return payload.message + } + + return fallback +} + +/** + * Normalize comma-separated Gong IDs from block text inputs. + */ +export function parseGongIdList(value?: string): string[] | undefined { + if (!value) { + return undefined + } + + const ids = value + .split(',') + .map((id) => id.trim()) + .filter(Boolean) + + return ids.length > 0 ? ids : undefined +} + +/** + * Parse a JSON object or array field that may already be resolved to structured data. + */ +export function parseGongJsonField(value: unknown, fieldName: string): T { + if (typeof value === 'string') { + try { + return JSON.parse(value) as T + } catch { + throw new Error(`${fieldName} must be valid JSON`) + } + } + + return value as T +} + +/** + * Parse and validate a JSON array field. + */ +export function parseGongJsonArray(value: unknown, fieldName: string): unknown[] { + const parsed = parseGongJsonField(value, fieldName) + if (!Array.isArray(parsed)) { + throw new Error(`${fieldName} must be a JSON array`) + } + + return parsed +} diff --git a/apps/sim/tools/incidentio/actions_list.ts b/apps/sim/tools/incidentio/actions_list.ts index 35e2fe02c8c..5c81c556647 100644 --- a/apps/sim/tools/incidentio/actions_list.ts +++ b/apps/sim/tools/incidentio/actions_list.ts @@ -26,6 +26,13 @@ export const actionsListTool: ToolConfig< visibility: 'user-or-llm', description: 'Filter actions by incident ID (e.g., "01FCNDV6P870EA6S7TK1DSYDG0")', }, + incident_mode: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Filter actions by incident mode (standard, retrospective, test, tutorial, or stream)', + }, }, request: { @@ -33,7 +40,11 @@ export const actionsListTool: ToolConfig< const url = new URL('https://api.incident.io/v2/actions') if (params.incident_id) { - url.searchParams.append('incident_id', params.incident_id) + url.searchParams.append('incident_id', params.incident_id.trim()) + } + + if (params.incident_mode) { + url.searchParams.append('incident_mode', params.incident_mode) } return url.toString() diff --git a/apps/sim/tools/incidentio/actions_show.ts b/apps/sim/tools/incidentio/actions_show.ts index d18e1ce83f5..04d506bfb28 100644 --- a/apps/sim/tools/incidentio/actions_show.ts +++ b/apps/sim/tools/incidentio/actions_show.ts @@ -29,7 +29,7 @@ export const actionsShowTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/actions/${params.id}`, + url: (params) => `https://api.incident.io/v2/actions/${params.id.trim()}`, method: 'GET', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/custom_fields_delete.ts b/apps/sim/tools/incidentio/custom_fields_delete.ts index 3e2dad99579..cf022423eb1 100644 --- a/apps/sim/tools/incidentio/custom_fields_delete.ts +++ b/apps/sim/tools/incidentio/custom_fields_delete.ts @@ -26,7 +26,7 @@ export const customFieldsDeleteTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/custom_fields/${params.id}`, + url: (params) => `https://api.incident.io/v2/custom_fields/${params.id.trim()}`, method: 'DELETE', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/custom_fields_show.ts b/apps/sim/tools/incidentio/custom_fields_show.ts index ec96b021e35..c0589dec580 100644 --- a/apps/sim/tools/incidentio/custom_fields_show.ts +++ b/apps/sim/tools/incidentio/custom_fields_show.ts @@ -23,7 +23,7 @@ export const customFieldsShowTool: ToolConfig `https://api.incident.io/v2/custom_fields/${params.id}`, + url: (params) => `https://api.incident.io/v2/custom_fields/${params.id.trim()}`, method: 'GET', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/custom_fields_update.ts b/apps/sim/tools/incidentio/custom_fields_update.ts index f8d8410b62a..de1d5151ae3 100644 --- a/apps/sim/tools/incidentio/custom_fields_update.ts +++ b/apps/sim/tools/incidentio/custom_fields_update.ts @@ -38,7 +38,7 @@ export const customFieldsUpdateTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/custom_fields/${params.id}`, + url: (params) => `https://api.incident.io/v2/custom_fields/${params.id.trim()}`, method: 'PUT', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/escalation_paths_create.ts b/apps/sim/tools/incidentio/escalation_paths_create.ts index 8794bdd033b..b4a4ee48a8d 100644 --- a/apps/sim/tools/incidentio/escalation_paths_create.ts +++ b/apps/sim/tools/incidentio/escalation_paths_create.ts @@ -49,7 +49,7 @@ export const escalationPathsCreateTool: ToolConfig< Authorization: `Bearer ${params.apiKey}`, }), body: (params) => { - const body: Record = { + const body: Record = { name: params.name, path: params.path, } @@ -128,8 +128,6 @@ export const escalationPathsCreateTool: ToolConfig< }, }, }, - created_at: { type: 'string', description: 'When the path was created' }, - updated_at: { type: 'string', description: 'When the path was last updated' }, }, }, }, diff --git a/apps/sim/tools/incidentio/escalation_paths_delete.ts b/apps/sim/tools/incidentio/escalation_paths_delete.ts index 1bb73935e0a..ba70fa8f294 100644 --- a/apps/sim/tools/incidentio/escalation_paths_delete.ts +++ b/apps/sim/tools/incidentio/escalation_paths_delete.ts @@ -29,7 +29,7 @@ export const escalationPathsDeleteTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/escalation_paths/${params.id}`, + url: (params) => `https://api.incident.io/v2/escalation_paths/${params.id.trim()}`, method: 'DELETE', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/escalation_paths_list.ts b/apps/sim/tools/incidentio/escalation_paths_list.ts new file mode 100644 index 00000000000..e92d78b4c72 --- /dev/null +++ b/apps/sim/tools/incidentio/escalation_paths_list.ts @@ -0,0 +1,96 @@ +import type { + IncidentioEscalationPathsListParams, + IncidentioEscalationPathsListResponse, +} from '@/tools/incidentio/types' +import type { ToolConfig } from '@/tools/types' + +export const escalationPathsListTool: ToolConfig< + IncidentioEscalationPathsListParams, + IncidentioEscalationPathsListResponse +> = { + id: 'incidentio_escalation_paths_list', + name: 'List Escalation Paths', + description: 'List escalation paths in incident.io', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'incident.io API Key', + }, + page_size: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of escalation paths to return per page', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor to fetch the next page of results', + }, + }, + + request: { + url: (params) => { + const url = new URL('https://api.incident.io/v2/escalation_paths') + if (params.page_size) url.searchParams.set('page_size', params.page_size.toString()) + if (params.after) url.searchParams.set('after', params.after) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + escalation_paths: data.escalation_paths ?? [], + pagination_meta: data.pagination_meta + ? { + after: data.pagination_meta.after, + page_size: data.pagination_meta.page_size, + } + : undefined, + }, + } + }, + + outputs: { + escalation_paths: { + type: 'array', + description: 'List of escalation paths', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'The escalation path ID' }, + name: { type: 'string', description: 'The escalation path name' }, + path: { type: 'array', description: 'Array of escalation levels' }, + working_hours: { + type: 'array', + description: 'Working hours configuration', + optional: true, + }, + }, + }, + }, + pagination_meta: { + type: 'object', + description: 'Pagination metadata', + optional: true, + properties: { + after: { type: 'string', description: 'Cursor for next page', optional: true }, + page_size: { type: 'number', description: 'Number of results per page' }, + }, + }, + }, +} diff --git a/apps/sim/tools/incidentio/escalation_paths_show.ts b/apps/sim/tools/incidentio/escalation_paths_show.ts index 5b188f56a49..ffb9b887d9e 100644 --- a/apps/sim/tools/incidentio/escalation_paths_show.ts +++ b/apps/sim/tools/incidentio/escalation_paths_show.ts @@ -29,7 +29,7 @@ export const escalationPathsShowTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/escalation_paths/${params.id}`, + url: (params) => `https://api.incident.io/v2/escalation_paths/${params.id.trim()}`, method: 'GET', headers: (params) => ({ 'Content-Type': 'application/json', @@ -103,8 +103,6 @@ export const escalationPathsShowTool: ToolConfig< }, }, }, - created_at: { type: 'string', description: 'When the path was created' }, - updated_at: { type: 'string', description: 'When the path was last updated' }, }, }, }, diff --git a/apps/sim/tools/incidentio/escalation_paths_update.ts b/apps/sim/tools/incidentio/escalation_paths_update.ts index f1829ed5934..c4a27972ee5 100644 --- a/apps/sim/tools/incidentio/escalation_paths_update.ts +++ b/apps/sim/tools/incidentio/escalation_paths_update.ts @@ -28,13 +28,13 @@ export const escalationPathsUpdateTool: ToolConfig< }, name: { type: 'string', - required: false, + required: true, visibility: 'user-or-llm', description: 'New name for the escalation path (e.g., "Critical Incident Path")', }, path: { type: 'json', - required: false, + required: true, visibility: 'user-or-llm', description: 'New escalation path configuration. Array of escalation levels with targets and time_to_ack_seconds', @@ -48,21 +48,16 @@ export const escalationPathsUpdateTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/escalation_paths/${params.id}`, + url: (params) => `https://api.incident.io/v2/escalation_paths/${params.id.trim()}`, method: 'PUT', headers: (params) => ({ 'Content-Type': 'application/json', Authorization: `Bearer ${params.apiKey}`, }), body: (params) => { - const body: Record = {} - - if (params.name !== undefined) { - body.name = params.name - } - - if (params.path !== undefined) { - body.path = params.path + const body: Record = { + name: params.name, + path: params.path, } if (params.working_hours !== undefined) { @@ -139,8 +134,6 @@ export const escalationPathsUpdateTool: ToolConfig< }, }, }, - created_at: { type: 'string', description: 'When the path was created' }, - updated_at: { type: 'string', description: 'When the path was last updated' }, }, }, }, diff --git a/apps/sim/tools/incidentio/escalations_list.ts b/apps/sim/tools/incidentio/escalations_list.ts index 5d5f76f7b90..01da43ecbee 100644 --- a/apps/sim/tools/incidentio/escalations_list.ts +++ b/apps/sim/tools/incidentio/escalations_list.ts @@ -20,10 +20,27 @@ export const escalationsListTool: ToolConfig< visibility: 'user-only', description: 'incident.io API Key', }, + page_size: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of escalations to return per page', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor to fetch the next page of results', + }, }, request: { - url: () => 'https://api.incident.io/v2/escalations', + url: (params) => { + const url = new URL('https://api.incident.io/v2/escalations') + if (params.page_size) url.searchParams.set('page_size', params.page_size.toString()) + if (params.after) url.searchParams.set('after', params.after) + return url.toString() + }, method: 'GET', headers: (params) => ({ 'Content-Type': 'application/json', @@ -38,6 +55,12 @@ export const escalationsListTool: ToolConfig< success: true, output: { escalations: data.escalations || [], + pagination_meta: data.pagination_meta + ? { + after: data.pagination_meta.after, + page_size: data.pagination_meta.page_size, + } + : undefined, }, } }, @@ -59,5 +82,14 @@ export const escalationsListTool: ToolConfig< }, }, }, + pagination_meta: { + type: 'object', + description: 'Pagination metadata', + optional: true, + properties: { + after: { type: 'string', description: 'Cursor for next page', optional: true }, + page_size: { type: 'number', description: 'Number of results per page' }, + }, + }, }, } diff --git a/apps/sim/tools/incidentio/escalations_show.ts b/apps/sim/tools/incidentio/escalations_show.ts index 8606de600cf..469f01ce4eb 100644 --- a/apps/sim/tools/incidentio/escalations_show.ts +++ b/apps/sim/tools/incidentio/escalations_show.ts @@ -29,7 +29,7 @@ export const escalationsShowTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/escalations/${params.id}`, + url: (params) => `https://api.incident.io/v2/escalations/${params.id.trim()}`, method: 'GET', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/follow_ups_list.ts b/apps/sim/tools/incidentio/follow_ups_list.ts index 603547b43e0..9fd33864779 100644 --- a/apps/sim/tools/incidentio/follow_ups_list.ts +++ b/apps/sim/tools/incidentio/follow_ups_list.ts @@ -26,6 +26,13 @@ export const followUpsListTool: ToolConfig< visibility: 'user-or-llm', description: 'Filter follow-ups by incident ID (e.g., "01FCNDV6P870EA6S7TK1DSYDG0")', }, + incident_mode: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Filter follow-ups by incident mode (standard, retrospective, test, tutorial, or stream)', + }, }, request: { @@ -33,7 +40,11 @@ export const followUpsListTool: ToolConfig< const url = new URL('https://api.incident.io/v2/follow_ups') if (params.incident_id) { - url.searchParams.append('incident_id', params.incident_id) + url.searchParams.append('incident_id', params.incident_id.trim()) + } + + if (params.incident_mode) { + url.searchParams.append('incident_mode', params.incident_mode) } return url.toString() diff --git a/apps/sim/tools/incidentio/follow_ups_show.ts b/apps/sim/tools/incidentio/follow_ups_show.ts index 4dc62d1042a..495be6d7b4e 100644 --- a/apps/sim/tools/incidentio/follow_ups_show.ts +++ b/apps/sim/tools/incidentio/follow_ups_show.ts @@ -29,7 +29,7 @@ export const followUpsShowTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/follow_ups/${params.id}`, + url: (params) => `https://api.incident.io/v2/follow_ups/${params.id.trim()}`, method: 'GET', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/incident_roles_delete.ts b/apps/sim/tools/incidentio/incident_roles_delete.ts index 93472c6f95b..c28c9ae5796 100644 --- a/apps/sim/tools/incidentio/incident_roles_delete.ts +++ b/apps/sim/tools/incidentio/incident_roles_delete.ts @@ -29,7 +29,7 @@ export const incidentRolesDeleteTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/incident_roles/${params.id}`, + url: (params) => `https://api.incident.io/v2/incident_roles/${params.id.trim()}`, method: 'DELETE', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/incident_roles_show.ts b/apps/sim/tools/incidentio/incident_roles_show.ts index 7e4b5813362..97d4355ed22 100644 --- a/apps/sim/tools/incidentio/incident_roles_show.ts +++ b/apps/sim/tools/incidentio/incident_roles_show.ts @@ -29,7 +29,7 @@ export const incidentRolesShowTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/incident_roles/${params.id}`, + url: (params) => `https://api.incident.io/v2/incident_roles/${params.id.trim()}`, method: 'GET', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/incident_roles_update.ts b/apps/sim/tools/incidentio/incident_roles_update.ts index a27bfca02ad..ca325f713a9 100644 --- a/apps/sim/tools/incidentio/incident_roles_update.ts +++ b/apps/sim/tools/incidentio/incident_roles_update.ts @@ -53,7 +53,7 @@ export const incidentRolesUpdateTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/incident_roles/${params.id}`, + url: (params) => `https://api.incident.io/v2/incident_roles/${params.id.trim()}`, method: 'PUT', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/incident_timestamps_show.ts b/apps/sim/tools/incidentio/incident_timestamps_show.ts index 2640c4c126a..b2425d3103e 100644 --- a/apps/sim/tools/incidentio/incident_timestamps_show.ts +++ b/apps/sim/tools/incidentio/incident_timestamps_show.ts @@ -29,7 +29,7 @@ export const incidentTimestampsShowTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/incident_timestamps/${params.id}`, + url: (params) => `https://api.incident.io/v2/incident_timestamps/${params.id.trim()}`, method: 'GET', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/incident_updates_list.ts b/apps/sim/tools/incidentio/incident_updates_list.ts index 2501f33bdba..2d738b7221f 100644 --- a/apps/sim/tools/incidentio/incident_updates_list.ts +++ b/apps/sim/tools/incidentio/incident_updates_list.ts @@ -43,22 +43,21 @@ export const incidentUpdatesListTool: ToolConfig< request: { url: (params) => { - const queryParams: string[] = [] + const url = new URL('https://api.incident.io/v2/incident_updates') if (params.incident_id) { - queryParams.push(`incident_id=${params.incident_id}`) + url.searchParams.set('incident_id', params.incident_id.trim()) } if (params.page_size) { - queryParams.push(`page_size=${params.page_size}`) + url.searchParams.set('page_size', params.page_size.toString()) } if (params.after) { - queryParams.push(`after=${params.after}`) + url.searchParams.set('after', params.after.trim()) } - const queryString = queryParams.length > 0 ? `?${queryParams.join('&')}` : '' - return `https://api.incident.io/v2/incident_updates${queryString}` + return url.toString() }, method: 'GET', headers: (params) => ({ diff --git a/apps/sim/tools/incidentio/incidents_list.ts b/apps/sim/tools/incidentio/incidents_list.ts index 85a076c40f2..f27907db675 100644 --- a/apps/sim/tools/incidentio/incidents_list.ts +++ b/apps/sim/tools/incidentio/incidents_list.ts @@ -38,6 +38,18 @@ export const incidentsListTool: ToolConfig< description: 'Pagination cursor to fetch the next page of results (e.g., "01FCNDV6P870EA6S7TK1DSYDG0")', }, + sort_by: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort order for incidents: created_at_newest_first or created_at_oldest_first', + }, + filter_mode: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'How to combine filters: all or any', + }, }, request: { @@ -49,7 +61,15 @@ export const incidentsListTool: ToolConfig< } if (params.after) { - url.searchParams.append('after', params.after) + url.searchParams.append('after', params.after.trim()) + } + + if (params.sort_by) { + url.searchParams.append('sort_by', params.sort_by) + } + + if (params.filter_mode) { + url.searchParams.append('filter_mode', params.filter_mode) } return url.toString() diff --git a/apps/sim/tools/incidentio/incidents_show.ts b/apps/sim/tools/incidentio/incidents_show.ts index 8996fa50f3b..3421d12ec5b 100644 --- a/apps/sim/tools/incidentio/incidents_show.ts +++ b/apps/sim/tools/incidentio/incidents_show.ts @@ -30,7 +30,7 @@ export const incidentsShowTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/incidents/${params.id}`, + url: (params) => `https://api.incident.io/v2/incidents/${params.id.trim()}`, method: 'GET', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/incidents_update.ts b/apps/sim/tools/incidentio/incidents_update.ts index 465bea2eb78..15e8a881a0f 100644 --- a/apps/sim/tools/incidentio/incidents_update.ts +++ b/apps/sim/tools/incidentio/incidents_update.ts @@ -67,7 +67,7 @@ export const incidentsUpdateTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/incidents/${params.id}/actions/edit`, + url: (params) => `https://api.incident.io/v2/incidents/${params.id.trim()}/actions/edit`, method: 'POST', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/index.ts b/apps/sim/tools/incidentio/index.ts index ae4470e7c90..91faa4b09bd 100644 --- a/apps/sim/tools/incidentio/index.ts +++ b/apps/sim/tools/incidentio/index.ts @@ -7,6 +7,7 @@ import { customFieldsShowTool } from '@/tools/incidentio/custom_fields_show' import { customFieldsUpdateTool } from '@/tools/incidentio/custom_fields_update' import { escalationPathsCreateTool } from '@/tools/incidentio/escalation_paths_create' import { escalationPathsDeleteTool } from '@/tools/incidentio/escalation_paths_delete' +import { escalationPathsListTool } from '@/tools/incidentio/escalation_paths_list' import { escalationPathsShowTool } from '@/tools/incidentio/escalation_paths_show' import { escalationPathsUpdateTool } from '@/tools/incidentio/escalation_paths_update' import { escalationsCreateTool } from '@/tools/incidentio/escalations_create' @@ -85,7 +86,10 @@ export const incidentioIncidentTimestampsShowTool = incidentTimestampsShowTool export const incidentioIncidentUpdatesListTool = incidentUpdatesListTool export const incidentioScheduleEntriesListTool = scheduleEntriesListTool export const incidentioScheduleOverridesCreateTool = scheduleOverridesCreateTool +export const incidentioEscalationPathsListTool = escalationPathsListTool export const incidentioEscalationPathsCreateTool = escalationPathsCreateTool export const incidentioEscalationPathsShowTool = escalationPathsShowTool export const incidentioEscalationPathsUpdateTool = escalationPathsUpdateTool export const incidentioEscalationPathsDeleteTool = escalationPathsDeleteTool + +export * from '@/tools/incidentio/types' diff --git a/apps/sim/tools/incidentio/schedule_entries_list.ts b/apps/sim/tools/incidentio/schedule_entries_list.ts index cf7633f3d07..d3d601622ff 100644 --- a/apps/sim/tools/incidentio/schedule_entries_list.ts +++ b/apps/sim/tools/incidentio/schedule_entries_list.ts @@ -40,44 +40,23 @@ export const scheduleEntriesListTool: ToolConfig< description: 'End date/time to filter entries in ISO 8601 format (e.g., "2024-01-22T09:00:00Z")', }, - page_size: { - type: 'number', - required: false, - visibility: 'user-or-llm', - description: 'Number of results to return per page (e.g., 10, 25, 50)', - }, - after: { - type: 'string', - required: false, - visibility: 'user-or-llm', - description: 'Cursor for pagination (e.g., "01FCNDV6P870EA6S7TK1DSYDG0")', - }, }, request: { url: (params) => { - const queryParams: string[] = [] + const url = new URL('https://api.incident.io/v2/schedule_entries') - queryParams.push(`schedule_id=${params.schedule_id}`) + url.searchParams.set('schedule_id', params.schedule_id.trim()) if (params.entry_window_start) { - queryParams.push(`entry_window_start=${encodeURIComponent(params.entry_window_start)}`) + url.searchParams.set('entry_window_start', params.entry_window_start) } if (params.entry_window_end) { - queryParams.push(`entry_window_end=${encodeURIComponent(params.entry_window_end)}`) + url.searchParams.set('entry_window_end', params.entry_window_end) } - if (params.page_size) { - queryParams.push(`page_size=${params.page_size}`) - } - - if (params.after) { - queryParams.push(`after=${params.after}`) - } - - const queryString = queryParams.length > 0 ? `?${queryParams.join('&')}` : '' - return `https://api.incident.io/v2/schedule_entries${queryString}` + return url.toString() }, method: 'GET', headers: (params) => ({ @@ -92,7 +71,11 @@ export const scheduleEntriesListTool: ToolConfig< return { success: true, output: { - schedule_entries: data.schedule_entries || data, + schedule_entries: { + final: data.schedule_entries?.final ?? [], + overrides: data.schedule_entries?.overrides ?? [], + scheduled: data.schedule_entries?.scheduled ?? [], + }, pagination_meta: data.pagination_meta, }, } @@ -100,28 +83,12 @@ export const scheduleEntriesListTool: ToolConfig< outputs: { schedule_entries: { - type: 'array', - description: 'List of schedule entries', - items: { - type: 'object', - properties: { - id: { type: 'string', description: 'The entry ID' }, - schedule_id: { type: 'string', description: 'The schedule ID' }, - user: { - type: 'object', - description: 'User assigned to this entry', - properties: { - id: { type: 'string', description: 'User ID' }, - name: { type: 'string', description: 'User name' }, - email: { type: 'string', description: 'User email' }, - }, - }, - start_at: { type: 'string', description: 'When the entry starts' }, - end_at: { type: 'string', description: 'When the entry ends' }, - layer_id: { type: 'string', description: 'The schedule layer ID' }, - created_at: { type: 'string', description: 'When the entry was created' }, - updated_at: { type: 'string', description: 'When the entry was last updated' }, - }, + type: 'object', + description: 'Schedule entries grouped by final, overrides, and scheduled entries', + properties: { + final: { type: 'array', description: 'Final computed schedule entries' }, + overrides: { type: 'array', description: 'Override schedule entries' }, + scheduled: { type: 'array', description: 'Scheduled entries before overrides are applied' }, }, }, pagination_meta: { @@ -131,7 +98,6 @@ export const scheduleEntriesListTool: ToolConfig< properties: { after: { type: 'string', description: 'Cursor for next page', optional: true }, after_url: { type: 'string', description: 'URL for next page', optional: true }, - page_size: { type: 'number', description: 'Number of results per page' }, }, }, }, diff --git a/apps/sim/tools/incidentio/schedule_overrides_create.ts b/apps/sim/tools/incidentio/schedule_overrides_create.ts index 75b1a87a8a7..7c4858c271a 100644 --- a/apps/sim/tools/incidentio/schedule_overrides_create.ts +++ b/apps/sim/tools/incidentio/schedule_overrides_create.ts @@ -26,6 +26,12 @@ export const scheduleOverridesCreateTool: ToolConfig< visibility: 'user-or-llm', description: 'The ID of the rotation to override (e.g., "01FCNDV6P870EA6S7TK1DSYDG0")', }, + layer_id: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the layer this override applies to', + }, schedule_id: { type: 'string', required: true, @@ -81,6 +87,7 @@ export const scheduleOverridesCreateTool: ToolConfig< if (params.user_slack_id) user.slack_user_id = params.user_slack_id return { + layer_id: params.layer_id.trim(), rotation_id: params.rotation_id, schedule_id: params.schedule_id, user, @@ -107,6 +114,7 @@ export const scheduleOverridesCreateTool: ToolConfig< description: 'The created schedule override', properties: { id: { type: 'string', description: 'The override ID' }, + layer_id: { type: 'string', description: 'The schedule layer ID' }, rotation_id: { type: 'string', description: 'The rotation ID' }, schedule_id: { type: 'string', description: 'The schedule ID' }, user: { diff --git a/apps/sim/tools/incidentio/schedules_delete.ts b/apps/sim/tools/incidentio/schedules_delete.ts index 928a1d7780c..4a1fc8829aa 100644 --- a/apps/sim/tools/incidentio/schedules_delete.ts +++ b/apps/sim/tools/incidentio/schedules_delete.ts @@ -29,7 +29,7 @@ export const schedulesDeleteTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/schedules/${params.id}`, + url: (params) => `https://api.incident.io/v2/schedules/${params.id.trim()}`, method: 'DELETE', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/schedules_show.ts b/apps/sim/tools/incidentio/schedules_show.ts index fa21c21a18a..146721e379b 100644 --- a/apps/sim/tools/incidentio/schedules_show.ts +++ b/apps/sim/tools/incidentio/schedules_show.ts @@ -29,7 +29,7 @@ export const schedulesShowTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/schedules/${params.id}`, + url: (params) => `https://api.incident.io/v2/schedules/${params.id.trim()}`, method: 'GET', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/schedules_update.ts b/apps/sim/tools/incidentio/schedules_update.ts index 6b962b20b93..a8a4802cc23 100644 --- a/apps/sim/tools/incidentio/schedules_update.ts +++ b/apps/sim/tools/incidentio/schedules_update.ts @@ -48,7 +48,7 @@ export const schedulesUpdateTool: ToolConfig< }, request: { - url: (params) => `https://api.incident.io/v2/schedules/${params.id}`, + url: (params) => `https://api.incident.io/v2/schedules/${params.id.trim()}`, method: 'PUT', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/types.ts b/apps/sim/tools/incidentio/types.ts index 317a6ca2691..c43971184f9 100644 --- a/apps/sim/tools/incidentio/types.ts +++ b/apps/sim/tools/incidentio/types.ts @@ -175,10 +175,31 @@ export const INCIDENTIO_FOLLOW_UP_OUTPUT: OutputProperty = { export const INCIDENTIO_WORKFLOW_OUTPUT_PROPERTIES = { id: { type: 'string', description: 'Workflow ID' }, name: { type: 'string', description: 'Workflow name' }, + trigger: { type: 'string', description: 'Workflow trigger' }, + once_for: { type: 'array', description: 'Fields that make the workflow run once' }, + version: { type: 'number', description: 'Workflow version' }, + expressions: { type: 'array', description: 'Workflow expressions' }, + condition_groups: { type: 'array', description: 'Workflow condition groups' }, + steps: { type: 'array', description: 'Workflow steps' }, + include_private_incidents: { + type: 'boolean', + description: 'Whether the workflow includes private incidents', + }, + include_private_escalations: { + type: 'boolean', + description: 'Whether the workflow includes private escalations', + }, + runs_on_incident_modes: { type: 'array', description: 'Incident modes the workflow runs on' }, + continue_on_step_error: { + type: 'boolean', + description: 'Whether execution continues after a step error', + }, + runs_on_incidents: { type: 'string', description: 'Incident lifecycle filter' }, state: { type: 'string', description: 'Workflow state (active, draft, disabled)' }, + delay: { type: 'object', description: 'Workflow delay configuration', optional: true }, folder: { type: 'string', description: 'Workflow folder', optional: true }, - created_at: { type: 'string', description: 'When the workflow was created', optional: true }, - updated_at: { type: 'string', description: 'When the workflow was last updated', optional: true }, + runs_from: { type: 'string', description: 'When the workflow runs from', optional: true }, + shortform: { type: 'string', description: 'Workflow shortform identifier', optional: true }, } as const satisfies Record /** @@ -279,6 +300,8 @@ interface IncidentioBaseParams { export interface IncidentioIncidentsListParams extends IncidentioBaseParams { page_size?: number after?: string + sort_by?: 'created_at_newest_first' | 'created_at_oldest_first' + filter_mode?: 'all' | 'any' } interface IncidentioIncident { @@ -396,6 +419,7 @@ export interface IncidentioIncidentsUpdateResponse extends ToolResponse { // Action types export interface IncidentioActionsListParams extends IncidentioBaseParams { incident_id?: string + incident_mode?: 'standard' | 'retrospective' | 'test' | 'tutorial' | 'stream' } interface IncidentioAction { @@ -445,6 +469,7 @@ export interface IncidentioActionsShowResponse extends ToolResponse { // Follow-up types export interface IncidentioFollowUpsListParams extends IncidentioBaseParams { incident_id?: string + incident_mode?: 'standard' | 'retrospective' | 'test' | 'tutorial' | 'stream' } interface IncidentioFollowUp { @@ -499,28 +524,33 @@ export interface IncidentioFollowUpsShowResponse extends ToolResponse { } // Workflow types -interface Workflow { +export interface Workflow { id: string name: string + trigger: string + once_for: unknown[] + version: number + expressions: unknown[] + condition_groups: unknown[] + steps: unknown[] + include_private_incidents: boolean + include_private_escalations: boolean + runs_on_incident_modes: string[] + continue_on_step_error: boolean + runs_on_incidents: 'newly_created' | 'newly_created_and_active' | 'active' | 'all' state: 'active' | 'draft' | 'disabled' + delay?: unknown folder?: string - created_at?: string - updated_at?: string + runs_from?: string + shortform?: string } // Workflows List tool types -export interface WorkflowsListParams extends IncidentioBaseParams { - page_size?: number - after?: string -} +export interface WorkflowsListParams extends IncidentioBaseParams {} export interface WorkflowsListResponse extends ToolResponse { output: { workflows: Workflow[] - pagination_meta?: { - after?: string - page_size: number - } } } @@ -543,6 +573,7 @@ export interface WorkflowsCreateParams extends IncidentioBaseParams { export interface WorkflowsCreateResponse extends ToolResponse { output: { + management_meta?: Record workflow: Workflow } } @@ -550,10 +581,12 @@ export interface WorkflowsCreateResponse extends ToolResponse { // Workflows Show tool types export interface WorkflowsShowParams extends IncidentioBaseParams { id: string + skip_step_upgrades?: boolean } export interface WorkflowsShowResponse extends ToolResponse { output: { + management_meta?: Record workflow: Workflow } } @@ -561,13 +594,23 @@ export interface WorkflowsShowResponse extends ToolResponse { // Workflows Update tool types export interface WorkflowsUpdateParams extends IncidentioBaseParams { id: string - name?: string + name: string + steps: string + condition_groups: string + runs_on_incidents: 'newly_created' | 'newly_created_and_active' | 'active' | 'all' + runs_on_incident_modes: string + include_private_incidents: boolean + continue_on_step_error: boolean + once_for: string + expressions: string state?: 'active' | 'draft' | 'disabled' folder?: string + delay?: string } export interface WorkflowsUpdateResponse extends ToolResponse { output: { + management_meta?: Record workflow: Workflow } } @@ -663,6 +706,8 @@ export interface CustomFieldsDeleteResponse extends ToolResponse { export interface IncidentioUsersListParams extends IncidentioBaseParams { page_size?: number after?: string + email?: string + slack_user_id?: string } interface IncidentioUser { @@ -784,13 +829,17 @@ export type IncidentioResponse = | IncidentioIncidentUpdatesListResponse | IncidentioScheduleEntriesListResponse | IncidentioScheduleOverridesCreateResponse + | IncidentioEscalationPathsListResponse | IncidentioEscalationPathsCreateResponse | IncidentioEscalationPathsShowResponse | IncidentioEscalationPathsUpdateResponse | IncidentioEscalationPathsDeleteResponse // Escalations types -export interface IncidentioEscalationsListParams extends IncidentioBaseParams {} +export interface IncidentioEscalationsListParams extends IncidentioBaseParams { + page_size?: number + after?: string +} interface IncidentioEscalation { id: string @@ -802,6 +851,10 @@ interface IncidentioEscalation { export interface IncidentioEscalationsListResponse extends ToolResponse { output: { escalations: IncidentioEscalation[] + pagination_meta?: { + after?: string + page_size: number + } } } @@ -1034,8 +1087,10 @@ export interface IncidentioIncidentUpdatesListResponse extends ToolResponse { // Schedule Entries types interface IncidentioScheduleEntry { - id: string - schedule_id: string + entry_id: string + fingerprint: string + rotation_id: string + layer_id: string user: { id: string name: string @@ -1043,26 +1098,24 @@ interface IncidentioScheduleEntry { } start_at: string end_at: string - layer_id: string - created_at: string - updated_at: string } export interface IncidentioScheduleEntriesListParams extends IncidentioBaseParams { schedule_id: string entry_window_start?: string entry_window_end?: string - page_size?: number - after?: string } export interface IncidentioScheduleEntriesListResponse extends ToolResponse { output: { - schedule_entries: IncidentioScheduleEntry[] + schedule_entries: { + final: IncidentioScheduleEntry[] + overrides: IncidentioScheduleEntry[] + scheduled: IncidentioScheduleEntry[] + } pagination_meta?: { after?: string after_url?: string - page_size: number } } } @@ -1070,6 +1123,7 @@ export interface IncidentioScheduleEntriesListResponse extends ToolResponse { // Schedule Overrides types interface IncidentioScheduleOverride { id: string + layer_id: string rotation_id: string schedule_id: string user: { @@ -1084,6 +1138,7 @@ interface IncidentioScheduleOverride { } export interface IncidentioScheduleOverridesCreateParams extends IncidentioBaseParams { + layer_id: string rotation_id: string schedule_id: string user_id?: string @@ -1122,8 +1177,21 @@ interface IncidentioEscalationPath { start_time: string end_time: string }> - created_at: string - updated_at: string +} + +export interface IncidentioEscalationPathsListParams extends IncidentioBaseParams { + page_size?: number + after?: string +} + +export interface IncidentioEscalationPathsListResponse extends ToolResponse { + output: { + escalation_paths: IncidentioEscalationPath[] + pagination_meta?: { + after?: string + page_size: number + } + } } export interface IncidentioEscalationPathsCreateParams extends IncidentioBaseParams { @@ -1163,8 +1231,8 @@ export interface IncidentioEscalationPathsShowResponse extends ToolResponse { export interface IncidentioEscalationPathsUpdateParams extends IncidentioBaseParams { id: string - name?: string - path?: Array<{ + name: string + path: Array<{ targets: Array<{ id: string type: string diff --git a/apps/sim/tools/incidentio/users_list.ts b/apps/sim/tools/incidentio/users_list.ts index cbf98291156..cfe56def211 100644 --- a/apps/sim/tools/incidentio/users_list.ts +++ b/apps/sim/tools/incidentio/users_list.ts @@ -31,6 +31,18 @@ export const usersListTool: ToolConfig `https://api.incident.io/v2/users/${params.id}`, + url: (params) => `https://api.incident.io/v2/users/${params.id.trim()}`, method: 'GET', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/utils.ts b/apps/sim/tools/incidentio/utils.ts new file mode 100644 index 00000000000..c08e81f4433 --- /dev/null +++ b/apps/sim/tools/incidentio/utils.ts @@ -0,0 +1,73 @@ +import type { Workflow } from '@/tools/incidentio/types' + +function getJsonParseErrorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error) +} + +function toStringValue(value: unknown): string { + return typeof value === 'string' ? value : String(value ?? '') +} + +function toOptionalStringValue(value: unknown): string | undefined { + return typeof value === 'string' && value ? value : undefined +} + +function toNumberValue(value: unknown): number { + return typeof value === 'number' ? value : Number(value ?? 0) +} + +function toBooleanValue(value: unknown): boolean { + return value === true +} + +function toArrayValue(value: unknown): T[] { + return Array.isArray(value) ? (value as T[]) : [] +} + +export function parseIncidentioJsonParam( + jsonString: string | undefined, + paramName: string, + defaultValue: unknown +): unknown { + if (jsonString === undefined || jsonString === '') return defaultValue + + try { + return JSON.parse(jsonString) + } catch (error) { + throw new Error(`Invalid JSON for ${paramName}: ${getJsonParseErrorMessage(error)}`) + } +} + +export function parseRequiredIncidentioJsonParam( + jsonString: string | undefined, + paramName: string +): unknown { + if (jsonString === undefined || jsonString === '') { + throw new Error(`Missing required JSON for ${paramName}`) + } + + return parseIncidentioJsonParam(jsonString, paramName, undefined) +} + +export function mapIncidentioWorkflow(workflow: Record): Workflow { + return { + id: toStringValue(workflow.id), + name: toStringValue(workflow.name), + trigger: toStringValue(workflow.trigger), + once_for: toArrayValue(workflow.once_for), + version: toNumberValue(workflow.version), + expressions: toArrayValue(workflow.expressions), + condition_groups: toArrayValue(workflow.condition_groups), + steps: toArrayValue(workflow.steps), + include_private_incidents: toBooleanValue(workflow.include_private_incidents), + include_private_escalations: toBooleanValue(workflow.include_private_escalations), + runs_on_incident_modes: toArrayValue(workflow.runs_on_incident_modes), + continue_on_step_error: toBooleanValue(workflow.continue_on_step_error), + runs_on_incidents: toStringValue(workflow.runs_on_incidents) as Workflow['runs_on_incidents'], + state: toStringValue(workflow.state) as Workflow['state'], + delay: workflow.delay, + folder: toOptionalStringValue(workflow.folder), + runs_from: toOptionalStringValue(workflow.runs_from), + shortform: toOptionalStringValue(workflow.shortform), + } +} diff --git a/apps/sim/tools/incidentio/workflows_create.ts b/apps/sim/tools/incidentio/workflows_create.ts index 87c3e2cd023..4a22510986f 100644 --- a/apps/sim/tools/incidentio/workflows_create.ts +++ b/apps/sim/tools/incidentio/workflows_create.ts @@ -1,9 +1,8 @@ -import { createLogger } from '@sim/logger' import type { WorkflowsCreateParams, WorkflowsCreateResponse } from '@/tools/incidentio/types' +import { INCIDENTIO_WORKFLOW_OUTPUT_PROPERTIES } from '@/tools/incidentio/types' +import { mapIncidentioWorkflow, parseIncidentioJsonParam } from '@/tools/incidentio/utils' import type { ToolConfig } from '@/tools/types' -const logger = createLogger('IncidentIOCreate') - export const workflowsCreateTool: ToolConfig = { id: 'incidentio_workflows_create', name: 'incident.io Workflows Create', @@ -122,27 +121,19 @@ export const workflowsCreateTool: ToolConfig { - // Helper function to safely parse JSON strings - const parseJsonParam = (jsonString: string | undefined, defaultValue: any) => { - if (!jsonString) return defaultValue - try { - return JSON.parse(jsonString) - } catch (error) { - logger.warn(`Failed to parse JSON parameter: ${jsonString}`, error) - return defaultValue - } - } - - // incident.io requires all these fields to create a workflow - const body: Record = { + const body: Record = { name: params.name, trigger: params.trigger || 'incident.updated', - once_for: parseJsonParam(params.once_for, []), - condition_groups: parseJsonParam(params.condition_groups, []), - steps: parseJsonParam(params.steps, []), - expressions: parseJsonParam(params.expressions, []), + once_for: parseIncidentioJsonParam(params.once_for, 'once_for', []), + condition_groups: parseIncidentioJsonParam(params.condition_groups, 'condition_groups', []), + steps: parseIncidentioJsonParam(params.steps, 'steps', []), + expressions: parseIncidentioJsonParam(params.expressions, 'expressions', []), include_private_incidents: params.include_private_incidents ?? true, - runs_on_incident_modes: parseJsonParam(params.runs_on_incident_modes, ['standard']), + runs_on_incident_modes: parseIncidentioJsonParam( + params.runs_on_incident_modes, + 'runs_on_incident_modes', + ['standard'] + ), continue_on_step_error: params.continue_on_step_error ?? false, runs_on_incidents: params.runs_on_incidents || 'newly_created', state: params.state || 'draft', @@ -153,7 +144,7 @@ export const workflowsCreateTool: ToolConfig `https://api.incident.io/v2/workflows/${params.id}`, + url: (params) => `https://api.incident.io/v2/workflows/${params.id.trim()}`, method: 'DELETE', headers: (params) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/incidentio/workflows_list.ts b/apps/sim/tools/incidentio/workflows_list.ts index ec45d3d1fce..490f4c74de5 100644 --- a/apps/sim/tools/incidentio/workflows_list.ts +++ b/apps/sim/tools/incidentio/workflows_list.ts @@ -1,4 +1,6 @@ import type { WorkflowsListParams, WorkflowsListResponse } from '@/tools/incidentio/types' +import { INCIDENTIO_WORKFLOW_OUTPUT_PROPERTIES } from '@/tools/incidentio/types' +import { mapIncidentioWorkflow } from '@/tools/incidentio/utils' import type { ToolConfig } from '@/tools/types' export const workflowsListTool: ToolConfig = { @@ -14,35 +16,10 @@ export const workflowsListTool: ToolConfig { - const url = new URL('https://api.incident.io/v2/workflows') - - if (params.page_size) { - url.searchParams.set('page_size', Number(params.page_size).toString()) - } - - if (params.after) { - url.searchParams.set('after', params.after) - } - - return url.toString() - }, + url: 'https://api.incident.io/v2/workflows', method: 'GET', headers: (params) => ({ 'Content-Type': 'application/json', @@ -56,20 +33,10 @@ export const workflowsListTool: ToolConfig ({ - id: workflow.id, - name: workflow.name, - state: workflow.state, - folder: workflow.folder, - created_at: workflow.created_at, - updated_at: workflow.updated_at, - })), - pagination_meta: data.pagination_meta - ? { - after: data.pagination_meta.after, - page_size: data.pagination_meta.page_size, - } - : undefined, + workflows: + data.workflows?.map((workflow: Record) => + mapIncidentioWorkflow(workflow) + ) ?? [], }, } }, @@ -80,34 +47,7 @@ export const workflowsListTool: ToolConfig = { @@ -20,10 +22,22 @@ export const workflowsShowTool: ToolConfig `https://api.incident.io/v2/workflows/${params.id}`, + url: (params) => { + const url = new URL(`https://api.incident.io/v2/workflows/${params.id.trim()}`) + if (params.skip_step_upgrades !== undefined) { + url.searchParams.set('skip_step_upgrades', String(params.skip_step_upgrades)) + } + return url.toString() + }, method: 'GET', headers: (params) => ({ 'Content-Type': 'application/json', @@ -37,14 +51,8 @@ export const workflowsShowTool: ToolConfig = { @@ -22,10 +28,59 @@ export const workflowsUpdateTool: ToolConfig `https://api.incident.io/v2/workflows/${params.id}`, + url: (params) => `https://api.incident.io/v2/workflows/${params.id.trim()}`, method: 'PUT', headers: (params) => ({ 'Content-Type': 'application/json', Authorization: `Bearer ${params.apiKey}`, }), body: (params) => { - const body: Record = {} - - if (params.name) { - body.name = params.name + const body: Record = { + name: params.name, + once_for: parseRequiredIncidentioJsonParam(params.once_for, 'once_for'), + condition_groups: parseRequiredIncidentioJsonParam( + params.condition_groups, + 'condition_groups' + ), + steps: parseRequiredIncidentioJsonParam(params.steps, 'steps'), + expressions: parseRequiredIncidentioJsonParam(params.expressions, 'expressions'), + include_private_incidents: params.include_private_incidents, + runs_on_incident_modes: parseRequiredIncidentioJsonParam( + params.runs_on_incident_modes, + 'runs_on_incident_modes' + ), + continue_on_step_error: params.continue_on_step_error, + runs_on_incidents: params.runs_on_incidents, } - if (params.state) { - body.state = params.state - } - - if (params.folder) { - body.folder = params.folder - } + if (params.state) body.state = params.state + if (params.folder) body.folder = params.folder + if (params.delay) body.delay = parseIncidentioJsonParam(params.delay, 'delay', undefined) return body }, @@ -72,14 +141,8 @@ export const workflowsUpdateTool: ToolConfig { + const normalized = value?.toLowerCase().replace(/[_-]/g, ' ') + return DEPLOYMENT_TYPES.includes(normalized as NewRelicDeploymentType) + ? (normalized as NewRelicDeploymentType) + : 'basic' +} + +const graphqlLiteral = (value: string | number | boolean): string => { + if (typeof value === 'string') return gqlString(value) + return String(value) +} + +const buildCustomAttributes = ( + customAttributes?: NewRelicCreateDeploymentEventParams['customAttributes'] +): string | undefined => { + if (!customAttributes) return undefined + + const entries = Object.entries(customAttributes) + if (!entries.length) return undefined + + for (const [key, value] of entries) { + if (!CUSTOM_ATTRIBUTE_NAME_PATTERN.test(key)) { + throw new Error( + `Invalid New Relic custom attribute name "${key}". Use letters, numbers, and underscores, and do not start with a number.` + ) + } + if (RESTRICTED_CUSTOM_ATTRIBUTE_NAMES.has(key) || key.includes('.')) { + throw new Error(`New Relic custom attribute name "${key}" is restricted`) + } + if (!['string', 'number', 'boolean'].includes(typeof value)) { + throw new Error( + `Invalid value for New Relic custom attribute "${key}". Use a string, number, or boolean.` + ) + } + if (typeof value === 'number' && !Number.isFinite(value)) { + throw new Error(`Invalid numeric value for New Relic custom attribute "${key}"`) + } + } + + const fields = entries.map(([key, value]) => `${key}: ${graphqlLiteral(value)}`).join(', ') + return `customAttributes: { ${fields} }` +} + +const buildDeploymentMutation = (params: NewRelicCreateDeploymentEventParams): string => { + const deploymentType = getDeploymentType(params.deploymentType) + const entityGuid = params.entityGuid.trim() + if (!entityGuid || entityGuid.includes("'")) { + throw new Error('Invalid entity GUID: value must not be empty or contain single quotes') + } + const version = params.version.trim() + const shortDescription = cleanOptionalString(params.shortDescription) + const description = cleanOptionalString(params.description) + const changelog = cleanOptionalString(params.changelog) + const commit = cleanOptionalString(params.commit) + const deepLink = cleanOptionalString(params.deepLink) + const user = cleanOptionalString(params.user) + const groupId = cleanOptionalString(params.groupId) + const customAttributes = buildCustomAttributes(params.customAttributes) + const deploymentFields = [ + `version: ${gqlString(version)}`, + changelog ? `changelog: ${gqlString(changelog)}` : undefined, + commit ? `commit: ${gqlString(commit)}` : undefined, + deepLink ? `deepLink: ${gqlString(deepLink)}` : undefined, + ] + .filter((field): field is string => Boolean(field)) + .join(', ') + const optionalFields = [ + shortDescription ? `shortDescription: ${gqlString(shortDescription)}` : undefined, + description ? `description: ${gqlString(description)}` : undefined, + user ? `user: ${gqlString(user)}` : undefined, + groupId ? `groupId: ${gqlString(groupId)}` : undefined, + customAttributes, + params.timestamp ? `timestamp: ${Math.trunc(Number(params.timestamp))}` : undefined, + ] + .filter((field): field is string => Boolean(field)) + .join('\n ') + + return `mutation { + changeTrackingCreateEvent( + changeTrackingEvent: { + categoryAndTypeData: { + categoryFields: { deployment: { ${deploymentFields} } } + kind: { category: "deployment", type: ${gqlString(deploymentType)} } + } + entitySearch: { query: ${gqlString(`id = '${entityGuid}'`)} } + ${optionalFields} + } + ) { + changeTrackingEvent { + category + categoryAndType + changeTrackingId + customAttributes + description + entity { + guid + name + } + groupId + shortDescription + timestamp + type + user + } + messages + } +}` +} + +export const newRelicCreateDeploymentEventTool: ToolConfig< + NewRelicCreateDeploymentEventParams, + NewRelicCreateDeploymentEventResponse +> = { + id: 'new_relic_create_deployment_event', + name: 'New Relic Create Deployment Event', + description: 'Record a deployment change event in New Relic change tracking.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'New Relic user API key for NerdGraph', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'New Relic data center region: us or eu', + }, + entityGuid: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'GUID of the entity associated with the deployment', + }, + version: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Deployment version, release name, or commit SHA', + }, + shortDescription: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Short description of the deployment', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Longer deployment description', + }, + changelog: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Deployment changelog text or URL', + }, + commit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Commit SHA or identifier associated with the deployment', + }, + deepLink: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'URL to the deployment, build, or release details', + }, + user: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'User or automation that performed the deployment', + }, + groupId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Optional group ID to correlate related changes', + }, + customAttributes: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'Custom change event metadata as key-value pairs with string, number, or boolean values', + }, + deploymentType: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Deployment type: basic, blue green, canary, rolling, or shadow', + }, + timestamp: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Event timestamp in milliseconds since Unix epoch', + }, + }, + + request: { + url: (params) => getNerdGraphEndpoint(params.region), + method: 'POST', + headers: (params) => newRelicHeaders(params.apiKey), + body: (params) => ({ + query: buildDeploymentMutation(params), + }), + }, + + transformResponse: async (response) => { + const payload = await parseNerdGraphResponse(response) + const result = payload.data?.changeTrackingCreateEvent + if (!result) { + throw new Error('New Relic did not return a deployment change tracking result') + } + if (!result.changeTrackingEvent) { + const message = result.messages?.length + ? result.messages.join('; ') + : 'New Relic did not create a deployment change tracking event' + throw new Error(message) + } + + return { + success: true, + output: { + event: result.changeTrackingEvent, + messages: result?.messages ?? [], + }, + } + }, + + outputs: { + event: { + type: 'object', + description: 'Created New Relic change tracking event', + properties: { + changeTrackingId: { + type: 'string', + description: 'New Relic change tracking ID', + nullable: true, + }, + customAttributes: { + type: 'json', + description: 'Custom attributes on the change tracking event', + optional: true, + nullable: true, + }, + category: { type: 'string', description: 'Change category', nullable: true }, + categoryAndType: { + type: 'string', + description: 'Combined category and type', + nullable: true, + }, + type: { type: 'string', description: 'Change type', nullable: true }, + shortDescription: { + type: 'string', + description: 'Short change description', + nullable: true, + }, + description: { type: 'string', description: 'Change description', nullable: true }, + timestamp: { + type: 'number', + description: 'Change timestamp in milliseconds', + nullable: true, + }, + user: { type: 'string', description: 'User associated with the change', nullable: true }, + groupId: { type: 'string', description: 'Change group ID', nullable: true }, + entity: { + type: 'object', + description: 'Entity associated with the change', + nullable: true, + properties: { + guid: { type: 'string', description: 'Entity GUID', nullable: true }, + name: { type: 'string', description: 'Entity name', nullable: true }, + }, + }, + }, + }, + messages: { + type: 'array', + description: 'Messages returned by New Relic for the created change event', + items: { + type: 'string', + description: 'New Relic message', + }, + }, + }, +} diff --git a/apps/sim/tools/new_relic/get_entity.ts b/apps/sim/tools/new_relic/get_entity.ts new file mode 100644 index 00000000000..4e839a04f3b --- /dev/null +++ b/apps/sim/tools/new_relic/get_entity.ts @@ -0,0 +1,93 @@ +import type { NewRelicGetEntityParams, NewRelicGetEntityResponse } from '@/tools/new_relic/types' +import { + getNerdGraphEndpoint, + gqlString, + newRelicHeaders, + parseNerdGraphResponse, +} from '@/tools/new_relic/utils' +import type { ToolConfig } from '@/tools/types' + +interface GetEntityData { + actor?: { + entity?: { + name?: string | null + entityType?: string | null + } | null + } | null +} + +export const newRelicGetEntityTool: ToolConfig = + { + id: 'new_relic_get_entity', + name: 'New Relic Get Entity', + description: 'Fetch a New Relic entity by GUID.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'New Relic user API key for NerdGraph', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'New Relic data center region: us or eu', + }, + guid: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Entity GUID', + }, + }, + + request: { + url: (params) => getNerdGraphEndpoint(params.region), + method: 'POST', + headers: (params) => newRelicHeaders(params.apiKey), + body: (params) => ({ + query: `{ + actor { + entity(guid: ${gqlString(params.guid.trim())}) { + name + entityType + } + } +}`, + }), + }, + + transformResponse: async (response, params) => { + const payload = await parseNerdGraphResponse(response) + const entity = payload.data?.actor?.entity + + return { + success: true, + output: { + entity: entity + ? { + guid: params?.guid ?? null, + name: entity.name ?? null, + entityType: entity.entityType ?? null, + } + : null, + }, + } + }, + + outputs: { + entity: { + type: 'object', + description: 'New Relic entity details', + optional: true, + properties: { + guid: { type: 'string', description: 'Entity GUID', nullable: true }, + name: { type: 'string', description: 'Entity name', nullable: true }, + entityType: { type: 'string', description: 'Entity type', nullable: true }, + }, + }, + }, + } diff --git a/apps/sim/tools/new_relic/index.ts b/apps/sim/tools/new_relic/index.ts new file mode 100644 index 00000000000..31877b8cd65 --- /dev/null +++ b/apps/sim/tools/new_relic/index.ts @@ -0,0 +1,5 @@ +export { newRelicCreateDeploymentEventTool } from '@/tools/new_relic/create_deployment_event' +export { newRelicGetEntityTool } from '@/tools/new_relic/get_entity' +export { newRelicNrqlQueryTool } from '@/tools/new_relic/nrql_query' +export { newRelicSearchEntitiesTool } from '@/tools/new_relic/search_entities' +export type * from '@/tools/new_relic/types' diff --git a/apps/sim/tools/new_relic/nrql_query.ts b/apps/sim/tools/new_relic/nrql_query.ts new file mode 100644 index 00000000000..9b7d0da4d4f --- /dev/null +++ b/apps/sim/tools/new_relic/nrql_query.ts @@ -0,0 +1,111 @@ +import type { NewRelicNrqlQueryParams, NewRelicNrqlQueryResponse } from '@/tools/new_relic/types' +import { + getNerdGraphEndpoint, + gqlString, + newRelicHeaders, + parseNerdGraphResponse, +} from '@/tools/new_relic/utils' +import type { ToolConfig } from '@/tools/types' + +interface NrqlQueryData { + actor?: { + account?: { + nrql?: { + results?: Record[] + } | null + } | null + } | null +} + +export const newRelicNrqlQueryTool: ToolConfig = + { + id: 'new_relic_nrql_query', + name: 'New Relic NRQL Query', + description: 'Run a NRQL query against a New Relic account using NerdGraph.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'New Relic user API key for NerdGraph', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'New Relic data center region: us or eu', + }, + accountId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'New Relic account ID to query', + }, + nrql: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'NRQL query to execute', + }, + timeout: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Optional query timeout in seconds', + }, + }, + + request: { + url: (params) => getNerdGraphEndpoint(params.region), + method: 'POST', + headers: (params) => newRelicHeaders(params.apiKey), + body: (params) => { + const timeout = params.timeout ? `, timeout: ${Math.trunc(Number(params.timeout))}` : '' + return { + query: `{ + actor { + account(id: ${Math.trunc(Number(params.accountId))}) { + nrql(query: ${gqlString(params.nrql)}${timeout}) { + results + } + } + } +}`, + } + }, + }, + + transformResponse: async (response) => { + const payload = await parseNerdGraphResponse(response) + const nrql = payload.data?.actor?.account?.nrql + if (!nrql) { + throw new Error('New Relic did not return NRQL data for the requested account') + } + const results = nrql.results ?? [] + + return { + success: true, + output: { + results, + resultCount: results.length, + }, + } + }, + + outputs: { + results: { + type: 'array', + description: 'NRQL result rows. Row fields depend on the query projection.', + items: { + type: 'object', + description: 'A NRQL result row', + }, + }, + resultCount: { + type: 'number', + description: 'Number of NRQL result rows returned', + }, + }, + } diff --git a/apps/sim/tools/new_relic/search_entities.ts b/apps/sim/tools/new_relic/search_entities.ts new file mode 100644 index 00000000000..9d0e1985626 --- /dev/null +++ b/apps/sim/tools/new_relic/search_entities.ts @@ -0,0 +1,131 @@ +import type { + NewRelicEntity, + NewRelicSearchEntitiesParams, + NewRelicSearchEntitiesResponse, +} from '@/tools/new_relic/types' +import { + getNerdGraphEndpoint, + newRelicHeaders, + parseNerdGraphResponse, +} from '@/tools/new_relic/utils' +import type { ToolConfig } from '@/tools/types' + +interface SearchEntitiesData { + actor?: { + entitySearch?: { + count?: number + query?: string + results?: { + nextCursor?: string | null + entities?: NewRelicEntity[] + } | null + } | null + } | null +} + +export const newRelicSearchEntitiesTool: ToolConfig< + NewRelicSearchEntitiesParams, + NewRelicSearchEntitiesResponse +> = { + id: 'new_relic_search_entities', + name: 'New Relic Search Entities', + description: 'Search New Relic entities by name, GUID, domain type, tags, or reporting state.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'New Relic user API key for NerdGraph', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'New Relic data center region: us or eu', + }, + query: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'Entity search query, for example: name like "api" or domainType = "APM-APPLICATION"', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous entity search', + }, + }, + + request: { + url: (params) => getNerdGraphEndpoint(params.region), + method: 'POST', + headers: (params) => newRelicHeaders(params.apiKey), + body: (params) => ({ + query: `query($query: String!, $cursor: String) { + actor { + entitySearch(query: $query) { + count + query + results(cursor: $cursor) { + nextCursor + entities { + guid + name + entityType + } + } + } + } +}`, + variables: { + query: params.query, + cursor: params.cursor || null, + }, + }), + }, + + transformResponse: async (response) => { + const payload = await parseNerdGraphResponse(response) + const entitySearch = payload.data?.actor?.entitySearch + if (!entitySearch) { + throw new Error('New Relic did not return entity search data') + } + const entities = entitySearch?.results?.entities ?? [] + + return { + success: true, + output: { + count: entitySearch?.count ?? entities.length, + query: entitySearch?.query ?? '', + entities, + nextCursor: entitySearch?.results?.nextCursor ?? null, + }, + } + }, + + outputs: { + count: { type: 'number', description: 'Total number of entities matching the query' }, + query: { type: 'string', description: 'Entity search query New Relic executed' }, + entities: { + type: 'array', + description: 'Matching New Relic entities', + items: { + type: 'object', + properties: { + guid: { type: 'string', description: 'Entity GUID', nullable: true }, + name: { type: 'string', description: 'Entity name', nullable: true }, + entityType: { type: 'string', description: 'Entity type', nullable: true }, + }, + }, + }, + nextCursor: { + type: 'string', + description: 'Cursor for the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/new_relic/types.ts b/apps/sim/tools/new_relic/types.ts new file mode 100644 index 00000000000..1cd123f4d88 --- /dev/null +++ b/apps/sim/tools/new_relic/types.ts @@ -0,0 +1,100 @@ +import type { ToolResponse } from '@/tools/types' + +export type NewRelicRegion = 'us' | 'eu' + +export interface NewRelicBaseParams { + apiKey: string + region?: NewRelicRegion +} + +export interface NewRelicNrqlQueryParams extends NewRelicBaseParams { + accountId: number + nrql: string + timeout?: number +} + +export interface NewRelicNrqlQueryResponse extends ToolResponse { + output: { + results: Record[] + resultCount: number + } +} + +export interface NewRelicSearchEntitiesParams extends NewRelicBaseParams { + query: string + cursor?: string +} + +export interface NewRelicEntity { + guid: string | null + name: string | null + entityType: string | null +} + +export interface NewRelicSearchEntitiesResponse extends ToolResponse { + output: { + count: number + query: string + entities: NewRelicEntity[] + nextCursor: string | null + } +} + +export interface NewRelicGetEntityParams extends NewRelicBaseParams { + guid: string +} + +export interface NewRelicGetEntityResponse extends ToolResponse { + output: { + entity: NewRelicEntity | null + } +} + +export type NewRelicDeploymentType = 'basic' | 'blue green' | 'canary' | 'rolling' | 'shadow' + +export type NewRelicCustomAttributes = Record + +export interface NewRelicCreateDeploymentEventParams extends NewRelicBaseParams { + entityGuid: string + version: string + shortDescription?: string + description?: string + changelog?: string + commit?: string + deepLink?: string + user?: string + groupId?: string + customAttributes?: NewRelicCustomAttributes + deploymentType?: NewRelicDeploymentType + timestamp?: number +} + +export interface NewRelicChangeTrackingEvent { + category: string | null + categoryAndType: string | null + changeTrackingId: string | null + customAttributes?: Record | null + description: string | null + groupId: string | null + shortDescription: string | null + timestamp: number | null + type: string | null + user: string | null + entity: { + guid: string | null + name: string | null + } | null +} + +export interface NewRelicCreateDeploymentEventResponse extends ToolResponse { + output: { + event: NewRelicChangeTrackingEvent | null + messages: string[] + } +} + +export type NewRelicResponse = + | NewRelicNrqlQueryResponse + | NewRelicSearchEntitiesResponse + | NewRelicGetEntityResponse + | NewRelicCreateDeploymentEventResponse diff --git a/apps/sim/tools/new_relic/utils.ts b/apps/sim/tools/new_relic/utils.ts new file mode 100644 index 00000000000..032ae1263f1 --- /dev/null +++ b/apps/sim/tools/new_relic/utils.ts @@ -0,0 +1,42 @@ +import type { NewRelicRegion } from '@/tools/new_relic/types' + +interface GraphQLError { + message?: string +} + +interface GraphQLResponse { + data?: TData + errors?: GraphQLError[] +} + +export const getNerdGraphEndpoint = (region?: NewRelicRegion): string => + region === 'eu' ? 'https://api.eu.newrelic.com/graphql' : 'https://api.newrelic.com/graphql' + +export const newRelicHeaders = (apiKey: string): Record => ({ + 'API-Key': apiKey, + 'Content-Type': 'application/json', +}) + +export const gqlString = (value: string): string => JSON.stringify(value) + +export async function parseNerdGraphResponse( + response: Response +): Promise> { + const payload = (await response.json().catch(() => ({}))) as GraphQLResponse + + if (!response.ok || payload.errors?.length) { + const message = + payload.errors + ?.map((error) => error.message) + .filter((errorMessage): errorMessage is string => Boolean(errorMessage)) + .join('; ') || `HTTP ${response.status}: ${response.statusText}` + throw new Error(message) + } + + return payload +} + +export const cleanOptionalString = (value?: string): string | undefined => { + const trimmed = value?.trim() + return trimmed ? trimmed : undefined +} diff --git a/apps/sim/tools/railway/create_environment.ts b/apps/sim/tools/railway/create_environment.ts new file mode 100644 index 00000000000..68a3b17c064 --- /dev/null +++ b/apps/sim/tools/railway/create_environment.ts @@ -0,0 +1,131 @@ +import type { + RailwayCreatedResource, + RailwayCreateEnvironmentParams, + RailwayCreateEnvironmentResponse, +} from '@/tools/railway/types' +import { + compactVariables, + optionalString, + parseRailwayGraphqlResponse, + RAILWAY_GRAPHQL_URL, + railwayHeaders, +} from '@/tools/railway/utils' +import type { ToolConfig } from '@/tools/types' + +interface RailwayCreateEnvironmentData { + environmentCreate?: RailwayCreatedResource +} + +export const railwayCreateEnvironmentTool: ToolConfig< + RailwayCreateEnvironmentParams, + RailwayCreateEnvironmentResponse +> = { + id: 'railway_create_environment', + name: 'Railway Create Environment', + description: 'Create a Railway project environment', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Railway API token', + }, + tokenType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Railway token type: account, workspace, project, or oauth', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Railway project ID', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Environment name', + }, + sourceEnvironmentId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Environment ID to clone from', + }, + ephemeral: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the environment is ephemeral', + }, + skipInitialDeploys: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to skip initial deploys for the environment', + }, + stageInitialChanges: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to stage initial changes instead of applying them immediately', + }, + }, + + request: { + url: RAILWAY_GRAPHQL_URL, + method: 'POST', + headers: (params) => railwayHeaders(params.apiKey, params.tokenType), + body: (params) => ({ + query: ` + mutation CreateEnvironment($input: EnvironmentCreateInput!) { + environmentCreate(input: $input) { + id + name + } + } + `, + variables: { + input: compactVariables({ + projectId: params.projectId.trim(), + name: params.name.trim(), + sourceEnvironmentId: optionalString(params.sourceEnvironmentId), + ephemeral: params.ephemeral, + skipInitialDeploys: params.skipInitialDeploys, + stageInitialChanges: params.stageInitialChanges, + }), + }, + }), + }, + + transformResponse: async (response: Response) => { + const data = await parseRailwayGraphqlResponse(response) + const environment = data.data?.environmentCreate + if (!environment) throw new Error('Railway did not return a created environment') + + return { + success: true, + output: { + environment: { + id: environment.id, + name: environment.name, + }, + }, + } + }, + + outputs: { + environment: { + type: 'object', + description: 'Created environment', + properties: { + id: { type: 'string', description: 'Environment ID' }, + name: { type: 'string', description: 'Environment name' }, + }, + }, + }, +} diff --git a/apps/sim/tools/railway/create_project.ts b/apps/sim/tools/railway/create_project.ts new file mode 100644 index 00000000000..0634801bbef --- /dev/null +++ b/apps/sim/tools/railway/create_project.ts @@ -0,0 +1,131 @@ +import type { + RailwayCreatedResource, + RailwayCreateProjectParams, + RailwayCreateProjectResponse, +} from '@/tools/railway/types' +import { + compactVariables, + optionalString, + parseRailwayGraphqlResponse, + RAILWAY_GRAPHQL_URL, + railwayHeaders, +} from '@/tools/railway/utils' +import type { ToolConfig } from '@/tools/types' + +interface RailwayCreateProjectData { + projectCreate?: RailwayCreatedResource +} + +export const railwayCreateProjectTool: ToolConfig< + RailwayCreateProjectParams, + RailwayCreateProjectResponse +> = { + id: 'railway_create_project', + name: 'Railway Create Project', + description: 'Create a Railway project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Railway API token', + }, + tokenType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Railway token type: account, workspace, project, or oauth', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project name', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Project description', + }, + workspaceId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Workspace ID to create the project in', + }, + isPublic: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the project should be publicly visible', + }, + defaultEnvironmentName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Name for the default environment', + }, + prDeploys: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to enable pull request deploys', + }, + }, + + request: { + url: RAILWAY_GRAPHQL_URL, + method: 'POST', + headers: (params) => railwayHeaders(params.apiKey, params.tokenType), + body: (params) => ({ + query: ` + mutation CreateProject($input: ProjectCreateInput!) { + projectCreate(input: $input) { + id + name + } + } + `, + variables: { + input: compactVariables({ + name: params.name.trim(), + description: optionalString(params.description), + workspaceId: optionalString(params.workspaceId), + isPublic: params.isPublic, + defaultEnvironmentName: optionalString(params.defaultEnvironmentName), + prDeploys: params.prDeploys, + }), + }, + }), + }, + + transformResponse: async (response: Response) => { + const data = await parseRailwayGraphqlResponse(response) + const project = data.data?.projectCreate + if (!project) throw new Error('Railway did not return a created project') + + return { + success: true, + output: { + project: { + id: project.id, + name: project.name, + }, + }, + } + }, + + outputs: { + project: { + type: 'object', + description: 'Created project', + properties: { + id: { type: 'string', description: 'Project ID' }, + name: { type: 'string', description: 'Project name' }, + }, + }, + }, +} diff --git a/apps/sim/tools/railway/delete_environment.ts b/apps/sim/tools/railway/delete_environment.ts new file mode 100644 index 00000000000..5d7c27417b0 --- /dev/null +++ b/apps/sim/tools/railway/delete_environment.ts @@ -0,0 +1,82 @@ +import type { + RailwayDeleteEnvironmentParams, + RailwayDeleteEnvironmentResponse, +} from '@/tools/railway/types' +import { + parseRailwayGraphqlResponse, + RAILWAY_GRAPHQL_URL, + railwayHeaders, +} from '@/tools/railway/utils' +import type { ToolConfig } from '@/tools/types' + +interface RailwayDeleteEnvironmentData { + environmentDelete?: boolean +} + +export const railwayDeleteEnvironmentTool: ToolConfig< + RailwayDeleteEnvironmentParams, + RailwayDeleteEnvironmentResponse +> = { + id: 'railway_delete_environment', + name: 'Railway Delete Environment', + description: 'Delete a Railway project environment', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Railway API token', + }, + tokenType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Railway token type: account, workspace, project, or oauth', + }, + environmentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Railway environment ID', + }, + }, + + request: { + url: RAILWAY_GRAPHQL_URL, + method: 'POST', + headers: (params) => railwayHeaders(params.apiKey, params.tokenType), + body: (params) => ({ + query: ` + mutation DeleteEnvironment($id: String!) { + environmentDelete(id: $id) + } + `, + variables: { + id: params.environmentId.trim(), + }, + }), + }, + + transformResponse: async (response: Response) => { + const data = await parseRailwayGraphqlResponse(response) + if (typeof data.data?.environmentDelete !== 'boolean') { + throw new Error('Railway did not return an environment deletion result') + } + + return { + success: true, + output: { + success: data.data.environmentDelete, + }, + } + }, + + outputs: { + success: { + type: 'boolean', + description: 'Whether the environment was deleted', + }, + }, +} diff --git a/apps/sim/tools/railway/delete_project.ts b/apps/sim/tools/railway/delete_project.ts new file mode 100644 index 00000000000..e81d5406cca --- /dev/null +++ b/apps/sim/tools/railway/delete_project.ts @@ -0,0 +1,82 @@ +import type { + RailwayDeleteProjectParams, + RailwayDeleteProjectResponse, +} from '@/tools/railway/types' +import { + parseRailwayGraphqlResponse, + RAILWAY_GRAPHQL_URL, + railwayHeaders, +} from '@/tools/railway/utils' +import type { ToolConfig } from '@/tools/types' + +interface RailwayDeleteProjectData { + projectDelete?: boolean +} + +export const railwayDeleteProjectTool: ToolConfig< + RailwayDeleteProjectParams, + RailwayDeleteProjectResponse +> = { + id: 'railway_delete_project', + name: 'Railway Delete Project', + description: 'Delete a Railway project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Railway API token', + }, + tokenType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Railway token type: account, workspace, project, or oauth', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Railway project ID', + }, + }, + + request: { + url: RAILWAY_GRAPHQL_URL, + method: 'POST', + headers: (params) => railwayHeaders(params.apiKey, params.tokenType), + body: (params) => ({ + query: ` + mutation DeleteProject($id: String!) { + projectDelete(id: $id) + } + `, + variables: { + id: params.projectId.trim(), + }, + }), + }, + + transformResponse: async (response: Response) => { + const data = await parseRailwayGraphqlResponse(response) + if (typeof data.data?.projectDelete !== 'boolean') { + throw new Error('Railway did not return a project deletion result') + } + + return { + success: true, + output: { + success: data.data.projectDelete, + }, + } + }, + + outputs: { + success: { + type: 'boolean', + description: 'Whether the project was deleted', + }, + }, +} diff --git a/apps/sim/tools/railway/deploy_service.ts b/apps/sim/tools/railway/deploy_service.ts new file mode 100644 index 00000000000..a7acd359a01 --- /dev/null +++ b/apps/sim/tools/railway/deploy_service.ts @@ -0,0 +1,118 @@ +import type { + RailwayDeployServiceParams, + RailwayDeployServiceResponse, +} from '@/tools/railway/types' +import { + optionalString, + parseRailwayGraphqlResponse, + RAILWAY_GRAPHQL_URL, + railwayHeaders, +} from '@/tools/railway/utils' +import type { ToolConfig } from '@/tools/types' + +interface RailwayDeployServiceData { + serviceInstanceDeployV2?: string +} + +export const railwayDeployServiceTool: ToolConfig< + RailwayDeployServiceParams, + RailwayDeployServiceResponse +> = { + id: 'railway_deploy_service', + name: 'Railway Deploy Service', + description: 'Trigger a deployment for a Railway service in an environment', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Railway API token', + }, + tokenType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Railway token type: account, workspace, project, or oauth', + }, + serviceId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Railway service ID', + }, + environmentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Railway environment ID', + }, + commitSha: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Specific Git commit SHA to deploy', + }, + }, + + request: { + url: RAILWAY_GRAPHQL_URL, + method: 'POST', + headers: (params) => railwayHeaders(params.apiKey, params.tokenType), + body: (params) => { + const commitSha = optionalString(params.commitSha) + + if (commitSha) { + return { + query: ` + mutation DeployService($serviceId: String!, $environmentId: String!, $commitSha: String!) { + serviceInstanceDeployV2( + serviceId: $serviceId + environmentId: $environmentId + commitSha: $commitSha + ) + } + `, + variables: { + serviceId: params.serviceId.trim(), + environmentId: params.environmentId.trim(), + commitSha, + }, + } + } + + return { + query: ` + mutation DeployService($serviceId: String!, $environmentId: String!) { + serviceInstanceDeployV2(serviceId: $serviceId, environmentId: $environmentId) + } + `, + variables: { + serviceId: params.serviceId.trim(), + environmentId: params.environmentId.trim(), + }, + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await parseRailwayGraphqlResponse(response) + const deploymentId = data.data?.serviceInstanceDeployV2 + if (!deploymentId) throw new Error('Railway did not return a deployment ID') + + return { + success: true, + output: { + deploymentId, + }, + } + }, + + outputs: { + deploymentId: { + type: 'string', + description: 'Created deployment ID', + }, + }, +} diff --git a/apps/sim/tools/railway/get_project.ts b/apps/sim/tools/railway/get_project.ts new file mode 100644 index 00000000000..5bc7ef9971b --- /dev/null +++ b/apps/sim/tools/railway/get_project.ts @@ -0,0 +1,166 @@ +import type { + RailwayGetProjectParams, + RailwayGetProjectResponse, + RailwayProjectEnvironment, + RailwayProjectService, + RailwayProjectSummary, +} from '@/tools/railway/types' +import { + parseRailwayGraphqlResponse, + RAILWAY_GRAPHQL_URL, + railwayHeaders, +} from '@/tools/railway/utils' +import type { ToolConfig } from '@/tools/types' + +interface RailwayGetProjectData { + project?: RailwayProjectSummary & { + services?: { + edges?: Array<{ + node?: RailwayProjectService + }> + } + environments?: { + edges?: Array<{ + node?: RailwayProjectEnvironment + }> + } + } +} + +export const railwayGetProjectTool: ToolConfig = + { + id: 'railway_get_project', + name: 'Railway Get Project', + description: 'Get a Railway project with its services and environments', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Railway API token', + }, + tokenType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Railway token type: account, workspace, project, or oauth', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Railway project ID', + }, + }, + + request: { + url: RAILWAY_GRAPHQL_URL, + method: 'POST', + headers: (params) => railwayHeaders(params.apiKey, params.tokenType), + body: (params) => ({ + query: ` + query GetProject($id: String!) { + project(id: $id) { + id + name + description + createdAt + services { + edges { + node { + id + name + icon + } + } + } + environments { + edges { + node { + id + name + } + } + } + } + } + `, + variables: { id: params.projectId.trim() }, + }), + }, + + transformResponse: async (response: Response) => { + const data = await parseRailwayGraphqlResponse(response) + const project = data.data?.project + if (!project) throw new Error('Railway did not return a project') + + const services = (project.services?.edges ?? []) + .map((edge) => edge.node) + .filter((service): service is RailwayProjectService => Boolean(service)) + .map((service) => ({ + id: service.id, + name: service.name, + icon: service.icon ?? null, + })) + + const environments = (project.environments?.edges ?? []) + .map((edge) => edge.node) + .filter((environment): environment is RailwayProjectEnvironment => Boolean(environment)) + .map((environment) => ({ + id: environment.id, + name: environment.name, + })) + + return { + success: true, + output: { + project: { + id: project.id, + name: project.name, + description: project.description ?? null, + createdAt: project.createdAt, + services, + environments, + }, + }, + } + }, + + outputs: { + project: { + type: 'object', + description: 'Project with services and environments', + properties: { + id: { type: 'string', description: 'Project ID' }, + name: { type: 'string', description: 'Project name' }, + description: { type: 'string', description: 'Project description', optional: true }, + createdAt: { type: 'string', description: 'Project creation timestamp' }, + services: { + type: 'array', + description: 'Project services', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Service ID' }, + name: { type: 'string', description: 'Service name' }, + icon: { type: 'string', description: 'Service icon', optional: true }, + }, + }, + }, + environments: { + type: 'array', + description: 'Project environments', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Environment ID' }, + name: { type: 'string', description: 'Environment name' }, + }, + }, + }, + }, + }, + }, + } diff --git a/apps/sim/tools/railway/index.ts b/apps/sim/tools/railway/index.ts new file mode 100644 index 00000000000..44ccf94867f --- /dev/null +++ b/apps/sim/tools/railway/index.ts @@ -0,0 +1,31 @@ +import { railwayCreateEnvironmentTool } from '@/tools/railway/create_environment' +import { railwayCreateProjectTool } from '@/tools/railway/create_project' +import { railwayDeleteEnvironmentTool } from '@/tools/railway/delete_environment' +import { railwayDeleteProjectTool } from '@/tools/railway/delete_project' +import { railwayDeployServiceTool } from '@/tools/railway/deploy_service' +import { railwayGetProjectTool } from '@/tools/railway/get_project' +import { railwayListDeploymentsTool } from '@/tools/railway/list_deployments' +import { railwayListProjectMembersTool } from '@/tools/railway/list_project_members' +import { railwayListProjectsTool } from '@/tools/railway/list_projects' +import { railwayListVariablesTool } from '@/tools/railway/list_variables' +import { railwayTransferProjectTool } from '@/tools/railway/transfer_project' +import { railwayUpdateProjectTool } from '@/tools/railway/update_project' +import { railwayUpsertVariableTool } from '@/tools/railway/upsert_variable' + +export { + railwayCreateEnvironmentTool, + railwayCreateProjectTool, + railwayDeleteEnvironmentTool, + railwayDeleteProjectTool, + railwayDeployServiceTool, + railwayGetProjectTool, + railwayListDeploymentsTool, + railwayListProjectMembersTool, + railwayListProjectsTool, + railwayListVariablesTool, + railwayTransferProjectTool, + railwayUpdateProjectTool, + railwayUpsertVariableTool, +} + +export * from '@/tools/railway/types' diff --git a/apps/sim/tools/railway/list_deployments.ts b/apps/sim/tools/railway/list_deployments.ts new file mode 100644 index 00000000000..a7d0d7d3501 --- /dev/null +++ b/apps/sim/tools/railway/list_deployments.ts @@ -0,0 +1,171 @@ +import type { + RailwayDeploymentSummary, + RailwayListDeploymentsParams, + RailwayListDeploymentsResponse, + RailwayPageInfo, +} from '@/tools/railway/types' +import { + optionalString, + parseRailwayGraphqlResponse, + RAILWAY_GRAPHQL_URL, + railwayHeaders, +} from '@/tools/railway/utils' +import type { ToolConfig } from '@/tools/types' + +interface RailwayListDeploymentsData { + deployments?: { + edges?: Array<{ + node?: RailwayDeploymentSummary + }> + pageInfo?: RailwayPageInfo + } +} + +export const railwayListDeploymentsTool: ToolConfig< + RailwayListDeploymentsParams, + RailwayListDeploymentsResponse +> = { + id: 'railway_list_deployments', + name: 'Railway List Deployments', + description: 'List deployments for a Railway service in an environment', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Railway API token', + }, + tokenType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Railway token type: account, workspace, project, or oauth', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Railway project ID', + }, + serviceId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Railway service ID', + }, + environmentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Railway environment ID', + }, + first: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of deployments to return', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Cursor for pagination', + }, + }, + + request: { + url: RAILWAY_GRAPHQL_URL, + method: 'POST', + headers: (params) => railwayHeaders(params.apiKey, params.tokenType), + body: (params) => ({ + query: ` + query ListDeployments($input: DeploymentListInput!, $first: Int, $after: String) { + deployments(input: $input, first: $first, after: $after) { + edges { + node { + id + status + createdAt + url + staticUrl + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + `, + variables: { + input: { + projectId: params.projectId.trim(), + serviceId: params.serviceId.trim(), + environmentId: params.environmentId.trim(), + }, + first: params.first ? Number(params.first) : 10, + after: optionalString(params.after), + }, + }), + }, + + transformResponse: async (response: Response) => { + const data = await parseRailwayGraphqlResponse(response) + const deploymentConnection = data.data?.deployments + if (!deploymentConnection) throw new Error('Railway did not return deployments') + + const deployments = (deploymentConnection.edges ?? []) + .map((edge) => edge.node) + .filter((deployment): deployment is RailwayDeploymentSummary => Boolean(deployment)) + .map((deployment) => ({ + id: deployment.id, + status: deployment.status, + createdAt: deployment.createdAt, + url: deployment.url ?? null, + staticUrl: deployment.staticUrl ?? null, + })) + + return { + success: true, + output: { + deployments, + pageInfo: { + hasNextPage: deploymentConnection.pageInfo?.hasNextPage ?? false, + endCursor: deploymentConnection.pageInfo?.endCursor ?? null, + }, + count: deployments.length, + }, + } + }, + + outputs: { + deployments: { + type: 'array', + description: 'Service deployments', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Deployment ID' }, + status: { type: 'string', description: 'Deployment status' }, + createdAt: { type: 'string', description: 'Deployment creation timestamp' }, + url: { type: 'string', description: 'Deployment URL', optional: true }, + staticUrl: { type: 'string', description: 'Static deployment URL', optional: true }, + }, + }, + }, + count: { + type: 'number', + description: 'Number of deployments returned', + }, + pageInfo: { + type: 'object', + description: 'Pagination information', + properties: { + hasNextPage: { type: 'boolean', description: 'Whether more deployments are available' }, + endCursor: { type: 'string', description: 'Cursor for the next page', optional: true }, + }, + }, + }, +} diff --git a/apps/sim/tools/railway/list_project_members.ts b/apps/sim/tools/railway/list_project_members.ts new file mode 100644 index 00000000000..09cde0d7734 --- /dev/null +++ b/apps/sim/tools/railway/list_project_members.ts @@ -0,0 +1,123 @@ +import type { + RailwayListProjectMembersParams, + RailwayListProjectMembersResponse, + RailwayProjectMember, +} from '@/tools/railway/types' +import { + parseRailwayGraphqlResponse, + RAILWAY_GRAPHQL_URL, + railwayHeaders, +} from '@/tools/railway/utils' +import type { ToolConfig } from '@/tools/types' + +interface RailwayListProjectMembersData { + projectMembers?: RailwayProjectMember[] +} + +export const railwayListProjectMembersTool: ToolConfig< + RailwayListProjectMembersParams, + RailwayListProjectMembersResponse +> = { + id: 'railway_list_project_members', + name: 'Railway List Project Members', + description: 'List members for a Railway project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Railway API token', + }, + tokenType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Railway token type: account, workspace, project, or oauth', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Railway project ID', + }, + }, + + request: { + url: RAILWAY_GRAPHQL_URL, + method: 'POST', + headers: (params) => railwayHeaders(params.apiKey, params.tokenType), + body: (params) => ({ + query: ` + query ProjectMembers($projectId: String!) { + projectMembers(projectId: $projectId) { + id + role + user { + id + name + email + } + } + } + `, + variables: { + projectId: params.projectId.trim(), + }, + }), + }, + + transformResponse: async (response: Response) => { + const data = await parseRailwayGraphqlResponse(response) + const projectMembers = data.data?.projectMembers + if (!projectMembers) throw new Error('Railway did not return project members') + + const members = projectMembers.map((member) => ({ + id: member.id, + role: member.role, + user: member.user + ? { + id: member.user.id, + name: member.user.name ?? null, + email: member.user.email ?? null, + } + : null, + })) + + return { + success: true, + output: { + members, + count: members.length, + }, + } + }, + + outputs: { + members: { + type: 'array', + description: 'Project members', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Project membership ID' }, + role: { type: 'string', description: 'Project role' }, + user: { + type: 'object', + description: 'Railway user', + properties: { + id: { type: 'string', description: 'User ID' }, + name: { type: 'string', description: 'User name', optional: true }, + email: { type: 'string', description: 'User email', optional: true }, + }, + }, + }, + }, + }, + count: { + type: 'number', + description: 'Number of members returned', + }, + }, +} diff --git a/apps/sim/tools/railway/list_projects.ts b/apps/sim/tools/railway/list_projects.ts new file mode 100644 index 00000000000..37b1553f202 --- /dev/null +++ b/apps/sim/tools/railway/list_projects.ts @@ -0,0 +1,156 @@ +import type { + RailwayListProjectsParams, + RailwayListProjectsResponse, + RailwayPageInfo, + RailwayProjectSummary, +} from '@/tools/railway/types' +import { + compactVariables, + optionalString, + parseRailwayGraphqlResponse, + RAILWAY_GRAPHQL_URL, + railwayHeaders, +} from '@/tools/railway/utils' +import type { ToolConfig } from '@/tools/types' + +interface RailwayListProjectsData { + projects?: { + edges?: Array<{ + node?: RailwayProjectSummary + }> + pageInfo?: RailwayPageInfo + } +} + +export const railwayListProjectsTool: ToolConfig< + RailwayListProjectsParams, + RailwayListProjectsResponse +> = { + id: 'railway_list_projects', + name: 'Railway List Projects', + description: 'List Railway projects visible to the provided token', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Railway API token', + }, + tokenType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Railway token type: account, workspace, project, or oauth', + }, + workspaceId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Workspace ID to list projects from', + }, + first: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of projects to return', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Cursor for pagination', + }, + }, + + request: { + url: RAILWAY_GRAPHQL_URL, + method: 'POST', + headers: (params) => railwayHeaders(params.apiKey, params.tokenType), + body: (params) => ({ + query: ` + query ListProjects($workspaceId: String, $first: Int, $after: String) { + projects(workspaceId: $workspaceId, first: $first, after: $after) { + edges { + node { + id + name + description + createdAt + updatedAt + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + `, + variables: compactVariables({ + workspaceId: optionalString(params.workspaceId), + first: params.first ? Number(params.first) : undefined, + after: optionalString(params.after), + }), + }), + }, + + transformResponse: async (response: Response) => { + const data = await parseRailwayGraphqlResponse(response) + const projectConnection = data.data?.projects + if (!projectConnection) throw new Error('Railway did not return projects') + + const projects = (projectConnection.edges ?? []) + .map((edge) => edge.node) + .filter((project): project is RailwayProjectSummary => Boolean(project)) + .map((project) => ({ + id: project.id, + name: project.name, + description: project.description ?? null, + createdAt: project.createdAt, + updatedAt: project.updatedAt ?? null, + })) + + return { + success: true, + output: { + projects, + pageInfo: { + hasNextPage: projectConnection.pageInfo?.hasNextPage ?? false, + endCursor: projectConnection.pageInfo?.endCursor ?? null, + }, + count: projects.length, + }, + } + }, + + outputs: { + projects: { + type: 'array', + description: 'Railway projects', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Project ID' }, + name: { type: 'string', description: 'Project name' }, + description: { type: 'string', description: 'Project description', optional: true }, + createdAt: { type: 'string', description: 'Project creation timestamp' }, + updatedAt: { type: 'string', description: 'Project update timestamp', optional: true }, + }, + }, + }, + pageInfo: { + type: 'object', + description: 'Pagination information', + properties: { + hasNextPage: { type: 'boolean', description: 'Whether more projects are available' }, + endCursor: { type: 'string', description: 'Cursor for the next page', optional: true }, + }, + }, + count: { + type: 'number', + description: 'Number of projects returned', + }, + }, +} diff --git a/apps/sim/tools/railway/list_variables.ts b/apps/sim/tools/railway/list_variables.ts new file mode 100644 index 00000000000..91150d6559e --- /dev/null +++ b/apps/sim/tools/railway/list_variables.ts @@ -0,0 +1,102 @@ +import type { + RailwayListVariablesParams, + RailwayListVariablesResponse, +} from '@/tools/railway/types' +import { + compactVariables, + optionalString, + parseRailwayGraphqlResponse, + RAILWAY_GRAPHQL_URL, + railwayHeaders, +} from '@/tools/railway/utils' +import type { ToolConfig } from '@/tools/types' + +interface RailwayListVariablesData { + variables?: Record +} + +export const railwayListVariablesTool: ToolConfig< + RailwayListVariablesParams, + RailwayListVariablesResponse +> = { + id: 'railway_list_variables', + name: 'Railway List Variables', + description: 'List Railway environment variables for a service or shared environment', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Railway API token', + }, + tokenType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Railway token type: account, workspace, project, or oauth', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Railway project ID', + }, + environmentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Railway environment ID', + }, + serviceId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Railway service ID. Omit for shared environment variables.', + }, + }, + + request: { + url: RAILWAY_GRAPHQL_URL, + method: 'POST', + headers: (params) => railwayHeaders(params.apiKey, params.tokenType), + body: (params) => ({ + query: ` + query Variables($projectId: String!, $environmentId: String!, $serviceId: String) { + variables(projectId: $projectId, environmentId: $environmentId, serviceId: $serviceId) + } + `, + variables: compactVariables({ + projectId: params.projectId.trim(), + environmentId: params.environmentId.trim(), + serviceId: optionalString(params.serviceId), + }), + }), + }, + + transformResponse: async (response: Response) => { + const data = await parseRailwayGraphqlResponse(response) + const variables = data.data?.variables + if (!variables) throw new Error('Railway did not return variables') + + return { + success: true, + output: { + variables, + count: Object.keys(variables).length, + }, + } + }, + + outputs: { + variables: { + type: 'object', + description: 'Variable names and values', + }, + count: { + type: 'number', + description: 'Number of variables returned', + }, + }, +} diff --git a/apps/sim/tools/railway/transfer_project.ts b/apps/sim/tools/railway/transfer_project.ts new file mode 100644 index 00000000000..b0894d9e114 --- /dev/null +++ b/apps/sim/tools/railway/transfer_project.ts @@ -0,0 +1,91 @@ +import type { + RailwayTransferProjectParams, + RailwayTransferProjectResponse, +} from '@/tools/railway/types' +import { + parseRailwayGraphqlResponse, + RAILWAY_GRAPHQL_URL, + railwayHeaders, +} from '@/tools/railway/utils' +import type { ToolConfig } from '@/tools/types' + +interface RailwayTransferProjectData { + projectTransfer?: boolean +} + +export const railwayTransferProjectTool: ToolConfig< + RailwayTransferProjectParams, + RailwayTransferProjectResponse +> = { + id: 'railway_transfer_project', + name: 'Railway Transfer Project', + description: 'Transfer a Railway project to another workspace', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Railway API token', + }, + tokenType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Railway token type: account, workspace, project, or oauth', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Railway project ID', + }, + workspaceId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Destination workspace ID', + }, + }, + + request: { + url: RAILWAY_GRAPHQL_URL, + method: 'POST', + headers: (params) => railwayHeaders(params.apiKey, params.tokenType), + body: (params) => ({ + query: ` + mutation TransferProject($projectId: String!, $input: ProjectTransferInput!) { + projectTransfer(projectId: $projectId, input: $input) + } + `, + variables: { + projectId: params.projectId.trim(), + input: { + workspaceId: params.workspaceId.trim(), + }, + }, + }), + }, + + transformResponse: async (response: Response) => { + const data = await parseRailwayGraphqlResponse(response) + if (typeof data.data?.projectTransfer !== 'boolean') { + throw new Error('Railway did not return a project transfer result') + } + + return { + success: true, + output: { + success: data.data.projectTransfer, + }, + } + }, + + outputs: { + success: { + type: 'boolean', + description: 'Whether the project was transferred', + }, + }, +} diff --git a/apps/sim/tools/railway/types.ts b/apps/sim/tools/railway/types.ts new file mode 100644 index 00000000000..deb68792614 --- /dev/null +++ b/apps/sim/tools/railway/types.ts @@ -0,0 +1,245 @@ +import type { ToolResponse } from '@/tools/types' + +export type RailwayTokenType = 'account' | 'workspace' | 'project' | 'oauth' + +export interface RailwayAuthParams { + apiKey: string + tokenType?: RailwayTokenType +} + +export interface RailwayPageInfo { + hasNextPage: boolean + endCursor: string | null +} + +export interface RailwayProjectSummary { + id: string + name: string + description: string | null + createdAt: string + updatedAt?: string | null +} + +export interface RailwayUpdatedProject { + id: string + name: string + description: string | null +} + +export interface RailwayProjectService { + id: string + name: string + icon: string | null +} + +export interface RailwayProjectEnvironment { + id: string + name: string +} + +export interface RailwayProjectMember { + id: string + role: string + user: { + id: string + name: string | null + email: string | null + } | null +} + +export interface RailwayCreatedResource { + id: string + name: string +} + +export interface RailwayDeploymentSummary { + id: string + status: string + createdAt: string + url: string | null + staticUrl: string | null +} + +export interface RailwayListProjectsParams extends RailwayAuthParams { + workspaceId?: string + first?: number + after?: string +} + +export interface RailwayGetProjectParams extends RailwayAuthParams { + projectId: string +} + +export interface RailwayCreateProjectParams extends RailwayAuthParams { + name: string + description?: string + workspaceId?: string + isPublic?: boolean + defaultEnvironmentName?: string + prDeploys?: boolean +} + +export interface RailwayUpdateProjectParams extends RailwayAuthParams { + projectId: string + name?: string + description?: string + isPublic?: boolean + prDeploys?: boolean +} + +export interface RailwayDeleteProjectParams extends RailwayAuthParams { + projectId: string +} + +export interface RailwayTransferProjectParams extends RailwayAuthParams { + projectId: string + workspaceId: string +} + +export interface RailwayListProjectMembersParams extends RailwayAuthParams { + projectId: string +} + +export interface RailwayCreateEnvironmentParams extends RailwayAuthParams { + projectId: string + name: string + sourceEnvironmentId?: string + ephemeral?: boolean + skipInitialDeploys?: boolean + stageInitialChanges?: boolean +} + +export interface RailwayDeleteEnvironmentParams extends RailwayAuthParams { + environmentId: string +} + +export interface RailwayListDeploymentsParams extends RailwayAuthParams { + projectId: string + serviceId: string + environmentId: string + first?: number + after?: string +} + +export interface RailwayDeployServiceParams extends RailwayAuthParams { + serviceId: string + environmentId: string + commitSha?: string +} + +export interface RailwayListVariablesParams extends RailwayAuthParams { + projectId: string + environmentId: string + serviceId?: string +} + +export interface RailwayUpsertVariableParams extends RailwayAuthParams { + projectId: string + environmentId: string + name: string + value: string + serviceId?: string + skipDeploys?: boolean +} + +export interface RailwayListProjectsResponse extends ToolResponse { + output: { + projects: RailwayProjectSummary[] + pageInfo: RailwayPageInfo + count: number + } +} + +export interface RailwayGetProjectResponse extends ToolResponse { + output: { + project: RailwayProjectSummary & { + services: RailwayProjectService[] + environments: RailwayProjectEnvironment[] + } + } +} + +export interface RailwayCreateProjectResponse extends ToolResponse { + output: { + project: RailwayCreatedResource + } +} + +export interface RailwayUpdateProjectResponse extends ToolResponse { + output: { + project: RailwayUpdatedProject + } +} + +export interface RailwayDeleteProjectResponse extends ToolResponse { + output: { + success: boolean + } +} + +export interface RailwayTransferProjectResponse extends ToolResponse { + output: { + success: boolean + } +} + +export interface RailwayListProjectMembersResponse extends ToolResponse { + output: { + members: RailwayProjectMember[] + count: number + } +} + +export interface RailwayCreateEnvironmentResponse extends ToolResponse { + output: { + environment: RailwayCreatedResource + } +} + +export interface RailwayDeleteEnvironmentResponse extends ToolResponse { + output: { + success: boolean + } +} + +export interface RailwayListDeploymentsResponse extends ToolResponse { + output: { + deployments: RailwayDeploymentSummary[] + pageInfo: RailwayPageInfo + count: number + } +} + +export interface RailwayDeployServiceResponse extends ToolResponse { + output: { + deploymentId: string + } +} + +export interface RailwayListVariablesResponse extends ToolResponse { + output: { + variables: Record + count: number + } +} + +export interface RailwayUpsertVariableResponse extends ToolResponse { + output: { + success: boolean + } +} + +export type RailwayResponse = + | RailwayListProjectsResponse + | RailwayGetProjectResponse + | RailwayCreateProjectResponse + | RailwayUpdateProjectResponse + | RailwayDeleteProjectResponse + | RailwayTransferProjectResponse + | RailwayListProjectMembersResponse + | RailwayCreateEnvironmentResponse + | RailwayDeleteEnvironmentResponse + | RailwayListDeploymentsResponse + | RailwayDeployServiceResponse + | RailwayListVariablesResponse + | RailwayUpsertVariableResponse diff --git a/apps/sim/tools/railway/update_project.ts b/apps/sim/tools/railway/update_project.ts new file mode 100644 index 00000000000..3e021d4083c --- /dev/null +++ b/apps/sim/tools/railway/update_project.ts @@ -0,0 +1,127 @@ +import type { + RailwayUpdatedProject, + RailwayUpdateProjectParams, + RailwayUpdateProjectResponse, +} from '@/tools/railway/types' +import { + compactVariables, + optionalString, + parseRailwayGraphqlResponse, + RAILWAY_GRAPHQL_URL, + railwayHeaders, +} from '@/tools/railway/utils' +import type { ToolConfig } from '@/tools/types' + +interface RailwayUpdateProjectData { + projectUpdate?: RailwayUpdatedProject +} + +export const railwayUpdateProjectTool: ToolConfig< + RailwayUpdateProjectParams, + RailwayUpdateProjectResponse +> = { + id: 'railway_update_project', + name: 'Railway Update Project', + description: 'Update a Railway project name or description', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Railway API token', + }, + tokenType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Railway token type: account, workspace, project, or oauth', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Railway project ID', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated project name', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated project description', + }, + isPublic: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the project should be publicly visible', + }, + prDeploys: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to enable pull request deploy environments', + }, + }, + + request: { + url: RAILWAY_GRAPHQL_URL, + method: 'POST', + headers: (params) => railwayHeaders(params.apiKey, params.tokenType), + body: (params) => ({ + query: ` + mutation UpdateProject($id: String!, $input: ProjectUpdateInput!) { + projectUpdate(id: $id, input: $input) { + id + name + description + } + } + `, + variables: { + id: params.projectId.trim(), + input: compactVariables({ + name: optionalString(params.name), + description: optionalString(params.description), + isPublic: params.isPublic, + prDeploys: params.prDeploys, + }), + }, + }), + }, + + transformResponse: async (response: Response) => { + const data = await parseRailwayGraphqlResponse(response) + const project = data.data?.projectUpdate + if (!project) throw new Error('Railway did not return an updated project') + + return { + success: true, + output: { + project: { + id: project.id, + name: project.name, + description: project.description ?? null, + }, + }, + } + }, + + outputs: { + project: { + type: 'object', + description: 'Updated project', + properties: { + id: { type: 'string', description: 'Project ID' }, + name: { type: 'string', description: 'Project name' }, + description: { type: 'string', description: 'Project description', optional: true }, + }, + }, + }, +} diff --git a/apps/sim/tools/railway/upsert_variable.ts b/apps/sim/tools/railway/upsert_variable.ts new file mode 100644 index 00000000000..120b9226496 --- /dev/null +++ b/apps/sim/tools/railway/upsert_variable.ts @@ -0,0 +1,123 @@ +import type { + RailwayUpsertVariableParams, + RailwayUpsertVariableResponse, +} from '@/tools/railway/types' +import { + compactVariables, + optionalString, + parseRailwayGraphqlResponse, + RAILWAY_GRAPHQL_URL, + railwayHeaders, +} from '@/tools/railway/utils' +import type { ToolConfig } from '@/tools/types' + +interface RailwayUpsertVariableData { + variableUpsert?: boolean +} + +export const railwayUpsertVariableTool: ToolConfig< + RailwayUpsertVariableParams, + RailwayUpsertVariableResponse +> = { + id: 'railway_upsert_variable', + name: 'Railway Upsert Variable', + description: 'Create or update a Railway environment variable', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Railway API token', + }, + tokenType: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Railway token type: account, workspace, project, or oauth', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Railway project ID', + }, + environmentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Railway environment ID', + }, + serviceId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Railway service ID. Omit to create or update a shared variable.', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Variable name', + }, + value: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Variable value', + }, + skipDeploys: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to skip automatic redeploys after changing the variable', + }, + }, + + request: { + url: RAILWAY_GRAPHQL_URL, + method: 'POST', + headers: (params) => railwayHeaders(params.apiKey, params.tokenType), + body: (params) => ({ + query: ` + mutation UpsertVariable($input: VariableUpsertInput!) { + variableUpsert(input: $input) + } + `, + variables: { + input: { + projectId: params.projectId.trim(), + environmentId: params.environmentId.trim(), + name: params.name.trim(), + value: params.value, + ...compactVariables({ + serviceId: optionalString(params.serviceId), + skipDeploys: params.skipDeploys, + }), + }, + }, + }), + }, + + transformResponse: async (response: Response) => { + const data = await parseRailwayGraphqlResponse(response) + if (typeof data.data?.variableUpsert !== 'boolean') { + throw new Error('Railway did not return a variable upsert result') + } + + return { + success: true, + output: { + success: data.data.variableUpsert, + }, + } + }, + + outputs: { + success: { + type: 'boolean', + description: 'Whether the variable was created or updated', + }, + }, +} diff --git a/apps/sim/tools/railway/utils.ts b/apps/sim/tools/railway/utils.ts new file mode 100644 index 00000000000..02e1f37160f --- /dev/null +++ b/apps/sim/tools/railway/utils.ts @@ -0,0 +1,58 @@ +import type { RailwayTokenType } from '@/tools/railway/types' + +export const RAILWAY_GRAPHQL_URL = 'https://backboard.railway.com/graphql/v2' + +interface RailwayGraphqlError { + message?: string +} + +interface RailwayGraphqlResponse { + data?: TData + errors?: RailwayGraphqlError[] +} + +export function railwayHeaders( + apiKey: string, + tokenType?: RailwayTokenType +): Record { + if (!apiKey) { + throw new Error('Missing API token for Railway API request') + } + + if (tokenType === 'project') { + return { + 'Content-Type': 'application/json', + 'Project-Access-Token': apiKey, + } + } + + return { + 'Content-Type': 'application/json', + Authorization: `Bearer ${apiKey}`, + } +} + +export async function parseRailwayGraphqlResponse( + response: Response +): Promise> { + const data = (await response.json()) as RailwayGraphqlResponse + + if (!response.ok) { + throw new Error(data.errors?.[0]?.message ?? `HTTP ${response.status}: ${response.statusText}`) + } + + if (data.errors?.length) { + throw new Error(data.errors[0]?.message ?? 'Railway API returned a GraphQL error') + } + + return data +} + +export function compactVariables(input: Record) { + return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined)) +} + +export function optionalString(value?: string): string | undefined { + const trimmed = value?.trim() + return trimmed ? trimmed : undefined +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index b65d6bda699..278891187a9 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -925,6 +925,7 @@ import { import { gongAggregateActivityTool, gongAnsweredScorecardsTool, + gongCreateCallTool, gongGetCallTool, gongGetCallTranscriptTool, gongGetCoachingTool, @@ -1287,6 +1288,7 @@ import { incidentioCustomFieldsUpdateTool, incidentioEscalationPathsCreateTool, incidentioEscalationPathsDeleteTool, + incidentioEscalationPathsListTool, incidentioEscalationPathsShowTool, incidentioEscalationPathsUpdateTool, incidentioEscalationsCreateTool, @@ -1824,6 +1826,12 @@ import { neo4jQueryTool, neo4jUpdateTool, } from '@/tools/neo4j' +import { + newRelicCreateDeploymentEventTool, + newRelicGetEntityTool, + newRelicNrqlQueryTool, + newRelicSearchEntitiesTool, +} from '@/tools/new_relic' import { notionAddDatabaseRowTool, notionAddDatabaseRowV2Tool, @@ -2075,6 +2083,21 @@ import { import { pulseParserTool, pulseParserV2Tool } from '@/tools/pulse' import { qdrantFetchTool, qdrantSearchTool, qdrantUpsertTool } from '@/tools/qdrant' import { quiverImageToSvgTool, quiverListModelsTool, quiverTextToSvgTool } from '@/tools/quiver' +import { + railwayCreateEnvironmentTool, + railwayCreateProjectTool, + railwayDeleteEnvironmentTool, + railwayDeleteProjectTool, + railwayDeployServiceTool, + railwayGetProjectTool, + railwayListDeploymentsTool, + railwayListProjectMembersTool, + railwayListProjectsTool, + railwayListVariablesTool, + railwayTransferProjectTool, + railwayUpdateProjectTool, + railwayUpsertVariableTool, +} from '@/tools/railway' import { rdsDeleteTool, rdsExecuteTool, @@ -3269,22 +3292,23 @@ export const tools: Record = { fireflies_create_bite: firefliesCreateBiteTool, fireflies_list_bites: firefliesListBitesTool, fireflies_list_contacts: firefliesListContactsTool, - gong_list_calls: gongListCallsTool, + gong_aggregate_activity: gongAggregateActivityTool, + gong_answered_scorecards: gongAnsweredScorecardsTool, + gong_create_call: gongCreateCallTool, gong_get_call: gongGetCallTool, gong_get_call_transcript: gongGetCallTranscriptTool, + gong_get_coaching: gongGetCoachingTool, gong_get_extensive_calls: gongGetExtensiveCallsTool, - gong_list_users: gongListUsersTool, + gong_get_folder_content: gongGetFolderContentTool, gong_get_user: gongGetUserTool, - gong_aggregate_activity: gongAggregateActivityTool, gong_interaction_stats: gongInteractionStatsTool, - gong_answered_scorecards: gongAnsweredScorecardsTool, + gong_list_calls: gongListCallsTool, + gong_list_flows: gongListFlowsTool, gong_list_library_folders: gongListLibraryFoldersTool, - gong_get_folder_content: gongGetFolderContentTool, gong_list_scorecards: gongListScorecardsTool, gong_list_trackers: gongListTrackersTool, + gong_list_users: gongListUsersTool, gong_list_workspaces: gongListWorkspacesTool, - gong_list_flows: gongListFlowsTool, - gong_get_coaching: gongGetCoachingTool, gong_lookup_email: gongLookupEmailTool, gong_lookup_phone: gongLookupPhoneTool, grafana_get_dashboard: grafanaGetDashboardTool, @@ -3942,6 +3966,10 @@ export const tools: Record = { neo4j_delete: neo4jDeleteTool, neo4j_execute: neo4jExecuteTool, neo4j_introspect: neo4jIntrospectTool, + new_relic_create_deployment_event: newRelicCreateDeploymentEventTool, + new_relic_get_entity: newRelicGetEntityTool, + new_relic_nrql_query: newRelicNrqlQueryTool, + new_relic_search_entities: newRelicSearchEntitiesTool, github_pr: githubPrTool, github_pr_v2: githubPrV2Tool, github_comment: githubCommentTool, @@ -4216,18 +4244,6 @@ export const tools: Record = { exa_find_similar_links: exaFindSimilarLinksTool, exa_answer: exaAnswerTool, exa_research: exaResearchTool, - incidentio_escalations_list: incidentioEscalationsListTool, - incidentio_escalations_create: incidentioEscalationsCreateTool, - incidentio_escalations_show: incidentioEscalationsShowTool, - incidentio_schedules_list: incidentioSchedulesListTool, - incidentio_schedules_create: incidentioSchedulesCreateTool, - incidentio_schedules_show: incidentioSchedulesShowTool, - incidentio_schedules_update: incidentioSchedulesUpdateTool, - incidentio_schedules_delete: incidentioSchedulesDeleteTool, - incidentio_custom_fields_create: incidentioCustomFieldsCreateTool, - incidentio_custom_fields_show: incidentioCustomFieldsShowTool, - incidentio_custom_fields_update: incidentioCustomFieldsUpdateTool, - incidentio_custom_fields_delete: incidentioCustomFieldsDeleteTool, parallel_search: parallelSearchTool, parallel_extract: parallelExtractTool, parallel_deep_research: parallelDeepResearchTool, @@ -5313,6 +5329,19 @@ export const tools: Record = { qdrant_fetch_points: qdrantFetchTool, qdrant_search_vector: qdrantSearchTool, qdrant_upsert_points: qdrantUpsertTool, + railway_create_environment: railwayCreateEnvironmentTool, + railway_create_project: railwayCreateProjectTool, + railway_delete_environment: railwayDeleteEnvironmentTool, + railway_delete_project: railwayDeleteProjectTool, + railway_deploy_service: railwayDeployServiceTool, + railway_get_project: railwayGetProjectTool, + railway_list_deployments: railwayListDeploymentsTool, + railway_list_project_members: railwayListProjectMembersTool, + railway_list_projects: railwayListProjectsTool, + railway_list_variables: railwayListVariablesTool, + railway_transfer_project: railwayTransferProjectTool, + railway_update_project: railwayUpdateProjectTool, + railway_upsert_variable: railwayUpsertVariableTool, hunter_discover: hunterDiscoverTool, hunter_domain_search: hunterDomainSearchTool, hunter_email_finder: hunterEmailFinderTool, @@ -5366,6 +5395,8 @@ export const tools: Record = { incidentio_incidents_update: incidentioIncidentsUpdateTool, incidentio_actions_list: incidentioActionsListTool, incidentio_actions_show: incidentioActionsShowTool, + incidentio_custom_fields_create: incidentioCustomFieldsCreateTool, + incidentio_custom_fields_delete: incidentioCustomFieldsDeleteTool, incidentio_follow_ups_list: incidentioFollowUpsListTool, incidentio_follow_ups_show: incidentioFollowUpsShowTool, incidentio_workflows_list: incidentioWorkflowsListTool, @@ -5374,6 +5405,11 @@ export const tools: Record = { incidentio_workflows_update: incidentioWorkflowsUpdateTool, incidentio_workflows_delete: incidentioWorkflowsDeleteTool, incidentio_custom_fields_list: incidentioCustomFieldsListTool, + incidentio_custom_fields_show: incidentioCustomFieldsShowTool, + incidentio_custom_fields_update: incidentioCustomFieldsUpdateTool, + incidentio_escalations_create: incidentioEscalationsCreateTool, + incidentio_escalations_list: incidentioEscalationsListTool, + incidentio_escalations_show: incidentioEscalationsShowTool, incidentio_users_list: incidentioUsersListTool, incidentio_users_show: incidentioUsersShowTool, incidentio_severities_list: incidentioSeveritiesListTool, @@ -5389,7 +5425,13 @@ export const tools: Record = { incidentio_incident_updates_list: incidentioIncidentUpdatesListTool, incidentio_schedule_entries_list: incidentioScheduleEntriesListTool, incidentio_schedule_overrides_create: incidentioScheduleOverridesCreateTool, + incidentio_schedules_create: incidentioSchedulesCreateTool, + incidentio_schedules_delete: incidentioSchedulesDeleteTool, + incidentio_schedules_list: incidentioSchedulesListTool, + incidentio_schedules_show: incidentioSchedulesShowTool, + incidentio_schedules_update: incidentioSchedulesUpdateTool, incidentio_escalation_paths_create: incidentioEscalationPathsCreateTool, + incidentio_escalation_paths_list: incidentioEscalationPathsListTool, incidentio_escalation_paths_show: incidentioEscalationPathsShowTool, incidentio_escalation_paths_update: incidentioEscalationPathsUpdateTool, incidentio_escalation_paths_delete: incidentioEscalationPathsDeleteTool,